Jump to content

Module:Sort/cellNum

Википедиа — Чөлөөт нэвтэрхий толь
local Sort = { suite   = "Sort",
               sub     = "cellNum",
               serial  = "2020-05-14",
               item    = 88370026,
               globals = { Cell      = 90144855,
                           FormatNum = 15709679 } }
--[=[
Sort/cellNum
         support table cells with numerical content and number formatting
]=]
local Failsafe  = Sort
local GlobalMod = Sort



Sort.digits  = { {    0x30 },
                 {  0x0660, "Arab" },
                 {  0x06F0, "Arab" },
                 {  0x07C0, "Nkoo" },
                 {  0x0966, "Deva" },
                 {  0x09E6, "Beng" },
                 {  0x0A66, "Guru" },
                 {  0x0AE6, "Gujr" },
                 {  0x0B66, "Orya" },
                 {  0x0BE6, "Taml" },
                 {  0x0C66, "Telu" },
                 {  0x0CE6, "Knda" },
                 {  0x0D66, "Mlym" },
                 {  0x0DE6, "Sinh" },
                 {  0x0E50, "Thai" },
                 {  0x0ED0, "Laoo" },
                 {  0x0F20, "Tibt" },
                 {  0x1040, "Mymr" },
                 {  0x1369, "Ethi" },
                 {  0x17E0, "Khmr" },
                 {  0x1810, "Mong" },
                 {  0x1946, "Limb" },
                 {  0x19D0, "Talu" },
                 {  0x1A80, "Lana" },
                 {  0x1A90, "Lana" },
                 {  0x1B50, "Bali" },
                 {  0x1BB0, "Sund" },
                 {  0x1C40, "Lepc" },
                 {  0x1C50, "Olck" },
                 {  0xA620, "Vaii" },
                 {  0xA8D0, "Saur" },
                 {  0xA900, "Kali" },
                 {  0xA9D0, "Java" },
               --{  0xA9F0, "" },        MYANMAR TAI LAING
                 {  0xAA50, "Cham" },
                 {  0xABF0, "Mtei" },
                 {  0x102E, "Copt" },
                 { 0x1104A, "Osma" },
                 { 0x110D3, "Rohg" },
               --{ 0x110E6, "" },        RUMI
                 { 0x11106, "Brah" },
                 { 0x1110F, "Sora" },
                 { 0x11113, "Cakm" },
                 { 0x1111D, "Shrd" },
                 { 0x1112F, "Sind" },
                 { 0x11145, "Newa" },
                 { 0x1114D, "Tirh" },
                 { 0x11165, "Modi" },
                 { 0x1116C, "Takr" },
                 { 0x11173, "Ahom" },
                 { 0x1118E, "Wara" },
                 { 0x111C5, "Bhks" },
                 { 0x111D5, "Gonm" },
                 { 0x111DA, "Gong" },
                 { 0x116A6, "Mroo" },
                 { 0x116B5, "Hmng" },
                 { 0x116E8, "Medf" },
                 { 0x11E14, "Hmnp" },
                 { 0x11E2F, "Wcho" },
                 { 0x11E8C, "Mend" },
                 { 0x11E95, "Adlm" }
               }
Sort.heading = { [0x2D]   =  45,   -- -
                 [0x2212] =  45,   -- -
                 [0x2B]   =  43    -- +
               }
Sort.mpz     = -0.5
Sort.prefix  = { [0x003C] = -5,
                 [0x003E] =  5,
                 [0x00B1] =  true,
                 [0x2248] =  false,
                 [0x2264] = -2,
                 [0x2265] =  2
               }
Sort.supreme = mw.ustring.char( 8734 )    -- infinit



