Modul:Sort/cell

aus Wikipedia, der freien Enzyklopädie
local Sort = { suite   = "Sort",
               sub     = "cell",
               serial  = "2021-04-11",
               item    = 90144855 }
--[=[
Sort/cell    --  support table cells sorting modules
]=]
local Failsafe = Sort



local face = function ( assign, attribute, args, apply, allow )
    -- Store preceding attribute
    -- Precondition:
    --     assign       -- table, to extend
    --     attribute    -- string, attribute name
    --     args         -- table|nil, parameters to retrieve
    --     apply        -- string|number|nil, for value
    --     allow        -- boolean, permit and request numbers >(=)0
    -- Postcondition:
    --     attributes extended
    local d, s, v
    if apply then
        d = apply
    elseif args then
        d = args[ attribute ]
    end
    s = type( d )
    if s == "string" then
        d = mw.text.trim( d )
        if d == "" then
            d = false
        end
    end
    if d then
        if allow then
            local min = 1
            if attribute == "rowspan" then
                min = 0
            end
            if s == "string" then
                s = string.format( "[%d-9]%%d*", min )
                if d:match( string.format( "^%s$", s ) ) then
                    v = d
                else
                    local p
                    s = string.format( "^(['\"]) *(%s) *%%1$", s )
                    p, s = d:match( s )
                    if s then
                        v = s
                    end
                end
            elseif s == "number"  and  d >= min then
                local k = math.floor( d )
                if d == k then
                    v = tostring( k )
                end
            end
            if not v then
                assign.scream = attribute .. "=????"
            end
        elseif s == "string" then
            local p
            p, s = d:match( "^(['\"])([^\"]*)%1$" )
            if s then
                d = mw.text.trim( s )
            end
            if d ~= "" then
                if attribute == "dir" then
                    d = d:lower()
                    if d == "ltr"  or  d == "rtl" then
                        v = d
                    end
                else
                    v = d
                end
            end
        end
    end
    if v then
        assign.props = assign.props  or  { }
        assign.props[ attribute ] = v
    end
end -- face()



