Modul:CoordParse

aus Wikipedia, der freien Enzyklopädie
Zur Navigation springen Zur Suche springen
Vorlagenprogrammierung Diskussionen Lua 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: 2022-09-15

local CoordParse = { suite  = "CoordParse",
                     serial = "2022-09-15",
                     item   = 113956219 }
--[==[
Coordinate parsing and validation
* feed
* focus
* fragments
* failsafe
]==]
local Failsafe = CoordParse



local function factory()
    -- Create patterns
    -- Postcondition:
    --     Patterns available
    if not CoordParse.re then
        CoordParse.re = {
            WS  = mw.ustring.char( 91, 37, 115,
                                       0xA0,
                                       0x1680,
                                       0x2000, 45, 0x200A,
                                       0x202F,
                                       0x205F,
                                       0x3000,
                                       0x303F, 93 ),
            Deg = mw.ustring.char( 91, 0xB0,
                                       0xBA, 93 ),
            Min = mw.ustring.char( 91, 0x27,
                                       0x2032,
                                       0x2019, 93 ),
            Sec = mw.ustring.char( 91, 0x22,
                                       0x2033,
                                       0x201C,
                                       0x201D, 93 ),
            sep = "[,;/]"
        }
    end
end -- factory()



local function faculty( analyze )
    -- Test for boolean interpretation
    -- Precondition:
    --     analyze  -- string or boolean or nil
    -- Postcondition:
    --     returns boolean
    local s = type( analyze )
    local r
    if s == "string" then
        r = mw.text.trim( analyze )
        if r == ""  or  r == "0"  or  r == "-" then
            r = false
        elseif r == "1" then
            r = true
        else
            r = r:lower()
            if r == "y"  or
               r == "yes"  or
               r == "true"  or
               r == "on" then
                r = true
            elseif r == "n"  or
                   r == "no"  or
                   r == "false"  or
                   r == "off" then
                r = false
            else
                if r == "falsch"  or  r == "nein" then
                    r = false
                    --    error( "faculty@Expr", 0 )
                else
                    r = true
                end
            end
        end
    elseif s == "boolean" then
        r = analyze
    elseif s == "nil" then
        r = false
    else
        r = true
    end
    return r
end -- faculty()



local function fair( adjust )
    -- Advanced trim
    -- Precondition:
    --     adjust  -- string, to be trimmed, or something else
    --     CoordParse.re has been initialized
    -- Postcondition:
    --     Return trimmed string, or false if empty
    local r
    if type( adjust ) == "string" then
        if not CoordParse.re.TrimL then
            CoordParse.re.TrimL = string.format( "^%s+",
                                                 CoordParse.re.WS )
        end
        r = mw.ustring.gsub( adjust, CoordParse.re.TrimL, "" )
        if r == "" then
            r = false
        else
            if not CoordParse.re.TrimR then
                CoordParse.re.TrimR = string.format( "%s+$",
                                                     CoordParse.re.WS )
            end
            r = mw.ustring.gsub( r, CoordParse.re.TrimR, "" )
        end
    else
        r = adjust
    end
    return r
end -- fair()



local function fault( apply, about )
    -- Error message
    -- Precondition:
    --     apply  -- string, with message ID, or not
    --     about  -- string, with details, or not
    -- Postcondition:
    --     Return mw.html object
    local r = mw.html.create( "span" )
    local s
    if apply then
        if CoordParse.err then
            local std = CoordParse.err[ "err" .. apply ]
            if type( std ) == "string" then
                std = mw.text.trim( std )
                if std ~= "" then
                    s = std
                end
            end
        end
        if not s then
            s = string.format( "((%s))", apply )
        end
    end
    if about then
        if s then
            s = string.format( "%s: %s",  s,  mw.text.nowiki( about ) )
        else
            s = about
        end
    end
    if CoordParse.err then
        if type( CoordParse.err.Cat ) == "string"  and
           CoordParse.err.Cat ~= "" then
            s = string.format( "%s[[Category:%s]]",
                               s,
                               CoordParse.err.Cat:gsub( "%]%]", "" ) )
        end
        if type( CoordParse.err.errClass ) == "string"  and
           CoordParse.err.errClass ~= "" then
            r:addClass( CoordParse.err.errClass )
        end
        if type( CoordParse.err.errStyle ) == "string"  and
           CoordParse.err.errStyle ~= "" then
            r:cssText( CoordParse.err.errStyle )
        end
    end
    r:addClass( "error" )
     :wikitext( s )
    return r
end -- fault()