local foreignModule = function ( access, advanced, append, alt, alert )
    -- Fetch global module
    -- Precondition:
    --     access    -- string, with name of base module
    --     advanced  -- true, for require(); else mw.loadData()
    --     append    -- string, with subpage part, if any; or false
    --     alt       -- number, of wikidata item of root; or false
    --     alert     -- true, for throwing error on data problem
    -- Postcondition:
    --     Returns whatever, probably table
    -- 2020-01-01
    local storage = access
    local finer   = function ()
                        if append then
                            storage = string.format( "%s/%s",
                                                     storage,
                                                     append )
                        end
                    end
    local fun, lucky, r, suited
    if advanced then
        fun = require
    else
        fun = mw.loadData
    end
    GlobalMod.globalModules = GlobalMod.globalModules or { }
    suited = GlobalMod.globalModules[ access ]
    if not suited then
        finer()
        lucky, r = pcall( fun,  "Module:" .. storage )
    end
    if not lucky then
        if not suited  and
           type( alt ) == "number"  and
           alt > 0 then
            suited = string.format( "Q%d", alt )
            suited = mw.wikibase.getSitelink( suited )
            GlobalMod.globalModules[ access ] = suited or true
        end
        if type( suited ) == "string" then
            storage = suited
            finer()
            lucky, r = pcall( fun, storage )
        end
        if not lucky and alert then
            error( "Missing or invalid page: " .. storage )
        end
    end
    return r
end -- foreignModule()



local fetch = function ( access, advanced, append )
    -- Fetch global library
    -- Precondition:
    --     access    -- string|false, with name of base module
    --     advanced  -- true, for require(); else mw.loadData()
    --     append    -- string, with subpage part, if any; or false
    local store, sub, suite
    if access then
        suite = access
        store = access
        sub   = append
    else
        suite = Sort.suite
        if append then
            sub   = append:lower()
            store = append
        else
            store = "Sorter"
        end
    end
    if type( Sort[ store ] ) == "nil" then
        local bib = foreignModule( suite,
                                   advanced,
                                   sub,
                                   Sort.globals[ store ],
                                   not access )
        if bib  and  type( bib[ suite ] ) == "function" then
            Sort[ store ] = bib[ suite ]()
        elseif advanced then
            error( tostring( bib ) )
        else
            Sort[ store ] = bib
        end
    end
    return Sort[ store ]
end -- fetch()



local factory = function ( ask )
    -- Ensure config data
    -- Precondition:
    --     assign    -- string|nil, particular query
    -- Postcondition:
    --     config data available
    --     Returns elements for "*", 10, 1000, fractpart
    local r
    if not ask then
        Sort.minus = Sort.minus  or  mw.ustring.char( 0x2212 )
    elseif ask == "sep" then
        if not Sort.sepDec then
            local seek = "%d(%p?)(%d?%d?456)(%p)7"
            local s
            Sort.contLang = Sort.contLang  or
                            mw.language.getContentLanguage()
            s             = Sort.contLang:formatNum( 123456.7 )
            Sort.sepGroup, s, Sort.sepDec = mw.ustring.match( s, seek )
            Sort.sepDec = Sort.sepDec or "."
            Sort.keyDec = mw.ustring.codepoint( Sort.sepDec, 1, 1 )
            if s then
                Sort.nGroup = #s
            end
            if Sort.sepDec == "." then
                Sort.seekDec = "%%."
            else
                Sort.seekDec = Sort.sepDec
            end
            if Sort.sepGroup  and  Sort.sepGroup ~= "" then
                if Sort.sepGroup == "." then
                    Sort.seekGroup = "%."
                else
                    Sort.seekGroup = Sort.sepGroup
                end
                Sort.separated = string.format( "%s%%d%%d%%d%s",
                                                Sort.seekGroup,
                                                Sort.seekGroup )
            end
        end
    elseif ask == "dec" then
        if not Sort.spanDec then
            if Sort.sepDec == "." then
                Sort.spanDec = "."
            else
                local e = mw.html.create( "span" )
                                 :addClass( "numericFormat-dec" )
                                 :node( mw.html.create( "span" )
                                               :wikitext( "." ) )
                Sort.spanDec = tostring( e )
            end
        end
    elseif ask == "minus" then
        if not Sort.spanMinus then
            local e = mw.html.create( "span" )
                             :addClass( "numericFormat-minus" )
                                 :node( mw.html.create( "span" )
                                               :wikitext( "-" ) )
            Sort.spanMinus = tostring( e )
        end
    elseif ask == 1000 then
        r = mw.html.create( "span" )
                   :addClass( "numericFormat-1000" )
    elseif ask == "fractpart" then
        r = mw.html.create( "span" )
                   :addClass( "numericFormat-fractpart" )
    elseif ask == "*" then
        r = mw.html.create( "span" )
                   :addClass( "numericFormat-multiply" )
    elseif ask == 10 then
        r = mw.html.create( "span" )
                   :addClass( "numericFormat-10" )
        --    ::before { content: "10"; }
    end
    return r
