Modul:Sort

aus Wikipedia, der freien Enzyklopädie
Zur Navigation springen Zur Suche springen

Die Dokumentation für dieses Modul kann unter Modul:Sort/Doku erstellt werden

local Sort = { suite  = "Sort",
               serial = "2024-07-02",
               item   = 24205172 }
--[=[
Sort
]=]


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


local Failsafe  = Sort
local GlobalMod = Sort



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()



Sort.lex = function ( adjust, apply, adapt )
    -- Build ASCII sortkey for text value
    -- Precondition:
    --     adjust  -- string to be aligned
    --     apply   -- string or table, with base
    --                "latin"
    --     adapt   -- string or table, with variation, or false
    --                "DIN5007m2"  -- DIN 5007 mode "2"
    local r = adjust
    if adapt  or  not r:match( "^[ -~]*$" ) then
        local collate, post, pre
        if apply then
            collate = apply
        else
            collate = "uni"
        end
        if type( collate ) == "string" then
            collate = foreignModule( Sort.suite,
                                     false,
                                     collate,
                                     Sort.item )
        end
        if adapt  and  type( collate ) == "table" then
            local variants = type( adapt )
            local n
            if variants == "string" then
                variants = mw.text.split( adapt, "%s+" )
            elseif variants == "table" then
                variants = adapt
            else
                variants = { }
            end
            n = #variants
            if n == 1  and  variants[ 1 ] == "" then
                n = 0
            end
            if n > 0 then
                local tmp = { }
                local var
                for k, v in pairs( collate ) do
                    tmp[ k ] = v
                end    -- for k, v
                collate = tmp
                for i = 1, n do
                    tmp = foreignModule( Sort.suite,
                                         false,
                                         variants[ i ],
                                         Sort.item )
                    if type( tmp ) == "table" then
                        var = tmp.single
                        if type( var ) ~= "table" then
                            -- legacy
                            var = tmp
                        end
                        if type( var ) == "table" then
                            for k, v in pairs( var ) do
                                collate[ k ] = v
                            end    -- for k, v
                        end
                        var = tmp.pre
                        if type( var ) == "table" then
                            if type( pre ) ~= "table" then
                                pre = { }
                            end
                            for k, v in pairs( var ) do
                                pre[ k ] = v
                            end    -- for k, v
                        end
                        var = tmp.post
                        if type( var ) == "table" then
                            if type( post ) ~= "table" then
                                post = { }
                            end
                            for k, v in pairs( var ) do
                                post[ k ] = v
                            end    -- for k, v
                        end
                    elseif type( tmp ) == "string" then
                        collate = tmp
                        break    -- for i
                    else
                        collate = "Invalid table " .. variants[ i ]
                        break    -- for i
                    end
                end    -- for i
            end
        end
        if type( collate ) == "table" then
            local k, n, s, start
            if type( pre ) == "table" then
                for k, v in pairs( pre ) do
                    r = mw.ustring.gsub( r, k, v )
                end    -- for k, v
            end
            n = mw.ustring.len( r )
            for i = n, 1, -1 do
                k = mw.ustring.codepoint( r, i, i )
                if k < 127 then    -- ASCII
                    s = ( k < 32 )    -- htab newline whitespace
                    if s then
                        s = " "
                    end
                elseif ( k >= 0x0300  and  k <= 0x0362 )   or
                       ( k >= 0x1AB0  and  k <= 0x1AFF )   or
                       ( k >= 0x1DC0  and  k <= 0x1DFF )   or
                       ( k >= 0xFE20  and  k <= 0xFE2F ) then
                    -- COMBINING ...
                    s = ""
                else
                    s = collate[ k ]
                end
                if s then
                    if i > 1 then
                        s = mw.ustring.sub( r, 1,  i - 1 )  ..  s
                    end
                    r = s .. mw.ustring.sub( r,  i + 1 )
                end
            end    -- for i--
            if type( post ) == "table" then
                for k, v in pairs( post ) do
                    r = mw.ustring.gsub( r, k, v )
                end    -- for k, v
            end
        else
            r = "**ERROR** Sort.lex ** Submodule unavailable " .. collate
        end
    end
    r = r:gsub( "  +", " " )
    return r
end -- Sort.lex()