local function fetch()
    -- Retrieve Expr library
    -- Postcondition:
    --     Return some message string, if failed
    local r
    if CoordParse.Expr then
        if type( CoordParse.Expr ) == "string" then
            r = CoordParse.Expr
        end
    else
        local lucky
        lucky, CoordParse.Expr = pcall( require, "Module:Expr" )
        if type( CoordParse.Expr ) == "table" then
            lucky, CoordParse.Expr = pcall( CoordParse.Expr )
            if type( CoordParse.Expr ) ~= "table"  or
               type( CoordParse.Expr.figure ) ~= "function" then
                r = "Invalid library 'Expr'"
            end
        else
            r = CoordParse.Expr
        end
        if r then
            r               = tostring( fault( r ) )
            CoordParse.Expr = r
        end
    end
    return r
end -- fetch()



local function field( apply, align, arglist )
    -- Parse compass direction word
    -- Precondition:
    --     apply    -- string, with word, or not
    --     align    -- true, for latitude
    --     arglist  -- table, with options
    -- Postcondition:
    --     Return
    --         1   -- mw.html object, with error message, if failed
    --         2   -- string, with SNWE letter, or not
    local r1, r2
    if apply  and  mw.ustring.match( apply, "^%a+$" ) then
        local supply = mw.ustring.upper( apply )
        local scan
        if align then
            scan = string.format( "^%s$",  arglist.N or "N" )
            if mw.ustring.match( supply, scan ) then
                r2 = "N"
            else
                scan = string.format( "^%s$",  arglist.S or "S" )
                if mw.ustring.match( supply, scan ) then
                    r2 = "S"
                end
            end
        else
            scan = string.format( "^%s$",  arglist.E or "E" )
            if mw.ustring.match( supply, scan ) then
                r2 = "E"
            else
                scan = string.format( "^%s$",  arglist.W or "W" )
                if mw.ustring.match( supply, scan ) then
                    r2 = "W"
                end
            end
        end
        if not r2 then
            r1 = fault( "Word", apply )
        end
    end
    return r1, r2
end -- field()



local function figure( analyze )
    -- Parse string
    -- Precondition:
    --     analyze   - string or number, with figure
    --     Expr available
    -- Postcondition:
    --     Return number or not
    local s = type( analyze )
    local r
    if s == "string" then
        if analyze:find( "," )  then
           s = "-,"
        else
           s = "-."
        end
        r = CoordParse.Expr.figure( analyze, s )
    elseif s == "number" then
        r = analyze
    end
    return r
end -- figure()



local function focus( align, apply, arglist )
    -- Extract single coordinate from set
    --     align    -- true, for latitude
    --     apply    -- string, with set
    --     arglist  -- table, with options
    -- Postcondition:
    --     Return single number, or mw.html error message
    local f = function ( a )
                  local re = ( arglist[ a ]  or  a )  ..  "%s*"
                  return re
              end -- f()
    local suite = "NSEW"
    local begin = { }
    local ended = { }
    local j, k, lucky, r, s
    for i = 1, 4 do
        s = f( suite:sub( i, i ) )
        j, k = mw.ustring.find( apply, s, 2 )
        table.insert( begin,  j or false )
        table.insert( ended,  k or false )
        if k  and  mw.ustring.find( apply,  s,  k + 1 ) then
            r = true
            break -- for i
        elseif j then
            lucky = true
        end
    end -- for i
    if r  or
       ( begin[ 1 ] and begin[ 2 ] )  or
       ( begin[ 3 ] and begin[ 4 ] ) then
        r = fault( "Multi", apply )
    else
        local j0, j1
        s = apply
        if lucky then
            j0 = begin[ 3 ] or begin[ 4 ]
            j1 = begin[ 1 ] or begin[ 2 ]
        end
        if j0 and j1 then
            local k, lead
            if j0 < j1 then
                k = ended[ 3 ] or ended[ 4 ]
                if align then
                    s    = mw.ustring.sub( s,  k + 1 )
                    lead = true
                else
                    s = mw.ustring.sub( s, 1, k )
                end
            else
                k = ended[ 1 ] or ended[ 2 ]
                if align then
                    s = mw.ustring.sub( s, 1, k )
                else
                    s    = mw.ustring.sub( s,  k + 1 )
                    lead = true
                end
            end
            if lead then
                local sep
                s   = fair( s )
                sep = mw.ustring.sub( s, 1, 1 )
                if mw.ustring.match( sep, CoordParse.re.sep ) then
                    s = mw.ustring.sub( s, 2 )
                end
            end
        elseif ( j0  and  not align )  or
               ( j1  and  align )  or
               not lucky then
            s = apply
        else
            s = false
        end
        s = fair( s )
        if not s  or  s == "-" then
            r = fault( "Empty", apply )
        end
    end
    if not r then
        r = CoordParse.feed( align, s, arglist )
    end
    return r