end -- factory()



local feeder = function ( access )
    -- Retrieve first TemplateStyles transclusion
    -- Precondition:
    --     access   -- string, TemplateStyles ID
    local ts = Sort[ access ]
    local r
    if ts  and  not  ts.loaded then
        local s = type( ts.origin )
        local src
        ts.loaded = true
        if s == "string" then
            src = ts.origin
        elseif s == "table" then
            src = ts.origin.prefixedText
        end
        if src then
            Sort.frame = Sort.frame or mw.getCurrentFrame()
            r = Sort.frame:extensionTag( "templatestyles",
                                         nil,
                                         { src = src } )
        end
    end
    return r or ""
end -- feeder()



local fine = function ( about )
    -- Equip number with styled special characters
    -- Precondition:
    --     about    -- table, parameters of base number
    --                 .show   -- string, digits
    --                 .low    -- boolean, heading minus
    --                 .sign   -- string, heading sign
    --                 .sub    -- string, decimal fraction
    --                 .long   -- boolean, grouping possible
    -- Postcondition:
    --     Returns updated entire presentation
    local r
    if about.long then
        factory( "sep" )
        local n = mw.ustring.len( about.show )
        local k = n - Sort.nGroup + 1
        local i, m, e, s
        r = mw.ustring.sub( about.show, k )
        n = k - Sort.nGroup
        for j = n,  -1,  - Sort.nGroup do
            if j > 0 then
                i = j
            else
                i = 1
            end
            m = j + Sort.nGroup - 1
            s = mw.ustring.sub( about.show, i, m )
            e = factory( 1000 ):wikitext( s )
            r = tostring( e ) .. r
        end --  for j
    end
    r = r or about.show or about.scream
    if about.low then
        factory( "minus" )
        r = Sort.spanMinus .. r
    elseif about.sign then
        r = about.sign .. r
    end
    if about.sub then
        local s = about.sub
        factory( "sep" )
        factory( "dec" )
        r = r .. Sort.spanDec
        if Sort.nGroup then
            local n = mw.ustring.len( s )
            if n  >  Sort.nGroup + 2 then
                local k = 0
                local m = n - Sort.nGroup
                local e, sg
                for j = 1, m, Sort.nGroup do
                    sg = mw.ustring.sub( about.sub,
                                         j,
                                         j + Sort.nGroup - 1 )
                    e  = factory( "fractpart" ):wikitext( sg )
                    r  = r .. tostring( e )
                    k  = k + Sort.nGroup
                end --  for j
                s = s:sub( k + 1,  n )
            end
        end
        r = r .. s
    end
    return feeder( "cssNum" ) .. r
end -- fine()



local fined = function ( all, assign )
    -- Append styled number
    -- Precondition:
    --     all       -- string, formatted entire presentation
    --     assign    -- table, exponent
    --                  .show   -- string, digits
    --                  .low    -- boolean, < 0
    -- Postcondition:
    --     Returns
    --         1. string, updated entire presentation
    --         2. string, styled number
    local r1 = all
    local r2 = assign.show
    if assign.low then
        if Sort.cssNum then
            factory( "minus" )
            r2 = Sort.spanMinus .. r2
            r1 = feeder( "cssNum" ) .. r1
        else
            factory()
            r2 = Sort.minus .. r2
        end
    end
    return r1, r2
end -- fined()