local facing = function ( args, append )
    -- Prepend preceding attributes
    -- Precondition:
    --     args      -- table, parameters
    --     append    -- string|html|nil, thing to be extended
    -- Postcondition:
    --     Returns string, if append is a string or nil
    --     otherwise html is extended
    local p = args.props
    local r = append
    if p then
        if type( append ) == "table" then
            for k, v in pairs( p ) 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
        else
            if p.css then
                local s = ""
                for k, v in pairs( p.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( args, "style", false, s:sub( 2 ) )
                end
                p.css = nil
            end
            if type( append ) == "string" then
                r = "| " .. append
            else
                r = "|"
            end
            for k, v in pairs( p ) do
                if k ~= "lang"  or  v ~= Sort.facility() then
                    r = string.format( " %s=\"%s\"%s", k, v, r )
                end
            end -- for k, v
            r = r:sub( 2 )
        end
    end
    return r
end -- facing()



local features = function ( args, assign )
    -- Parse CSS string
    -- Precondition:
    --     args      -- table, parameters
    --                  .style    -- string|nil, CSS to be parsed
    --     assign    -- table, to be extended
    -- Postcondition:
    --     args.props.css added
    if args.style then
        local s         = mw.text.trim( args.style )
        local pair, css = s:match( "^(['\"])([^\"]*)%1$" )
        if css then
            s = mw.text.trim( css )
        end
        css = mw.text.split( s, ";" )
        --    Problem: URL; not expected
        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
                    assign.props          = assign.props  or  { }
                    assign.props.css      = assign.props.css  or  { }
                    assign.props.css[ s ] = mw.text.trim( pair[ 2 ] )
                end
            end
        end -- i = 1, #css
    end
end -- features()



Sort.faced = function ( args, assign )
    -- Assign a sortable value
    -- Precondition:
    --     args      -- table, to be extended
    --     assign    -- string, to be memorized
    -- Postcondition:
    --     args is extended, if meaningful
    if type( assign ) == "string" then
        local s = mw.text.trim( assign )
        if s ~= "" then
            s = mw.text.decode( s )
            s = mw.ustring.gsub( s, mw.ustring.char( 160 ), " " )
            s = mw.ustring.gsub( s, "%s+", " " )
            s = s:gsub( "\"", "" )
                 :gsub( "<", "" )
                 :gsub( ">", "" )
            s = mw.ustring.sub( s, 1, 99 )
            face( args, "data-sort-value", false, s )
        end
    end
end -- Sort.faced()



Sort.facility = function ()
    -- Retrieve page or project language
    -- Postcondition:
    --     Returns string, downcased
    if type( Sort.slang ) ~= "string" then
        Sort.contLang = Sort.contLang  or
                        mw.language.getContentLanguage()
        Sort.slang    = Sort.contLang:getCode():lower()
    end
    return Sort.slang
end -- Sort.facility()



Sort.fair = function ( args, access, assign )
    -- Assign a non-empty string
    -- Precondition:
    --     args      -- table, to be queried
    --     access    -- string, to identify a component
    --     assign    -- table, to be extended
    -- Postcondition:
    --     assign is extended, if meaningful
    if type( args[ access ] ) == "string" then
        local s = mw.text.trim( args[ access ] )
        if s ~= "" then
            assign[ access ] = s
        end
    end
end -- Sort.fair()



Sort.fault = function ( alert, args )
    -- Error occurred
    -- Parameter:
    --     alert    -- string, with message
    --     args     -- table, parameters
    --                 .elem    -- table, if mw.html
    --                 .cat     -- string|table|nil, for error category
    --                             may contain one or more mw.title
    -- Postcondition:
    --     Returns string, or expands .elem
    local r, suffix
    local e = mw.html.create( "span" )
                     :addClass( "error" )
                     :wikitext( alert )
    if args.cat then
        local s = type( args.cat )
        local c
        if s == "string" then
            s = mw.text.trim( args.cat )
            if s ~= "" then
                c = { }
                table.insert( c, s )
            end
        elseif s == "table" then
            if type( args.cat.baseText ) == "string" then
                c = { }
                table.insert( c, args.cat.text )
            else
                local v
                for i = 1, #args.cat do
                    v = args.cat[ i ]
                    s = type( v )
                    if s == "string" then
                        s = mw.text.trim( v )
                        if s ~= "" then
                            c = c or { }
                            table.insert( c, s )
                        end
                    elseif s == "table"  and
                           type( v.baseText ) == "string" then
                        c = c or { }
                        table.insert( c, v.text )
                    end
                end -- i = 1, #args.cat
            end
        end
        if c then
            suffix = ""
            for i = 1, #c do
                suffix = string.format( "%s[[Category:%s]]",
                                        suffix, c[ i ] )
            end -- i = 1, #c
        end
    end
    if args.elem then
        if suffix then
            e:wikitext( suffix )
        end
        args.elem:node( e )
    else
        r = tostring( e )
        if suffix then
            r = r .. suffix
        end
    end
    return r
end -- Sort.fault()



Sort.feature = function ( args, access, assign )
    -- Retrieve or set CSS property
    -- Precondition:
    --     args      -- table, parameters
    --     access    -- string, property name
    --     assign    -- string|nil, value to be set
    -- Postcondition:
    --     Returns string, or not, if not assigned
    local r
    if assign then
        args.props               = args.props  or  { }
        args.props.css           = args.props.css  or  { }
        args.props.css[ access ] = assign
    elseif args.props  and
       args.props.css  and
       args.props.css[ access ] then
        r = args.props.css[ access ]
    end
    return r
end -- Sort.feature()



Sort.finalize = function ( args, append )
    -- Complete table cell
    -- Parameter:
    --     args      -- table, parameters
    --                  .props     -- table, if present
    --                  .cell      -- true, if mandatory table syntax
    --                  .elem      -- table, if mw.html
    --                  .scream    -- string|nil, with error message
    --     append    -- string|nil, with content
    -- Postcondition:
    --     Returns string, or expands .elem, or nil
    local r
    if args.props then
        if args.elem then
            facing( args, args.elem )
            if append then
                args.elem:wikitext( append )
            end
        elseif append  and  not args.cell then
            local e = mw.html.create( "span" )
                             :wikitext( append )
            facing( args, e )
            r = tostring( e )
        else
            r = facing( args, append )
        end
    elseif args.elem and append then
        args.elem:wikitext( append )
    else
        r = append
    end
    if args.scream then
        if args.elem then
            Sort.fault( args.scream, args )
        else
            r = r or ""
            r = r .. Sort.fault( args.scream, args )
        end
    end
    return r
end -- Sort.finalize()



Sort.first = function ( args, always )
    -- Initialize table cell start
    -- Parameter:
    --     args    -- table, parameters
    --                .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
    --                .lang       -- string, for cell attribute
    --                .dir        -- string, for cell attribute
    --                .frame      -- object, if present
    --     always    -- true, if mandatory table syntax
    -- Postcondition:
    --     Returns table with consolidated parameters
    local r = { }
    local s = type( args.style )
    if s == "string" then
        features( args, r )
    elseif s == "table" then
        r.props     = { }
        r.props.css = args.style
    end
    s = type( args.cell )
    if s == "string" then
        r.cell = ( args.cell == "1" )
    elseif s == "table"  and
           type( args.cell.node ) == "function" then
        r.elem = args.cell
        r.cell = true
    elseif s == "boolean" then
        r.cell = args.cell
    else
        r.cell = always
    end
    if r.cell then
        face( r, "rowspan", args, false, true )
        face( r, "colspan", args, false, true )
    end
    face( r, "class", args )
    face( r, "id", args )
    face( r, "lang", args )
    face( r, "dir", args )
    if type( args.frame ) == "table" then
        r.frame = args.frame
    end
    return r
end -- Sort.first()



Sort.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 -- Sort.following()



Sort.formatDate = function ( align, at )
    -- Format local date and time
    --     align    -- string, with format
    --     at       -- string|nil, with timestamp
    -- Postcondition:
    --     Returns string
    Sort.contLang = Sort.contLang  or
                    mw.language.getContentLanguage()
    return Sort.contLang:formatDate( align, at, true )
end -- Sort.following()



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