Zum Inhalt springen

Modul:Sort/cellNum

aus Wikipedia, der freien Enzyklopädie
Dies ist eine alte Version dieser Seite, zuletzt bearbeitet am 23. März 2020 um 13:58 Uhr durch PerfektesChaos (Diskussion | Beiträge) (2020-03-22). Sie kann sich erheblich von der aktuellen Version unterscheiden.
Vorlagenprogrammierung Diskussionen Lua Test Unterseiten
Modul Deutsch English

Modul: Dokumentation

Diese Seite enthält Code in der Programmiersprache Lua. Einbindungszahl Cirrus

Dies ist die (produktive) Mutterversion eines global benutzten Lua-Moduls.
Wenn die serial-Information nicht übereinstimmt, müsste eine Kopie hiervon in das lokale Wiki geschrieben werden.
Versionsbezeichnung auf WikiData: 2024-06-01

Updating notwendig

(lokal: 2020-03-22)

local Sort = { suite   = "Sort",
               sub     = "cellNum",
               serial  = "2020-03-22",
               item    = 88370026,
               globals = { FormatNum = 15709679 } }
--[=[
Sort/cellNum -- support table cells with numerical content and number formatting
]=]
local Failsafe   = Sort
local GlobalMod  = Sort
local Cell



Sort.mpz = -0.5



if mw.site.server:find( ".beta.wmflabs.org", 4, true ) then
    require( "Module:No Globals" )
end



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
    -- 2019-10-29
    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, 0 )
        end
    end
    return r
end -- foreignModule()



local face = function ( attribute, assign )
    -- Store preceding attribute
    -- Precondition:
    --     attribute    -- string, attribute name
    --     assign       -- string|nil, for value
    -- Postcondition:
    --     attributes extended
    if type( assign ) == "string" then
        local s = mw.text.trim( assign )
        if s ~= "" then
            Cell = Cell  or  { }
            Cell[ attribute ] = s
        end
    end
end -- face()



local facing = function ( append )
    -- Prepend preceding attributes
    -- Precondition:
    --     append    -- string|html, thing to be extended
    -- Postcondition:
    --     Returns string, if append is a string
    --     otherwise html is extended
    local r = append
    if Cell then
        if type( append ) == "string" then
            if Cell.css then
                local s = ""
                for k, v in pairs( Cell.css ) do
                    if type( k ) == "string"  and
                       type( v ) == "string" then
                        v = mw.text.trim( v )
                        k = mw.text.trim( k )
                        if v ~= ""  and
                           k ~= "" then
                            s = string.format( "%s;%s:%s", s, k, v )
                        end
                    end
                end -- for k, v
                if s ~= "" then
                    face( "style", s:sub( 2 ) )
                end
                Cell.css = nil
            end
            r = "| " .. r
            for k, v in pairs( Cell ) do
                r = string.format( " %s=\"%s\"%s", k, v, r )
            end -- for k, v
            r = r:sub( 2 )
        else
            for k, v in pairs( Cell ) do
                if k == "class" then
                    append:addClass( v )
                elseif k == "css" then
                    append:css( v )
                else
                    append:attr( k, v )
                end
            end -- for k, v
        end
    end
    return r
end -- facing()



local factory = function ( ask )
    -- Ensure config data
    -- Precondition:
    --     assign    -- string|nil, particular query
    -- Postcondition:
    --     config data available
    if not ask then
        Sort.minus = Sort.minus  or  mw.ustring.char( 8722 )
    elseif ask == "sep" then
        if not Sort.sepDec then
            Sort.contLang = Sort.contLang  or
                            mw.language.getContentLanguage()
            Sort.sepGroup, Sort.sepDec =
                    mw.ustring.match( Sort.contLang:formatNum( 1234.5 ),
                                      "1(%p?)234(%p)5" )
            Sort.sepDec = Sort.sepDec or "."
            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 == "*" then
        if not Sort.spanMult then
            local e = mw.html.create( "span" )
                             :addClass( "numericFormat-multiply" )
                             :wikitext( "*" )
            Sort.spanMult = tostring( e )
        end
    elseif ask == "1000" then
        if not Sort.span1000 then
            local e = mw.html.create( "span" )
                             :addClass( "numericFormat-1000" )
                             :wikitext( "@@@" )
            Sort.span1000 = tostring( e )
        end
    elseif ask == "10sup" then
        if not Sort.span10sup then
            local e = mw.html.create( "span" )
                             :addClass( "numericFormat-10sup" )
                             :wikitext( "@@@" )
            Sort.span10sup = tostring( e )
        end
    end
end -- factory()



local features = function ( assign )
    -- Parse CSS string
    -- Precondition:
    --     assign    -- string, CSS to be parsed
    -- Postcondition:
    --     Cell.css
    local css = mw.text.split( assign, ";" )
    --          Problem: URL; not expected
    local pair, s
    for i = 1, #css do
        pair = mw.text.split( css[ i ], ":" )
        --     Problem: URL; not expected
        if #pair == 2 then
            s = mw.text.trim( pair[ 1 ] )
            if s ~= "" then
                Cell     = Cell  or  { }
                Cell.css = Cell.css  or  { }
                Cell.css[ s ] = pair[ 2 ]
            end
        end
    end -- i = 1, #css