local finest = function ( args, ahead )
    -- Append styled decimal power
    -- Precondition:
    --     args     -- table, parameters
    --                 .exp    -- table, exponent
    --     ahead    -- string, formatted presentation
    -- Postcondition:
    --     Returns expanded presentation
    local r = string.format( "%s%s%s",
                             feeder( "cssNum" ),
                             feeder( "cssNumExp" ),
                             ahead )
    local em = factory( "*" )
    local ep = factory( 10 )
    local s
    r, s = fined( r, args.exp )
    em:wikitext( "e" )
    ep:node( mw.html.create( "sup" )
                    :wikitext( s ) )
    s = Sort.Cell.feature( args, "color" )  or  "#000000"
    em:css( "background-color", s )
    r = string.format( "%s%s%s",
                       r,
                       tostring( em ),
                       tostring( ep ) )
    return r
end -- finest()



local flat = function ( assign, after, adjust )
    -- Parse decimal number
    -- Precondition:
    --     assign    -- string, number to be parsed
    --     after     -- boolean, decimal separator expected
    --     adjust    -- number|boolean|nil, cheat sort figure
    -- Postcondition:
    --     Returns table, with analysis
    --             .long      -- >= 1000  etc.
    --             .low       -- < 0
    --             .sign      -- minus, plus
    --             .show      -- leading digits, scripting
    --             .sub       -- decimal fragment digits, scripting
    --             .sort      -- signed ASCII sort text
    --             .script    -- script code
    --             .scream    -- error text
    local r = { }
    local s = assign
    local k, init
    local face = function ()
                     local j
                     if init == 45 then
                         j = 8722
                     else
                         j = init
                     end
                     return mw.ustring.char( j )
                 end -- face()
    factory( "sep" )
    if s:find( "&", 1, true ) then
        s = mw.text.decode( s )
                   :gsub( "&thinsp;", mw.ustring.char( 0x2009 ) )
    end
    s = mw.ustring.gsub( s, "%s", "" )
    k = mw.ustring.codepoint( s, 1, 1 )
    if k then
        init = Sort.heading[ k ]
        if init then
            if mw.ustring.len( s ) > 1 then
                s = mw.ustring.sub( s, 2 )
                k = mw.ustring.codepoint( s, 1, 1 )
                r.low = ( init == 45 )
            else
                k = 0x30
                s = "0"
            end
        end
    else
        k = 0x30
        s = "0"
    end
    if after then
        local m = mw.ustring.find( s, Sort.sepGroup, 1, true )
        local dig, j0, j9, n
        if m  and
           Sort.separated  and
           mw.ustring.match( s, Sort.separated ) then
            s = mw.ustring.gsub( s, Sort.seekGroup, "" )
        end
        for i = 1, #Sort.digits do
            dig = Sort.digits[ i ]
            j0  = dig[ 1 ]
            j9  = j0 + 9
            if k >= j0  and  k <= j9 then
                r.script = dig[ 2 ]
                n        = mw.ustring.len( s )
                break    -- for i
            elseif k < j0 then
                break    -- for i
            end
        end -- for i
        if n then
            m = 0
            for i = 1, n do
                if k == j0 then
                    m = i
                    if i < n then
                        k = mw.ustring.codepoint( s,  i + 1,  i + 1 )
                    end
                else
                    break    -- for i
                end
            end -- for i
            if m > 0 then
                s = mw.ustring.sub( s,  m + 1 )
                n = n - m
            end
            m = 0
            for i = 1, n do
                if k == Sort.keyDec  or  k == 46 then
                    k = false
                    break    -- for i
                elseif k >= j0  and  k <= j9 then
                    r.sort = string.format( "%s%c",
                                            r.sort or "",
                                            k - j0 + 48 )
                    if i < n then
                        k = mw.ustring.codepoint( s,  i + 1,  i + 1 )
                    end
                    m = i
                end
            end -- for i
            if m > 0 then
                r.long = ( m > Sort.nGroup )
                r.sort = r.sort or "0"
                r.show = mw.ustring.sub( s, 1, m )
                if not k then
                    m = m + 1
                    s = mw.ustring.sub( s,  m + 1 )
                end
                n = n - m
            else
                r.show = mw.ustring.char( j0 )
                r.sort = "0"
                if not k then
                    s = mw.ustring.sub( s, 2 )
                    n = n - 1
                end
            end
            if r.low  and  r.sort ~= "0" then
                r.sort = "-" .. r.sort
            end
            if n > 0 then
                if k then
                    r.scream = s
                else
                    r.sort = r.sort .. Sort.sepDec
                    k      = mw.ustring.codepoint( s, 1, 1 )
                    for i = 1, n do
                        if k  and  k >= j0  and  k <= j9 then
                            r.sort = string.format( "%s%c",
                                                    r.sort,
                                                    k - j0 + 48 )
                            if i < n then
                                k = mw.ustring.codepoint( s,
                                                          i + 1,
                                                          i + 1 )
                            end
                        else
                            k = false
                        end
                    end -- for i
                    if k then
                        r.sub = s
                    else
                        r.scream = s
                        r.show   = false
                    end
                end
            end
            if adjust then
                if adjust == true then
                    -- plus/minus
                    r.sort = "0"
                elseif r.sort then
                    k = tonumber( r.sort )
                    if k then
                        local m = k * 0.0000000001
                        if m < 0 then
                             m = -m
                        end
                        k = k + m * adjust
                        r.sort = tostring( k )
                    end
                end
            end
        else
            r.scream = assign
        end
        if init  and  not r.scream then
            r.sign = face()
        end
    else
        k = tonumber( s )
        if k  and  k >= 0 then
            k = math.floor( k )
            r.show = tostring( k )
            r.sort = r.show
            if init then
                r.sign = face()
                if init == 45 then
                    r.sort = "-" .. r.sort
                end
            end
        else
            r.scream = assign
        end
    end
    return r