end -- focus()



local function fracking( apply, accept )
    -- Parse number with unit
    -- Precondition:
    --     apply   -- string, with coordinate remainder
    --     accept  -- string, with key for pattern of unit
    --                        -- "Deg"
    --                        -- "Min"
    --                        -- "Sec"
    --                        -- "Min2"
    -- Postcondition:
    --     Return
    --         1   -- number, if found
    --         2   -- string, with remainder, or not
    local s = CoordParse.re[ accept ]
    local i, j = mw.ustring.find( apply, s, 2 )
    local r1, r2
    if i then
        s = mw.ustring.sub( apply,  1,  i - 1 )
        r1 = figure( s )
        if r1 then
            r2 = fair( mw.ustring.sub( apply,  j + 1 ) )
        end
    end
    return r1, r2
end -- fracking()



local function from( apply, align, arglist )
    -- Parse string
    -- Precondition:
    --     apply    -- string, with coordinate
    --     align    -- true, for latitude
    --     arglist  -- table, with options
    -- Postcondition:
    --     Return  -- mw.html object, with error message, if failed
    --             -- number, if success
    local g, r, snwe
    if apply:find( "/", 1, true ) then
        local n
        g = mw.text.split( apply, "%s*/%s*" )
        n = #g
        if n > 4 then
            r = fault( "GT4", apply )
        else
            r, snwe = field( fair( g[ n ] ),  align,  arglist )
            if not r then
                if snwe then
                    g[ n ] = false
                    n      = n - 1
                end
            end
        end
    else
        local s = fair( apply )
        local start, suffix
        if s then
            start, suffix = mw.ustring.match( s, "^(.*%A)(%a+)$" )
            if start then
                r, snwe = field( suffix, align, arglist )
                s = fair( start )
            end
        else
            r = fault( "Empty", s )
        end
        if not r then
            r = fetch()
        end
        if not r then
            g = { }
            start, suffix = fracking( s, "Deg" )
            if start then
                table.insert( g, start )
                s = fair( suffix )
                if s then
                    start, suffix = fracking( s, "Min" )
                    if start then
                        table.insert( g, start )
                        s = fair( suffix )
                        if s then
                            start, suffix = fracking( s, "Sec" )
                            if start then
                                table.insert( g, start )
                            else
                                if not r then
                                    CoordParse.re.Min2 =
                                                     CoordParse.re.Min ..
                                                     CoordParse.re.Min
                                end
                                start, suffix = fracking( s, "Min2" )
                                if start then
                                    table.insert( g, start )
                                else
                                    r = fault( "Bad", s )
                                end
                            end
                            s = fair( suffix )
                            if s then
                                r = fault( "Bad", s )
                            end
                        end
                    else
                        r = fault( "Bad", s )
                    end
                end
            else
                r = fetch()
                if not r then
                s = figure( s )
                    if s then
                        table.insert( g, s )
                    else
                        r = fault( "Bad", apply )
                    end
                end
            end
        end
    end
    if not r then
        if not snwe then
            if align then
                snwe = "N"
            else
                snwe = "E"
            end
        end
        for i = #g + 1,  3 do
            table.insert( g, false )
        end -- for i
        if g == 3 then
            table.insert( g, snwe )
        else
            g[ 4 ] = snwe
        end
        r = CoordParse.fragments( align, g, arglist )
    end
    return r
end -- from()



CoordParse.feed = function ( align, adjust, arglist )
    -- Parse single string
    -- Precondition:
    --     align    -- true, for latitude
    --     adjust   -- string, to be parsed
    --     arglist  -- table, with options
    -- Postcondition:
    --     Return single number, or mw.html error message
    local r, stuff
    if type( arglist ) == "table" then
        CoordParse.err = arglist
        if type( adjust ) == "string" then
            factory()
            stuff = adjust
            if stuff:find( "<", 1, true ) then
                stuff = stuff:gsub( "<[^>]*>", "" )
            end
            if stuff:find( "&.+;" ) then
                stuff = mw.text.decode( stuff, true )
            end
            r = from( stuff, align, arglist )
        end
    end
    if not stuff then
        r = fault( "Empty" )
    end
    return r
end -- CoordParse.feed()



CoordParse.focus = function ( align, all, arglist )
    -- Parse coordinate set
    --     align    -- true, for latitude
    --     all      -- string, with set
    --     arglist  -- table, with options
    -- Postcondition:
    --     Return single number, or mw.html error message
    local r
    if type( arglist ) == "table" then
        CoordParse.err = arglist
        if type( all ) == "string" then
            factory()
            r = all
            if r:find( "&.+;" ) then
                r = mw.text.decode( r, true )
            end
            r = fair( r )
            if r then
                r = focus( align, r, arglist )
            end
        end
    end
    if not r then
        r = fault( "Empty" )
    end
    return r