end -- features()



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 ( all, ahead, assist, adjacent )
    -- Equip number with styled special characters
    -- Precondition:
    --     all         -- string, formatted entire presentation
    --     ahead       -- boolean, heading minus
    --     assist      -- boolean, grouping possible
    --     adjacent    -- boolean, decimal
    -- Postcondition:
    --     Returns updated entire presentation
    local r = all
    if ahead  and  mw.ustring.codepoint( r, 1, 1 ) == 8722 then
        factory( "minus" )
        r = Sort.spanMinus .. mw.ustring.sub( r, 2 )
    end
    if adjacent then
        factory( "sep" )
        factory( "dec" )
        if Sort.spanDec then
            r = mw.ustring.gsub( r, Sort.seekDec, Sort.spanDec )
        end
    end
    if assist then
        factory( "sep" )
        if Sort.seekGroup then
            local s
            Sort.seek1000 = Sort.seek1000  or
                            "(%d?%d?%d)" .. Sort.seekGroup
            factory( "1000" )
            repeat
                s = mw.ustring.match( r, Sort.seek1000 )
                if s then
                    r = mw.ustring.gsub( r,
                                         s .. Sort.seekGroup,
                                         Sort.span1000:gsub( "@@@", s ) )
                end
            until not s
        end
    end
    return feeder( "cssNum" ) .. r
end -- fine()



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



local finest = function ( all, assign )
    -- Append styled decimal power
    -- Precondition:
    --     all       -- string, formatted presentation
    --     assign    -- number, exponent
    -- Postcondition:
    --     Returns expanded presentation
    local r = string.format( "%s%s%s",
                             feeder( "cssNum" ),
                             feeder( "cssNumExp" ),
                             all )
    local s
    factory( "*" )
    factory( "10sup" )
    r, s = fined( r, assign )
    r = string.format( "%s%s%s",
                       r,
                       Sort.spanMult:gsub( ">%*<", ">E<" ),
                       Sort.span10sup:gsub( "@@@", s ) )
    return r
end -- finest()



local flat = function ( assign, after )
    -- Parse decimal number
    -- Precondition:
    --     assign    -- string, number to be parsed
    --     after     -- boolean, decimal separator expected
    -- Postcondition:
    --     Returns number, or false
    local r = mw.text.trim( assign )
    if r:find( "%d" ) then
        if mw.ustring.codepoint( r, 1, 1 ) == 8722 then
            r = "-" .. mw.ustring.sub( r, 2 )
        end
        if after then
            local i, k
            factory( "sep" )
            i = mw.ustring.find( r, Sort.sepGroup, 1, true )
            if i  and  Sort.separated  and
               mw.ustring.match( r, Sort.separated ) then
                r = mw.ustring.gsub( r, Sort.seekGroup, "" )
                i = false
            end
            k = mw.ustring.find( r, Sort.sepDec, 1, true )
            if i  and  k  and  i < k then
                r = mw.ustring.gsub( r, Sort.seekGroup, "" )
                i = false
            end
            if k  and  Sort.sepDec ~= "." then
                r = mw.ustring.gsub( r, Sort.seekDec, "." )
            end
        end
        r = tonumber( r )
    else
        r = false
    end
    return r
end -- flat()



local following = function ()
    -- Retrieve text order
    -- Postcondition:
    --     Returns true, if left-to-right
    if type( Sort.ltr ) ~= "boolean" then
        Sort.contLang = Sort.contLang  or
                        mw.language.getContentLanguage()
        Sort.ltr = not Sort.contLang:isRTL()
    end
    return Sort.ltr
end -- following()



local fore = function ( amount, above )
    -- Create and merge sort attribute
    -- Precondition:
    --     amount    -- number, for base
    --     above     -- number|nil, for exponent
    -- Postcondition:
    --     attributes extended
    local s = tostring( amount )
    local i = s:find( ".", 1, true )
    if i then
        factory( "sep" )
        if Sort.sepDec ~= "." then
            s = string.format( "%s%s%s",
                               s:sub( 1,  i - 1 ),
                               Sort.sepDec,
                               s:sub( i + 1 ) )
        end
    end
    if above  and  above ~= 0 then
        s = string.format( "%sE%d", s, above )
    end
    face( "data-sort-value", s )
end -- fore()