end -- flat()



local fore = function ( args )
    -- Create and merge sort attribute
    -- Precondition:
    --     args      -- table, parameters
    --                       .n      -- table, for base number
    --                                  .sort    -- string|nil
    --                       .exp    -- table|nil, for exponent
    --     amount    -- number, for base
    -- Postcondition:
    --     attributes extended
    local s = args.n.sort or "0"
    if args.exp  and  args.exp.sort then
        s = string.format( "%sE%s", s, args.exp.sort )
    end
    Sort.Cell.faced( args, s )
end -- fore()



local format = function ( args )
    -- Format visible number
    -- Precondition:
    --     args    -- table, parameters
    --                .pad       -- number|false, for padding
    --                .pre       -- string|false, for prefix
    --                .n         -- table, for base
    --                              .low       -- boolean, < 0
    --                              .sign      -- minus, plus
    --                              .show      -- leading digits
    --                              .sub       -- decimal fragment digits
    --                              .script    -- script code
    --                .suffix    -- string|false, extending base
    --                .exp       -- table|false, for exponent
    --                .post      -- string|false, for postfix
    --                .round     -- number|false, for rounding
    --                .cell      -- boolean, enfoce sort value
    -- Postcondition:
    --     Returns string
    local r = args.n.show or args.n.scream
    local e, move, s, shift
    if args.pad  and  args.pad < 0 then
        move = args.pad + mw.ustring.len( args.n.show )
        if args.n.sign then
            move = move + 1
        end
        if move < 0 then
            move = move + 1
        end
        if args.pre and move then
            move = move + mw.ustring.len( args.pre ) + 1
        end
    end
    if move then
        if move < 0 then
            if not Sort.shift then
                if Sort.Cell.following() then
                    Sort.shift = "left"
                else
                    Sort.shift = "right"
                end
                Sort.shift = "padding-" .. Sort.shift
            end
            Sort.Cell.feature( args,
                               Sort.shift,
                               string.format( "%.2fem",
                                              Sort.mpz * move ) )
        end
    elseif args.pad then
        move = args.pad
        if args.n.sub then
            move = move - mw.ustring.len( args.n.sub ) - 1
            if args.suffix then
                move = move - mw.ustring.len( args.suffix )
            end
        else
            move = move + 0.5
        end
        if args.post then
           move = move - mw.ustring.len( args.post ) - 1
        end
        if move > 0 then
            shift = string.rep( "0", move )
            e     = mw.html.create( "span" )
                           :css( "visibility", "hidden" )
                           :wikitext( shift )
            shift = tostring( e )
        end
    end
    if args.n.low or args.n.long or args.n.sub then
        r = fine( args.n )
    end
    if args.suffix then
        r = r .. args.suffix
    end
    if args.exp then
        if Sort.cssNumExp then
            r = finest( args, r )
        else
            Sort.stick = Sort.stick  or  mw.ustring.char( 0xB7 )
            r, s = fined( r, args.exp )
            e = mw.html.create( "sup" )
                       :wikitext( s )
            r = string.format( "%s%s10%s",
                               r,
                               Sort.stick,
                               tostring( e ) )
        end
    end
    if args.pre  or
       args.n.long  or
       args.n.sub   or
       args.exp and args.exp.low  or
       args.post  or
       args.n.script then
        if args.pre then
            r =  string.format( "%s %s", args.pre, r )
        end
        if args.post then
            r =  string.format( "%s %s", r, args.post )
        end
        e = mw.html.create( "span" )
                   :css( "white-space", "nowrap" )
                   :wikitext( r )
        if ( args.pre or args.post )  and
           not Sort.Cell.following() then
            e:attr( "dir", "ltr" )
        end
        if args.n.script then
            e:attr( "lang", "und-" .. args.n.script )
        end
        r = tostring( e )
    end
    if shift then
        r =  r .. shift
    end
    if args.pad   or
       args.pre   or
       ( args.cell  and
         ( args.n.low   or
           args.n.sign  or
           args.n.sub   or
           args.n.long  or
           args.suffix  or
           args.exp     or
           args.n.script ) ) then
        fore( args )
    end
    return r