Sort.num = function ( adjust, ad, at, align, absolute )
    -- Build sortkey for heading numerical value
    -- Precondition:
    --     adjust    -- string to be aligned; leading digits / minus
    --     ad        -- decimal separator; "." or ","; defaults to "."
    --     at        -- thousands group separator; defaults to none
    --                  ","  "."  "'"
    --     align     -- number of leading zeros / maximum length
    --                  defaults to 15
    --     absolute  -- negative figures by digits; default: by value
    -- Postcondition:
    --     Returns string with sortkey
    local max    = 15
    local mid    = 46    -- "."
    local min1   = -1    -- none
    local min2   = -2    -- none
    local low    = false
    local last   = false
    local lead   = true
    local source = tostring( adjust )
    local sub    = "."
    local suffix = false
    local n      = mw.ustring.len( source )
    local r      = ""
    local c
    if ad then
        mid = mw.ustring.codepoint( ad, 1, 1 )
    end
    if at then
        min1, min2 = mw.ustring.codepoint( at, 1, 2 )
    end
    if align then
        max = align
    end
    for i = 1, n do
        c = mw.ustring.codepoint( source, i, i )
        if c > 32 then    -- not whitespace
            if c >= 48 and c <= 57 then    -- digits
                r   = string.format( "%s%c", r, c )
                max = max - 1
            elseif c == min1 or c == min2 then    -- group separator
            elseif c == mid then    -- decimal separator
                 for j = i + 1, n do
                     c = mw.ustring.codepoint( source, j, j )
                     if c >= 48 and c <= 57 then    -- digits
                         sub = string.format( "%s%c", sub, c )
                     elseif c == min1 or c == min2 then    -- grouping
                     else
                         i = j
                         break    -- for j
                     end
                     i = n
                 end    -- for j
                 last = true
            elseif lead then
                if c == 45 or c == 8722 then    -- minus
                    low = true
                elseif c ~= 43 then    -- plus
                    last = true
                end
            else
                last = true
            end
            lead = false
        elseif not lead then    -- whitespace not leading
            last = true
        end
        if last then
            if i < n then
                suffix = mw.ustring.sub( source, i )
                if c == 69  or  c == 101 then    -- E e
                    local s = suffix:match( "^[Ee](-?%d+)" )
                    local j
                    if s then
                        j      = tonumber( s )
                        sub    = sub:sub( 2 )
                        suffix = suffix:sub( #s + 2 )
                        if j > 0 then
                            if j > #sub then
                                sub = sub .. string.rep( "0",  j - #sub )
                            end
                            r   = r .. sub:sub( 1, j )
                            sub = sub:sub( j + 1 )
                            max = max - j
                        elseif j < 0 then
                            j = - j
                            if j > #r then
                                r = string.rep( "0",  j - #r ) .. r
                            end
                            sub = r:sub( - j ) .. sub
                            r   = r:sub( 1,  #r - j )
                            max = max + j
                        end
                        sub = "." .. sub
                    end
                end
            end
            break    -- for i
       end
    end    -- for i
    if low then
        if not absolute then   -- complementary value
            local s    = "."
            local cmpl = function ( str, k )
                             return 57 - str:byte( k )
                         end
            for i = 2, #sub do
                s = string.format( "%s%d",  s,  cmpl( sub, i ) )
            end    -- for i
            for i = #r, 1, -1 do
                s = string.format( "%d%s",  cmpl( r, i ),  s )
            end    -- for i--
            r = s
            if max > 0 then
                r = string.rep( "9", max )  ..  r
            end
            sub = false
            max = 0
        end
    end
    if sub then
        r = r .. sub
    end
    if max > 0 then
        r = string.rep( "0", max )  ..  r
    end
    if low then
        r = "-" .. r
    end
    if suffix then
        r = string.format( "%s %s", r, suffix )
    end
    return r
end -- Sort.num()



Failsafe.failsafe = function ( atleast )
    -- Retrieve versioning and check for compliance
    -- Precondition:
    --     atleast  -- string, with required version
    --                         or wikidata|item|~|@ or false
    -- Postcondition:
    --     Returns  string  -- with queried version/item, also if problem
    --              false   -- if appropriate
    -- 2024-03-01
    local since  = atleast
    local last   = ( since == "~" )
    local linked = ( since == "@" )
    local link   = ( since == "item" )
    local r
    if last  or  link  or  linked  or  since == "wikidata" then
        local item = Failsafe.item
        since = false
        if type( item ) == "number"  and  item > 0 then
            local suited = string.format( "Q%d", item )
            if link then
                r = suited
            else
                local entity = mw.wikibase.getEntity( suited )
                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
                        elseif linked then
                            if mw.title.getCurrentTitle().prefixedText
                               ==  mw.wikibase.getSitelink( suited ) then
                                r = false
                            else
                                r = suited
                            end
                        else
                            r = vsn.value
                        end
                    end
                end
            end
        elseif link then
            r = false
        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.Tlatin = function ( frame )
    -- Template::latin
    --     {{{1}}}
    -- #invoke
    --     v  -- variant, omitted or "DIN5007m2"
    local lucky, r = pcall( Sort.lex,
                            frame.args[ 1 ]  or
                            frame:getParent().args[ 1 ]  or
                            "",
                            "latin",
                            frame.args.v )
    return r
end -- p.Tlatin



p.Tn = function ( frame )
    -- Template::numerical
    --     {{{1}}}
    -- #invoke
    --     d  -- decimal separator; defaults to "."
    --     t  -- thousands group separator; defaults to none
    --     z  -- number of leading zeros / maximum length; defaults to 15
    --     m  -- negative figures by digits; default: by value
    local lucky, r = pcall( Sort.num,
                            frame.args[ 1 ]  or
                            frame:getParent().args[ 1 ]  or
                            "",
                            frame.args.d,
                            frame.args.t,
                            tonumber( frame.args.z ),
                            frame.args.m == "1" )
    return r
end -- p.Tn



p.textOrder = function ( frame )
    -- order text
    --     {{{1}}}  -- first text
    --     {{{2}}}  -- second text
    -- Not empty if {{{1}}} is before {{{2}}}
    local t1 = frame.args[ 1 ]  or  ""
    local t2 = frame.args[ 2 ]  or  ""
    local r
    if t1 < t2 then
        r = "1"
    else
        r = ""
    end
    return r
end -- p.textOrder



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 ()
    return Sort
end -- p.Sort

setmetatable( p,  { __call = function ( func, ... )
                                 setmetatable( p, nil )
                                 return Failsafe
                             end } )
return p