local format = function ( args )
    -- Format visible number
    -- Precondition:
    --     args    -- table, parameters
    --                .pad        -- number, for padding
    --                .pre        -- string, for prefix
    --                .lead       -- boolean, for plus sign
    --                .n          -- number, for base
    --                .exp        -- number, for exponent
    --                .post       -- string, for postfix
    --                .round      -- number, for rounding
    --                .cell       -- boolean, enfoce sort value
    -- Postcondition:
    --     Returns string
    local low = ( args.n < 0 )
    local e, i, less, long, move, n, r, s, shift
    if low then
        n = -1 * args.n
    else
        n = args.n
    end
    less = ( n > math.floor( n ) )
    long = ( n >= 1000 )
    if low or less or long then
        factory()
        r = Sort.contLang:formatNum( n )
        if low then
            r = Sort.minus .. r
        elseif args.lead then
            r = "+" .. r
        end
    else
        r = tostring( n )
    end
    if args.pad  and  args.pad < 0 then
        move = args.pad
        if less then
            factory( "sep" )
            i = mw.ustring.find( r, Sort.sepDec, 1, true )
            if not i then
                i = #r
            end
            move = move + i
        end
        if move < 0 then
            move = move + 1
        end
    end
    if args.pre and move then
        move = move + mw.ustring.len( args.pre ) + 1
    end
    if move then
        if move < 0 then
            move     = Sort.mpz * move
            Cell     = Cell  or  { }
            Cell.css = Cell.css  or  { }
            if not Sort.shift then
                if following() then
                    Sort.shift = "left"
                else
                    Sort.shift = "right"
                end
                Sort.shift = "padding-" .. Sort.shift
            end
            Cell.css[ Sort.shift ] = string.format( "%.2fem", move )
        end
    elseif args.pad then
        factory( "sep" )
        i = mw.ustring.find( r, Sort.sepDec, 1, true )
        if i then
            i = #r - i
            shift = ""
        else
            i = 0
            shift = Sort.sepDec
        end
        i = args.pad - i
        if args.post then
           i = i - mw.ustring.len( args.post ) - 1
        end
        if i > 0 then
            shift = shift .. string.rep( "0", i )
            e     = mw.html.create( "span" )
                           :css( "visibility", "hidden" )
                           :wikitext( shift )
            shift = tostring( e )
        end
    end
    if Sort.cssNum then
        if low or long or less then
            r = fine( r, low, long, less )
        end
    end
    if args.exp then
        if Sort.cssNumExp then
            r = finest( r, args.exp )
        else
            Sort.stick = Sort.stick  or  mw.ustring.char( 183 )
            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.post 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 not following() then
            e:attr( "dir", "ltr" )
        end
        r = tostring( e )
    end
    if shift then
        r =  r .. shift
    end
    if args.pad   or
       args.pre   or
       ( args.cell  and
         ( args.lead   or
           low   or
           less   or
           long   or
           args.exp ) ) then
        fore( args.n, args.exp )
    end
    return r
end -- format()



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
    --                .exp        -- number|string, for exponent
    --                .post       -- string, for postfix
    --                .round      -- number|string, for rounding
    --                .cell       -- string|boolean|table, enforce sort
    --                .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
    --                .frame      -- table, if present
    -- Postcondition:
    --     Returns string, or expands .cell
    local r
    Cell = false
    if type( args ) == "table" then
        local present = { }
        local s = type( args.n )
        if type( args.frame ) == "table" then
            Sort.frame = args.frame
        end
        if s == "string" then
            local i = args.n:find( "%d[.,]?[eE]%-?%d+%s*$" )
            if i then
                if not args.exp  or  args.exp == "" then
                    s = args.n:sub( i + 2 ):upper()
                    if s:sub( 1, 1 ) == "E" then
                        s = s:sub( 2 )
                    end
                    present.exp = tonumber( s )
                    s           = args.n:sub( 1, i )
                end
            else
                s = args.n
            end
            present.n = flat( s, true )
        elseif s == "number" then
            present.n = args.n
        end
        if present.n then
            s = type( args.exp )
            if s == "string"  and  args.exp ~= "" then
                present.exp = flat( args.exp )
            elseif s == "number" then
                present.exp = args.exp
            end
            s = type( args.style )
            if s == "string" then
                features( args.style )
            elseif s == "table" then
                Cell     = { }
                Cell.css = args.style
            end
            face( "class", args.class )
            face( "id", args.id )
            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
            if type( args.pre ) == "string" then
                s = mw.text.trim( args.pre )
                if s ~= "" then
                    present.pre = s
                end
            end
            if type( args.post ) == "string" then
                s = mw.text.trim( args.post )
                if s ~= "" then
                    present.post = s
                end
            end
            s = type( args.cell )
            if s == "string" then
                present.cell = ( args.cell == "1" )
            elseif s == "boolean"  or  s == "table" then
                present.cell = args.cell
            end
            s = type( args.plus )
            if s == "string" then
                present.lead = ( args.plus == "1"  or  args.plus == "+" )
            elseif s == "boolean" then
                present.lead = args.plus
            end
            if present.lead  and  present.n < 0 then
                present.lead = false
            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 )
            if Cell then
                s = type( present.cell )
                if s == "boolean" then
                    r = facing( r )
                elseif s == "table" then
                    facing( present.cell )
                    present.cell:wikitext( r )
                else
                    local e = mw.html.create( "span" )
                                     :wikitext( r )
                    facing( e )
                    r = tostring( e )
                end
            elseif type( present.cell ) == "table" then
                present.cell:wikitext( r )
            end
        else
            local e = mw.html.create( "span" )
                             :addClass( "error" )
                             :wikitext( "???" )
            r = tostring( e )
            if type( present.cell ) == "table" then
                present.cell:wikitext( r )
            end
        end
    end
    return r
end -- Sort.f()



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.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