end -- format()



local front = function ( analyse )
    -- Interprete heading text in front of number
    -- Parameter:
    --     analyse    -- string, with probably prefixed number
    -- Postcondition:
    --     Returns
    --         1. string|nil  -- prefix
    --         2. string|nil  -- remaining text, hopefully number
    --         3. number|nil  -- weight, cheat sort figure
    local i, r1, r2, r3
    local s = analyse
    if s:find( "&", 1, true ) then
        s = mw.text.decode( s )
        if s:find( "&", 1, true ) then
            local html = { lt     = 0x003C,
                           gt     = 0x003E,
                           plusmn = 0x00B1,
                           asymp  = 0x2248,
                           le     = 0x2264,
                           ge     = 0x2265 }
            for k, v in pairs( html ) do
                s = s:gsub( string.format( "&%s;", k ),
                            mw.ustring.char( v ) )
            end -- for k, v
        end
    end
    i = mw.ustring.codepoint( s, 1, 1 )
    for k, v in pairs( Sort.prefix ) do
        if k == i then
            r1 = mw.ustring.char( k )
            r2 = mw.text.trim( mw.ustring.sub( s, 2 ) )
            r3 = v
            break    -- for k, v
        end
    end -- for k, v
    if not r1 then
        local Value = fetch( "Text", false, "value" )
        if Value  and  type( Value.prefix ) == "table" then
            for k, v in pairs( Value.prefix ) do
                i = mw.ustring.len( k )
                if k == mw.ustring.sub( s, 1, i ) then
                    r1 = k
                    r2 = mw.text.trim( mw.ustring.sub( s,  i + 1 ) )
                    r3 = v
                    break    -- for k, v
                end
            end -- for k, v
        end
    end
    return r1, r2, r3
end -- front()