end -- CoordParse.focus()



CoordParse.fragments = function ( align, array, arglist )
    -- Parse component set
    -- Precondition:
    --     align    -- true, for latitude
    --     array    -- sequence table, with components
    --                 [ 1 ]  -- string or number, with degrees
    --                 [ 2 ]  -- string or number, with minutes, or not
    --                 [ 3 ]  -- string or number, with seconds, or not
    --                 [ 4 ]  -- string or not, with direction
    --     arglist  -- table, with options
    -- Postcondition:
    --     Return single number, or mw.html error message
    local r
    if type( array ) == "table"  and  type( arglist ) == "table" then
        local g = { }
        local max, min, s, v
        factory()
        for i = 1, #array do
            v = array[ i ]
            s = type( v )
            if s == "string" then
                v = fair( v )
                if v  and  i < 4 then
                    max = i
                    if not min  and  v:find( "[%.,]%d" ) then
                        min = i
                    end
                end
            elseif s == "number" then
                if i < 4 then
                    max = i
                    if not min  and  v ~= math.floor( v ) then
                        min = i
                    end
                end
            else
                v = false
            end
            table.insert( g, v )
        end -- for i
        for i = #g + 1, 4 do
            table.insert( g, false )
        end -- for i
        if g[ 1 ] then
            if g[ 3 ]  and  not g[ 2 ] then
                r = fault( "MinX" )
            elseif min  and  min < max then
                r = fault( "SepEl",  tostring( g[ min ] ) )
            else
                r = fetch()
                if not r then
                    for i = 1, max do
                        v = g[ i ]
                        if type( v ) == "string" then
                            v = figure( v )
                            if v then
                                g[ i ] = v
                            else
                                r = fault( "Num", v )
                                break -- for i
                            end
                        end
                    end -- for i
                end
                if not r  and  max > 1 then
                    v = g[ 2 ]
                    if v < 0 then
                        r = fault( "Mlt0",  tostring( v ) )
                    elseif v >= 60 then
                        r = fault( "Mgt60",  tostring( v ) )
                    elseif max > 2 then
                        v = g[ 3 ]
                        if v < 0 then
                            r = fault( "Slt0",  tostring( v ) )
                        elseif v >= 60 then
                            r = fault( "Sgt60",  tostring( v ) )
                        end
                    end
                end
                if not r then
                    v = g[ 1 ]
                    if align then
                        if v < -90 then
                            r = fault( "DegLT",  tostring( v ) )
                        elseif v > 90 then
                            r = fault( "DegGT",  tostring( v ) )
                        end
                    else
                        if v <= -180 then
                            r = fault( "DegLT",  tostring( v ) )
                        elseif v > 180 then
                            if v < 360 then
                                g[ 1 ] = v - 360
                                g[ 4 ] = "W"
                            else
                                r = fault( "DegGT",  tostring( v ) )
                            end
                        end
                    end
                    if not r then
                        r = g[ 1 ]
                        if g[ 2 ] then
                            r = r  +  g[ 2 ] * 0.0166666666666667
                            if g[ 3 ] then
                                r = r  +  g[ 3 ] * 0.0002777777777777778
                            end
                        end
                        if g[ 4 ] then
                            if r > 0 then
                                if g[ 4 ] == "S"  or
                                   g[ 4 ] == "W" then
                                    r = -1 * r
                                end
                            elseif r < 0  and
                                   g[ 4 ] ~= "S"  and
                                   g[ 4 ] ~= "W" then
                                r = fault( "Minus" )
                            end
                        end
                    end
                end
            end
        else
            r = fault( "DegX" )
        end
    end
    return r
end -- CoordParse.fragments()



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
    -- 2020-08-17
    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
        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 = {}

function p.feed( frame )
    return tostring( CoordParse.feed( faculty( frame.args.latitude ),
                                      frame.args[ 1 ],
                                      frame.args ) )
end

function p.focus( frame )
    return tostring( CoordParse.focus( faculty( frame.args.latitude ),
                                       frame.args[ 1 ],
                                       frame.args ) )
end

function p.fragments( frame )
    local latitude = faculty( frame.args.latitude )
    local parts = { }
    for i = 1, 4 do
        table.insert( parts, frame.args[ i ] )
    end -- for i
    return tostring( CoordParse.fragments( latitude,
                                           parts,
                                           frame.args ) )
end

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

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

return p