local furnish = function ( args )
    -- Execute task
    -- Parameter:
    --     args    -- table, parameters
    -- Postcondition:
    --     Returns string, or expands .cell
    --     Throws error on failure
    local r
    fetch( false, true, "Cell" )
    if type( args ) == "table" then
        local present = Sort.Cell.first( args )
        local s = type( args.exp )
        if s == "string" then
           Sort.Cell.fair( args, "exp", present )
        elseif s == "number" then
            present.exp = args.exp
        end
        Sort.Cell.fair( args, "pre", present )
        s = type( args.n )
        if s == "string" then
            s = mw.text.trim( args.n )
            if s == Sort.supreme then
                present.infinit = 1
            elseif mw.ustring.len( s ) == 2  and
                   mw.ustring.codepoint( s, 2, 2 ) == 8734 then
                local m = mw.ustring.codepoint( s, 1, 1 )
                if m == 45  or  m == 8722 then
                    present.infinit = -1
                elseif m == 43 then
                    present.infinit = 1
                end
            end
            if not present.infinit then
                if not present.exp then
                    local i = s:find( "%d[.,]?[eE]%-?%d+$" )
                    if i then
                        local split = s:sub( i + 2 ):upper()
                        if split:sub( 1, 1 ) == "E" then
                            split = split:sub( 2 )
                        end
                        present.exp = split
                        s           = s:sub( 1, i )
                    end
                end
                present.n = flat( s, true )
                if present.n.scream  and  not present.pre then
                    local move
                    present.pre, s, move = front( s )
                    if present.pre then
                        present.n = flat( s, true, move )
                    end
                end
            end
        elseif s == "number" then
            local k = args.n
            present.n = { low  = ( k < 0 ),
                          sort = tostring( k ) }
            present.n.show = present.n.sort
            if present.n.low then
                present.n.show = present.n.show:sub( 2 )
            end
            k = present.n.show:find( ".", 1, true )
            if k then
                present.n.sub  = present.n.show:sub( k + 1 )
                k = k - 1
                present.n.show = present.n.show:sub( 1, k )
            else
                k = mw.ustring.len( present.n.show )
            end
            present.n.long = ( k > Sort.nGroup )
        end
        if present.n or present.infinit then
            if present.n then
                local lazy
                s = type( present.exp )
                if s == "string" then
                    present.exp = flat( present.exp )
                elseif s == "number" then
                    local k = present.exp
                    present.exp = { low = ( k < 0 ) }
                    if present.exp.low then
                        k = -1 * k
                    end
                    k = math.floor( k )
                    present.exp.show = tostring( k )
                    if k > 0 then
                        present.exp.sort = present.exp.show
                        if present.exp.low then
                            present.exp.sort = "-" .. present.exp.sort
                        end
                    end
                end
                s = type( args.lazy )
                if s == "string" then
                    lazy = ( args.lazy == "1" )
                elseif s == "" then
                    lazy = args.lazy
                end
                if lazy then
                    fore( present )
                else
                    Sort.Cell.fair( args, "suffix", present )
                    s = type( args.pad )
                    if s == "string" then
                        present.pad = tonumber( args.pad )
                    elseif s == "number" then
                        present.pad = args.pad
                    end
                    if present.pad  and  present.pad == 0 then
                        present.pad = false
                    end
                    Sort.Cell.fair( args, "post", present )
                    if not present.n.low then
                        s = type( args.plus )
                        if s == "string" then
                            if args.plus == "1"  or
                               args.plus == "+" then
                                present.n.sign = "+"
                            end
                        elseif s == "boolean" then
                            present.n.sign = "+"
                        end
                    end
                    if args.cssNum  and  not Sort.cssNum then
                        Sort.cssNum = { }
                        Sort.cssNum.origin = args.cssNum
                    end
                    if args.cssNumExp  and  not Sort.cssNumExp then
                        Sort.cssNumExp = { }
                        Sort.cssNumExp.origin = args.cssNumExp
                    end
                    r = format( present )
                end
            else
                present.n = { sort = "9999999" }
                if present.infinit < 0 then
                    present.n.sort = "-" .. present.n.sort
                end
                present.exp = { sort = "301" }
                fore( present )
            end
            r = Sort.Cell.finalize( present, r )
        else
            Sort.Cell.fault( "???", args )
        end
    else
        error( "'args' is not a table" )
    end
    return r
end -- furnish()



Sort.f = function ( args )
    -- Create table cell start
    -- Parameter:
    --     args    -- table, parameters
    --                .pad        -- number|string, for padding
    --                .pre        -- string, for prefix
    --                .plus       -- boolean|string, for plus sign
    --                .n          -- number|string, for base
    --                .suffix     -- string, extending base
    --                .exp        -- number|string, for exponent
    --                .post       -- string, for postfix
    --                .round      -- number|string, for rounding
    --                .lazy       -- string|boolean, mute
    --                .cell       -- string|boolean|table, enforce sort
    --                .rowspan    -- number|string, for cell attribute
    --                .colspan    -- number|string, for cell attribute
    --                .class      -- string, for cell attribute
    --                .style      -- string|table, for cell attribute
    --                .id         -- string, for cell attribute
    --                .cssNum     -- string|title, for templatestyles
    --                .cssNumExp  -- string|title, for templatestyles
    --                .cat        -- string|nil, for error category
    --                .frame      -- table, if present
    -- Postcondition:
    --     Returns string, or expands .cell, or nil
    local lucky, r = pcall( furnish, args )
    if not lucky then
        local e = mw.html.create( "span" )
                         :addClass( "error" )
                         :wikitext( "Module:Sort/cell * " .. r )
        if type( args.cell ) == "table"  and
           type( args.cell.wikitext ) == "function" then
            args.cell:node( e )
        else
            r = tostring( e )
        end
    end
    return r
end -- Sort.f()



Sort.furnish = function ()
    -- Retrieve list of project prefixes
    -- Postcondition:
    --     Returns  string  -- with wikitext list
    local r, Value
    for k, v in pairs( Sort.prefix ) do
        if r then
            r = r .. "\n"
        else
            r = ""
        end
        r = string.format( "%s* %s",  r,  mw.ustring.char( k ) )
    end -- for k, v
    Value = fetch( "Text", false, "value" )
    if Value  and  type( Value.prefix ) == "table" then
        for k, v in pairs( Value.prefix ) do
            r = string.format( "%s\n* %s",  r,  k )
        end -- for k, v
    end
    return r
end -- Sort.furnish()



Failsafe.failsafe = function ( atleast )
    -- Retrieve versioning and check for compliance
    -- Precondition:
    --     atleast  -- string, with required version or "wikidata" or "~"
    --                 or false
    -- Postcondition:
    --     Returns  string  -- with queried version, also if problem
    --              false   -- if appropriate
    -- 2019-10-15
    local last  = ( atleast == "~" )
    local since = atleast
    local r
    if last  or  since == "wikidata" then
        local item = Failsafe.item
        since = false
        if type( item ) == "number"  and  item > 0 then
            local entity = mw.wikibase.getEntity( string.format( "Q%d",
                                                                 item ) )
            if type( entity ) == "table" then
                local seek = Failsafe.serialProperty or "P348"
                local vsn  = entity:formatPropertyValues( seek )
                if type( vsn ) == "table"  and
                   type( vsn.value ) == "string"  and
                   vsn.value ~= "" then
                    if last  and  vsn.value == Failsafe.serial then
                        r = false
                    else
                        r = vsn.value
                    end
                end
            end
        end
    end
    if type( r ) == "nil" then
        if not since  or  since <= Failsafe.serial then
            r = Failsafe.serial
        else
            r = false
        end
    end
    return r
end -- Failsafe.failsafe()



-- Export
local p = { }

p.f = function ( frame )
    -- Template call
    Sort.frame = frame
    return Sort.f( frame.args )  or  ""
end -- p.f

p.furnish = function ()
    return Sort.furnish()
end -- p.f

p.failsafe = function ( frame )
    -- Versioning interface
    local s = type( frame )
    local since
    if s == "table" then
        since = frame.args[ 1 ]
    elseif s == "string" then
        since = frame
    end
    if since then
        since = mw.text.trim( since )
        if since == "" then
            since = false
        end
    end
    return Failsafe.failsafe( since )  or  ""
end -- p.failsafe

p.Sort = function ()
    -- Module interface
    return Sort
end

return p