Modul:JSONutil: Unterschied zwischen den Versionen

Aus skandinavien-wiki.net
(2019-05-16)
 
Keine Bearbeitungszusammenfassung
Markierung: Manuelle Zurücksetzung
 
(16 dazwischenliegende Versionen von 3 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
local JSONutil = { suite  = "JSONutil",
local JSONutil = { suite  = "JSONutil",
                   serial = "2019-05-16",
                   serial = "2020-11-08",
                   item  = 63869449 }
                   item  = 63869449 }
--[=[
--[=[
preprocess JSON data
preprocess or generate JSON data
]=]
]=]






local Failsafe = JSONutil
JSONutil.Encoder  = { stab  = string.char( 9 ),
                      sep    = string.char( 10 ),
                      scream = "@error@JSONencoder@" }
JSONutil.more = 50    -- length of trailing context
JSONutil.more = 50    -- length of trailing context






JSONutil.failsafe = function ( atleast )
local Fallback = function ()
     -- Retrieve versioning and check for compliance
    -- Retrieve current default language code
     -- Precondition:
    --    Returns  string
     --    atleast -- string, with required version or "wikidata"
    return mw.language.getContentLanguage():getCode()
     --                 or false
                                          :lower()
     -- Postcondition:
end -- Fallback()
     --    Returns string with appropriate version, or false
 
    local since = atleast
 
 
local flat = function ( adjust )
     -- Clean template argument string
     -- Parameter:
     --    adjust -- string, or not
    -- Returns:
    --    string
    local r
    if adjust then
        r = mw.text.trim( mw.text.unstripNoWiki( adjust ) )
    else
        r = ""
    end
    return r
end -- flat()
 
 
 
local flip = function ( frame )
     -- Retrieve template argument indent
     -- Parameter:
     --    frame  -- object
    -- Returns:
    --    number, of indentation level, or not
     local r
     local r
     if since == "wikidata" then
     if frame.args.indent  and  frame.args.indent:match( "^%d+$" ) then
         local item = JSONutil.item
         r = tonumber( frame.args.indent )
        since = false
    end
        if type( item ) == "number" and  item > 0 then
    return r
            local entity = mw.wikibase.getEntity( string.format( "Q%d",
end -- flip()
                                                                item ) )
 
             if type( entity ) == "table" then
 
                local vsn = entity:formatPropertyValues( "P348" )
 
                if type( vsn ) == "table" and
JSONutil.Encoder.Array = function ( apply, adapt, alert )
                  type( vsn.value ) == "string" and
    -- Convert table to JSON Array
                  vsn.value ~= "" then
    -- Parameter:
                    r = vsn.value
    --    apply  -- table, with sequence of raw elements, or
    --              string, with formatted Array, or empty
    --    adapt  -- string, with requested type, or not
    --    alert  -- true, if non-numeric elements shall trigger errors
    -- Returns:
    --    string, with JSON Array
    local r = type( apply )
    if r == "string" then
        r = mw.text.trim( apply )
        if r == "" then
            r = "[]"
        elseif r:byte( 1, 1 ) ~= 0x5B  or
              r:byte( -1, -1 ) ~= 0x5D then
             r = false
        end
    elseif r == "table" then
        local n = 0
        local strange
        for k, v in pairs( apply ) do
            if type( k ) == "number" then
                if k > n then
                    n = k
                end
            elseif alert then
                if strange then
                    strange = strange .. " "
                else
                    strange = ""
                 end
                 end
                strange = strange .. tostring( k )
             end
             end
        end -- for k, v
        if strange then
            r = string.format( "{ \"%s\": \"%s\" }",
                              JSONutil.Encoder.scream,
                              JSONutil.Encoder.string( strange ) )
        elseif n > 0 then
            local sep  = ""
            local scope = adapt or "string"
            local s
            if type( JSONutil.Encoder[ scope ] ) ~= "function" then
                scope = "string"
            end
            r = " ]"
            for i = n, 1, -1 do
                s = JSONutil.Encoder[ scope ]( apply[ i ] )
                r = string.format( "%s%s%s", s, sep, r )
                sep = ",\n  "
            end -- for i = n, 1, -1
            r = "[ " .. r
        else
            r = "[]"
         end
         end
    else
        r = false
     end
     end
     if not r then
     if not r then
         if not since or  since <= JSONutil.serial then
         r = string.format( "[ \"%s * %s\" ]",
             r = JSONutil.serial
                          JSONutil.Encoder.scream,
                          "Bad Array" )
    end
    return r
end -- JSONutil.Encoder.Array()
 
 
 
JSONutil.Encoder.boolean = function ( apply )
    -- Convert string to JSON boolean
    -- Parameter:
    --    apply  -- string, with value
    -- Returns:
    --    boolean as string
    local r = mw.text.trim( apply )
    if r == ""  or  r == "null"  or  r == "false"
      or  r == "0"  or  r == "-" then
        r = "false"
    else
        r = "true"
    end
    return r
end -- JSONutil.Encoder.boolean()
 
 
 
JSONutil.Encoder.Component = function ( access, apply,
                                        adapt, align, alert )
    -- Create single entry for mapping object
    -- Parameter:
    --    access  -- string, with component name
    --    apply  -- component value
    --    adapt  -- string, with value type, or not
    --    align  -- number, of indentation level, or not
    --    alert  --
    -- Returns:
    --    string, with JSON fragment, and comma
    local v    = apply
    local types = adapt
    local indent, liner, scope, sep, sign
    if type( access ) == "string" then
        sign = mw.text.trim( access )
        if sign == "" then
            sign = false
        end
    end
    if type( types ) == "string" then
        types = mw.text.split( mw.text.trim( types ),  "%s+" )
    end
    if type( types ) ~= "table" then
        types = { }
        table.insert( types, "string" )
    end
    if #types == 1 then
        scope = types[ 1 ]
    else
        for i = 1, #types do
            if types[ i ] == "boolean" then
                if v == "1"  or  v == 1  or  v == true then
                    v = "true"
                    scope = "boolean"
                elseif v == "0"  or  v == 0 or  v == false then
                    v = "false"
                    scope = "boolean"
                end
                if scope then
                    types = { }
                    break    -- for i
                else
                    table.remove( types, i )
                end
            end
        end  -- for i
        for i = 1, #types do
            if types[ i ] == "number" then
                if tonumber( v ) then
                    v    = tostring( v )
                    scope = "number"
                    types = { }
                    break    -- for i
                else
                    table.remove( types, i )
                end
            end
        end    -- for i
    end
    scope = scope or "string"
    if type( JSONutil.Encoder[ scope ] ) ~= "function" then
        scope = "string"
    elseif scope == "I18N" then
        scope = "Polyglott"
    end
    if scope == "string" then
        v = v or ""
    end
    if type( align ) == "number"  and  align > 0 then
        indent = math.floor( align )
        if indent == 0 then
             indent = false
        end
    end
    if scope == "object"  or  not sign then
        liner = true
    elseif scope == "string" then
        local k = mw.ustring.len( sign ) + mw.ustring.len( v )
        if k > 60 then
            liner = true
        end
    end
    if liner then
        if indent then
            sep = "\n" .. string.rep( "  ", indent )
         else
         else
             r = false
             sep = "\n"
        end
    else
        sep = " "
    end
    if indent then
        indent = indent + 1
    end
    return  string.format( " \"%s\":%s%s,\n",
                          sign or "???",
                          sep,
                          JSONutil.Encoder[ scope ]( v, indent ) )
end -- JSONutil.Encoder.Component()
 
 
 
JSONutil.Encoder.Hash = function ( apply, adapt, alert )
    -- Create entries for mapping object
    -- Parameter:
    --    apply  -- table, with element value assignments
    --    adapt  -- table, with value types assignment, or not
    -- Returns:
    --    string, with JSON fragment, and comma
    local r = ""
    local s
    for k, v in pairs( apply ) do
        if type( adapt ) == "table" then
            s = adapt[ k ]
        end
        r = r .. JSONutil.Encoder.Component( tostring( k ), v, s )
    end -- for k, v
    return
end -- JSONutil.Encoder.Hash()
 
 
 
JSONutil.Encoder.I18N = function ( apply, align )
    -- Convert multilingual string table to JSON
    -- Parameter:
    --    apply  -- table, with mapping object
    --    align  -- number, of indentation level, or not
    -- Returns:
    --    string, with JSON object
    local r = type( apply )
    if r == "table" then
        local strange
        local fault = function ( a )
                  if strange then
                      strange = strange .. " *\n "
                  else
                      strange = ""
                  end
                  strange = strange .. a
              end
        local got, sep, indent
        for k, v in pairs( apply ) do
            if type( k ) == "string" then
                k = mw.text.trim( k )
                if type( v ) == "string" then
                    v = mw.text.trim( v )
                    if v == "" then
                        fault( string.format( "%s %s=",
                                              "Empty text", k ) )
                    end
                    if not ( k:match( "%l%l%l?" )  or
                            k:match( "%l%l%l?-%u%u" )  or
                            k:match( "%l%l%l?-%u%l%l%l+" ) ) then
                        fault( string.format( "%s %s=",
                                              "Strange language code",
                                              k ) )
                    end
                else
                    v = tostring( v )
                    fault( string.format( "%s %s=%s",
                                          "Bad type for text",
                                          k,
                                          type( v ) ) )
                end
                got = got  or  { }
                got[ k ] = v
            else
                fault( string.format( "%s %s: %s",
                                      "Bad language code type",
                                      type( k ),
                                      tostring( k ) ) )
            end
        end -- for k, v
        if not got then
            fault( "No language codes" )
            got = { }
        end
        if strange then
            got[ JSONutil.Encoder.scream ] = strange
        end
        r = false
        if type( align ) == "number"  and  align > 0 then
            indent = math.floor( align )
        else
            indent = 0
        end
        sep = string.rep( "  ",  indent + 1 )
        for k, v in pairs( got ) do
            if r then
                r = r .. ",\n"
            else
                r = ""
            end
            r = string.format( "%s  %s%s: %s",
                              r,
                              sep,
                              JSONutil.Encoder.string( k ),
                              JSONutil.Encoder.string( v ) )
        end -- for k, v
        r = string.format( "{\n%s\n%s}", r, sep )
    elseif r == "string" then
        r = JSONutil.Encoder.string( apply )
    else
        r = string.format( "{ \"%s\": \"%s: %s\" }",
                          JSONutil.Encoder.scream,
                          "Bad Lua type",
                          r )
    end
    return r
end -- JSONutil.Encoder.I18N()
 
 
 
JSONutil.Encoder.number = function ( apply )
    -- Convert string to JSON number
    -- Parameter:
    --    apply  -- string, with presumable number
    -- Returns:
    --    number, or "NaN"
    local s = mw.text.trim( apply )
    JSONutil.Encoder.minus = JSONutil.Encoder.minus  or
                            mw.ustring.char( 0x2212 )
    s = s:gsub( JSONutil.Encoder.minus, "-" )
    return tonumber( s:lower() )  or  "NaN"
end -- JSONutil.Encoder.number()
 
 
 
JSONutil.Encoder.object = function ( apply, align )
    -- Create mapping object
    -- Parameter:
    --    apply  -- string, with components, may end with comma
    --    align  -- number, of indentation level, or not
    -- Returns:
    --    string, with JSON fragment
    local story = mw.text.trim( apply )
    local start = ""
    if story:sub( -1 ) == "," then
        story = story:sub( 1, -2 )
    end
    if type( align ) == "number"  and  align > 0 then
        local indent = math.floor( align )
        if indent > 0 then
            start = string.rep( "  ", indent )
        end
    end
    return  string.format( "%s{ %s\n%s}", start, story, start )
end -- JSONutil.Encoder.object()
 
 
 
JSONutil.Encoder.Polyglott = function ( apply, align )
    -- Convert string or multilingual string table to JSON
    -- Parameter:
    --    apply  -- string, with string or object
    --    align  -- number, of indentation level, or not
    -- Returns:
    --    string
    local r = type( apply )
    if r == "string" then
        r = mw.text.trim( apply )
        if not r:match( "^{%s*\"" )  or
          not r:match( "\"%s*}$" ) then
            r = JSONutil.Encoder.string( r )
         end
         end
    else
        r = string.format( "{ \"%s\": \"%s: %s\" }",
                          JSONutil.Encoder.scream,
                          "Bad Lua type",
                          r )
     end
     end
     return r
     return r
end -- JSONutil.failsafe()
end -- JSONutil.Encoder.Polyglott()
 
 
 
JSONutil.Encoder.string = function ( apply )
    -- Convert plain string to strict JSON string
    -- Parameter:
    --    apply  -- string, with plain string
    -- Returns:
    --    string, with quoted trimmed JSON string
    return  string.format( "\"%s\"",
                          mw.text.trim( apply )
                                  :gsub( "\\",  "\\\\" )
                                  :gsub( "\"",  "\\\"" )
                                  :gsub( JSONutil.Encoder.sep,  "\\n" )
                                  :gsub( JSONutil.Encoder.stab, "\\t" ) )
end -- JSONutil.Encoder.string()




Zeile 54: Zeile 442:
     --    apply  -- string, with enhanced JSON
     --    apply  -- string, with enhanced JSON
     -- Returns:
     -- Returns:
     --    1    -- string, or nil or false, with error keyword
     --    1    -- string|nil|false, with error keyword
     --    2    -- string, with JSON or context
     --    2    -- string, with JSON or context
     local m  = 0
     local m  = 0
     local n  = 0
     local n  = 0
     local s  = mw.text.trim( apply )
     local s  = mw.text.trim( apply )
    local sep = string.char( 10 )
     local i, j, last, r, scan, sep0, sep1, start, stub, suffix
     local i, j, r, scan, sep0, sep1, stab, start, stub, suffix
     local framework = function ( a )
     local framework = function ( a )
                               -- syntax analysis outside strings
                               -- syntax analysis outside strings
Zeile 81: Zeile 468:
                                   end
                                   end
                               end  -- while k
                               end  -- while k
                       end
                       end   -- framework()
    local free = function ( a, at, f )
                    -- Throws: error if /* is not matched by */
                    local s = a
                    local i = s:find( "//", at, true )
                    local k = s:find( "/*", at, true )
                    if i or k then
                        local m = s:find( sep0, at )
                        if i  and  ( not m  or  i < m ) then
                            k = s:find( "\n",  i + 2,  true )
                            if k then
                                if i == 1 then
                                    s = s:sub( k + 1 )
                                else
                                    s = s:sub( 1,  i - 1 )  ..
                                        s:sub( k + 1 )
                                end
                            elseif i > 1 then
                                s = s:sub( 1,  i - 1 )
                            else
                                s = ""
                            end
                        elseif k  and  ( not m  or  k < m ) then
                            i = s:find( "*/",  k + 2,  true )
                            if i then
                                if k == 1 then
                                    s = s:sub( i + 2 )
                                else
                                    s = s:sub( 1,  k - 1 )  ..
                                        s:sub( i + 2 )
                                end
                            else
                                error( s:sub( k + 2 ), 0 )
                            end
                            i = k
                        else
                            i = false
                        end
                        if i then
                            s = mw.text.trim( s )
                            if s:find( "/", 1, true ) then
                                s = f( s, i, f )
                            end
                        end
                    end
                    return s
                end    -- free()
     if s:sub( 1, 1 ) == '{' then
     if s:sub( 1, 1 ) == '{' then
        stab = string.char( 9 )
         s    = s:gsub( string.char( 13, 10 ),  JSONutil.Encoder.sep )
         s    = s:gsub( string.char( 13, 10 ),  sep )
                 :gsub( string.char( 13 ),  JSONutil.Encoder.sep )
                 :gsub( string.char( 13 ),  sep )
         stub = s:gsub( JSONutil.Encoder.sep, "" )
         stub = s:gsub( sep, "" ):gsub( stab, "" )
                :gsub( JSONutil.Encoder.stab, "" )
         scan = string.char( 0x5B, 0x01, 0x2D, 0x1F, 0x5D )
         scan = string.char( 0x5B, 0x01, 0x2D, 0x1F, 0x5D )   -- [ \-\ ]
         j    = stub:find( scan )
         j    = stub:find( scan )
         if j then
         if j then
             r = "ControlChar"
             r = "ControlChar"
             s = s:sub( j + 1, j + JSONutil.more )
             s = mw.text.trim( s:sub( j + 1 ) )
            s = mw.ustring.sub( s, 1, JSONutil.more )
         else
         else
             i    = true
             i    = true
             j    = 1
             j    = 1
            last = ( stub:sub( -1 ) == "}" )
             sep0 = string.char( 0x5B, 0x22, 0x27, 0x5D )    -- [ " ' ]
             sep0 = string.char( 0x5B, 0x22, 0x27, 0x5D )    -- [ " ' ]
             sep1 = string.char( 0x5B, 0x5C, 0x22, 0x5D )    -- [ \ " ]
             sep1 = string.char( 0x5B, 0x5C, 0x22, 0x5D )    -- [ \ " ]
Zeile 100: Zeile 535:
     else
     else
         r = "Bracket0"
         r = "Bracket0"
         s = s:sub( 1, JSONutil.more )
         s = mw.ustring.sub( s, 1, JSONutil.more )
     end
     end
     while i do
     while i do
         i = s:find( sep0, j )
         i, s = pcall( free, s, j, free )
        if i then
            i = s:find( sep0, j )
        else
            r = "CommentEnd"
            s = mw.text.trim( s )
            s = mw.ustring.sub( s, 1, JSONutil.more )
        end
         if i then
         if i then
             if j == 1 then
             if j == 1 then
Zeile 109: Zeile 551:
             end
             end
             if s:sub( i, i ) == '"' then
             if s:sub( i, i ) == '"' then
                 stub = s:sub( j + 1,  i - 1 )
                 stub = s:sub( j,  i - 1 )
                 if stub:find( '[^"]*,%s*[%]}]' ) then
                 if stub:find( '[^"]*,%s*[%]}]' ) then
                     r = "CommaEnd"
                     r = "CommaEnd"
                     s = stub:sub( 1, JSONutil.more )
                     s = mw.text.trim( stub )
                    s = mw.ustring.sub( s, 1, JSONutil.more )
                     i = false
                     i = false
                     j = false
                     j = false
Zeile 130: Zeile 573:
                             if j > i then
                             if j > i then
                                 stub = s:sub( i,  j - 1 )
                                 stub = s:sub( i,  j - 1 )
                                         :gsub( sep, "\\n" )
                                         :gsub( JSONutil.Encoder.sep,
                                         :gsub( stab, "\\t" )
                                              "\\n" )
                                         :gsub( JSONutil.Encoder.stab,
                                              "\\t" )
                                 j = i + stub:len()
                                 j = i + stub:len()
                                 s = string.format( "%s%s%s",
                                 s = string.format( "%s%s%s",
Zeile 145: Zeile 590:
                     else
                     else
                         r = "QouteEnd"
                         r = "QouteEnd"
                         s = s:sub( i, i + JSONutil.more )
                         s = mw.text.trim( s:sub( i ) )
                        s = mw.ustring.sub( s, 1, JSONutil.more )
                         i = false
                         i = false
                     end
                     end
Zeile 151: Zeile 597:
             else
             else
                 r = "Qoute"
                 r = "Qoute"
                 s = s:sub( i, i + JSONutil.more )
                 s = mw.text.trim( s:sub( i ) )
                s = mw.ustring.sub( s, 1, JSONutil.more )
                 i = false
                 i = false
             end
             end
         else
         elseif not r then
             stub = s:sub( j )
             stub = s:sub( j )
             if stub:find( '[^"]*,%s*[%]}]' ) then
             if stub:find( '[^"]*,%s*[%]}]' ) then
                 r = "CommaEnd"
                 r = "CommaEnd"
                 s = stub:sub( 1, JSONutil.more )
                 s = mw.text.trim( stub )
                s = mw.ustring.sub( s, 1, JSONutil.more )
             else
             else
                 framework( stub )
                 framework( stub )
Zeile 186: Zeile 634:
         if j > 1 then
         if j > 1 then
             s =  string.format( "%d %s", j, s )
             s =  string.format( "%d %s", j, s )
        end
    elseif not ( r or last ) then
        stub = suffix or apply or ""
        j    = stub:find( "/", 1, true )
        if j then
            i, stub = pcall( free, stub, j, free )
        else
            i = true
        end
        stub = mw.text.trim( stub )
        if i then
            if stub:sub( - 1 ) ~= "}" then
                r = "Trailing"
                s = stub:match( "%}%s*(%S[^%}]*)$" )
                if s then
                    s = mw.ustring.sub( s, 1, JSONutil.more )
                else
                    s = mw.ustring.sub( stub,  - JSONutil.more )
                end
            end
        else
            r = "CommentEnd"
            s = mw.ustring.sub( stub, 1, JSONutil.more )
         end
         end
     end
     end
     if r and s then
     if r and s then
         s = mw.text.encode( s:gsub( sep,  " " ) ):gsub( "|", "&#124;" )
         s = s:gsub( JSONutil.Encoder.sep,  " " )
        s = mw.text.encode( s ):gsub( "|", "&#124;" )
     end
     end
     return r, s
     return r, s
Zeile 196: Zeile 668:




JSONutil.fault = function ( alert, add, alien )
JSONutil.fault = function ( alert, add, adapt )
     -- Retrieve formatted message
     -- Retrieve formatted message
     -- Parameter:
     -- Parameter:
     --    alert  -- string, with error keyword, or other text
     --    alert  -- string, with error keyword, or other text
     --    add    -- string, or nil or false, with context
     --    add    -- string|nil|false, with context
     --    alien -- string, or nil or false, with language codes
     --    adapt -- function|string|table|nil|false, for I18N
     -- Returns string, with HTML span
     -- Returns string, with HTML span
     local e = mw.html.create( "span" )
     local e = mw.html.create( "span" )
                     :addClass( "error" )
                     :addClass( "error" )
    local f = function ( all, at )
                  local slang
                  if at == "*" then
                      slang = mw.language.getContentLanguage():getCode()
                  elseif at then
                      slang = at
                  end
                  return all[ slang ]
              end -- f()
     local s = alert
     local s = alert
     if type( s ) == "string" then
     if type( s ) == "string" then
Zeile 235: Zeile 698:
                                 e = e[ 2 ]
                                 e = e[ 2 ]
                                 if type( e ) == "table" then
                                 if type( e ) == "table" then
                                     local q
                                     local q = type( adapt )
                                     if type( alien ) == "string" then
                                     if q == "function" then
                                      t = mw.text.split( alien, "%s+" )
                                        s = adapt( e, s )
                                        t = false
                                    elseif q == "string" then
                                        t = mw.text.split( adapt, "%s+" )
                                    elseif q == "table" then
                                        t = adapt
                                     else
                                     else
                                      t = { }
                                        t = { }
                                    end
                                    if t then
                                        table.insert( t, Fallback() )
                                        table.insert( t, "en" )
                                        for k = 1, #t do
                                            q = e[ t[ k ] ]
                                            if type( q ) == "string" then
                                                s = q
                                                break  -- for k
                                            end
                                        end  -- for k
                                     end
                                     end
                                    table.insert( t, "*" )
                                    table.insert( t, "en" )
                                    for k = 1, #t do
                                        q = f( e, t[ k ] )
                                        if type( q ) == "string" then
                                            s = q
                                            break  -- for k
                                        end
                                    end  -- for k
                                 else
                                 else
                                     s = "JSONutil.fault I18N bad #" ..
                                     s = "JSONutil.fault I18N bad #" ..
Zeile 264: Zeile 734:
                 end
                 end
             else
             else
                 s = "INVALID JSONutil.fault " .. t
                 s = "INVALID JSONutil.fault commons:Data: " .. type( t )
             end
             end
         end
         end
Zeile 279: Zeile 749:




JSONutil.fetch = function ( apply, always, alien )
JSONutil.fetch = function ( apply, always, adapt )
     -- Retrieve JSON data, or error message
     -- Retrieve JSON data, or error message
     -- Parameter:
     -- Parameter:
     --    apply  -- string, with presumable JSON text
     --    apply  -- string, with presumable JSON text
     --    always  -- true, if apply is expected to need preprocessing
     --    always  -- true, if apply is expected to need preprocessing
     --    alien  -- string, or nil or false, with language codes
     --    adapt  -- function|string|table|nil|false, for I18N
     -- Returns table, with data, or string, with error as HTML span
     -- Returns table, with data, or string, with error as HTML span
     local lucky, r
     local lucky, r
Zeile 293: Zeile 763:
         lucky, r = JSONutil.fair( apply )
         lucky, r = JSONutil.fair( apply )
         if lucky then
         if lucky then
             r = JSONutil.fault( lucky, r, alien )
             r = JSONutil.fault( lucky, r, adapt )
         else
         else
             lucky, r = pcall( mw.text.jsonDecode, r )
             lucky, r = pcall( mw.text.jsonDecode, r )
             if not lucky then
             if not lucky then
                 r = JSONutil.fault( r, false, alien )
                 r = JSONutil.fault( r, false, adapt )
             end
             end
         end
         end
Zeile 303: Zeile 773:
     return r
     return r
end -- JSONutil.fetch()
end -- JSONutil.fetch()
Failsafe.failsafe = function ( atleast )
    -- Retrieve versioning and check for compliance
    -- Precondition:
    --    atleast  -- string, with required version
    --                        or "wikidata" or "~" or "@" 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()




Zeile 324: Zeile 852:
         end
         end
     end
     end
     return JSONutil.failsafe( since )  or  ""
     return Failsafe.failsafe( since )  or  ""
end -- p.failsafe()
end -- p.failsafe
 
 
p.encodeArray = function ( frame )
    return JSONutil.Encoder.Array( frame:getParent().args,
                                  frame.args.type,
                                  frame.args.error == "1" )
end -- p.encodeArray
 
 
p.encodeComponent = function ( frame )
    return JSONutil.Encoder.Component( frame.args.sign,
                                      frame.args.value,
                                      frame.args.type,
                                      flip( frame ),
                                      frame.args.error == "1" )
end -- p.encodeComponent
 
 
p.encodeHash = function ( frame )
    return JSONutil.Encoder.Hash( frame:getParent().args,
                                  frame.args )
end -- p.encodeHash
 
 
p.encodeI18N = function ( frame )
    return JSONutil.Encoder.I18N( frame:getParent().args,
                                  flip( frame ) )
end -- p.encodeI18N
 
 
p.encodeObject = function ( frame )
    return JSONutil.Encoder.object( flat( frame.args[ 1 ] ),
                                    flip( frame ) )
end -- p.encodeObject
 
 
p.encodePolyglott = function ( frame )
    return JSONutil.Encoder.Polyglott( flat( frame.args[ 1 ] ),
                                      flip( frame ) )
end -- p.encodePolyglott
 


p.JSONutil = function ()
p.JSONutil = function ()

Aktuelle Version vom 25. Januar 2023, 22:12 Uhr

Vorlagenprogrammierung Diskussionen Lua Unterseiten
Modul Deutsch English

Modul: Dokumentation

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


local JSONutil = { suite  = "JSONutil",
                   serial = "2020-11-08",
                   item   = 63869449 }
--[=[
preprocess or generate JSON data
]=]



local Failsafe = JSONutil
JSONutil.Encoder  = { stab   = string.char( 9 ),
                      sep    = string.char( 10 ),
                      scream = "@error@JSONencoder@" }
JSONutil.more = 50    -- length of trailing context



local Fallback = function ()
    -- Retrieve current default language code
    --     Returns  string
    return mw.language.getContentLanguage():getCode()
                                           :lower()
end -- Fallback()



local flat = function ( adjust )
    -- Clean template argument string
    -- Parameter:
    --     adjust  -- string, or not
    -- Returns:
    --     string
    local r
    if adjust then
        r = mw.text.trim( mw.text.unstripNoWiki( adjust ) )
    else
        r = ""
    end
    return r
end -- flat()



local flip = function ( frame )
    -- Retrieve template argument indent
    -- Parameter:
    --     frame  -- object
    -- Returns:
    --     number, of indentation level, or not
    local r
    if frame.args.indent  and  frame.args.indent:match( "^%d+$" ) then
        r = tonumber( frame.args.indent )
    end
    return r
end -- flip()



JSONutil.Encoder.Array = function ( apply, adapt, alert )
    -- Convert table to JSON Array
    -- Parameter:
    --     apply  -- table, with sequence of raw elements, or
    --               string, with formatted Array, or empty
    --     adapt  -- string, with requested type, or not
    --     alert  -- true, if non-numeric elements shall trigger errors
    -- Returns:
    --     string, with JSON Array
    local r = type( apply )
    if r == "string" then
        r = mw.text.trim( apply )
        if r == "" then
            r = "[]"
        elseif r:byte( 1, 1 ) ~= 0x5B  or
               r:byte( -1, -1 ) ~= 0x5D then
            r = false
        end
    elseif r == "table" then
        local n = 0
        local strange
        for k, v in pairs( apply ) do
            if type( k ) == "number" then
                if k > n then
                    n = k
                end
            elseif alert then
                if strange then
                    strange = strange .. " "
                else
                    strange = ""
                end
                strange = strange .. tostring( k )
            end
        end -- for k, v
        if strange then
            r = string.format( "{ \"%s\": \"%s\" }",
                               JSONutil.Encoder.scream,
                               JSONutil.Encoder.string( strange ) )
        elseif n > 0 then
            local sep   = ""
            local scope = adapt or "string"
            local s
            if type( JSONutil.Encoder[ scope ] ) ~= "function" then
                scope = "string"
            end
            r = " ]"
            for i = n, 1, -1 do
                s = JSONutil.Encoder[ scope ]( apply[ i ] )
                r = string.format( "%s%s%s", s, sep, r )
                sep = ",\n  "
            end -- for i = n, 1, -1
            r = "[ " .. r
        else
            r = "[]"
        end
    else
        r = false
    end
    if not r then
        r = string.format( "[ \"%s * %s\" ]",
                           JSONutil.Encoder.scream,
                           "Bad Array" )
    end
    return r
end -- JSONutil.Encoder.Array()



JSONutil.Encoder.boolean = function ( apply )
    -- Convert string to JSON boolean
    -- Parameter:
    --     apply  -- string, with value
    -- Returns:
    --     boolean as string
    local r = mw.text.trim( apply )
    if r == ""  or  r == "null"  or  r == "false"
       or  r == "0"  or  r == "-" then
        r = "false"
    else
        r = "true"
    end
    return r
end -- JSONutil.Encoder.boolean()



JSONutil.Encoder.Component = function ( access, apply,
                                        adapt, align, alert )
    -- Create single entry for mapping object
    -- Parameter:
    --     access  -- string, with component name
    --     apply   -- component value
    --     adapt   -- string, with value type, or not
    --     align   -- number, of indentation level, or not
    --     alert   --
    -- Returns:
    --     string, with JSON fragment, and comma
    local v     = apply
    local types = adapt
    local indent, liner, scope, sep, sign
    if type( access ) == "string" then
        sign = mw.text.trim( access )
        if sign == "" then
            sign = false
        end
    end
    if type( types ) == "string" then
        types = mw.text.split( mw.text.trim( types ),  "%s+" )
    end
    if type( types ) ~= "table" then
        types = { }
        table.insert( types, "string" )
    end
    if #types == 1 then
        scope = types[ 1 ]
    else
        for i = 1, #types do
            if types[ i ] == "boolean" then
                if v == "1"  or  v == 1  or  v == true then
                    v = "true"
                    scope = "boolean"
                elseif v == "0"  or  v == 0  or  v == false then
                    v = "false"
                    scope = "boolean"
                end
                if scope then
                    types = { }
                    break    -- for i
                else
                    table.remove( types, i )
                end
            end
        end   -- for i
        for i = 1, #types do
            if types[ i ] == "number" then
                if tonumber( v ) then
                    v     = tostring( v )
                    scope = "number"
                    types = { }
                    break    -- for i
                else
                    table.remove( types, i )
                end
            end
        end    -- for i
    end
    scope = scope or "string"
    if type( JSONutil.Encoder[ scope ] ) ~= "function" then
        scope = "string"
    elseif scope == "I18N" then
        scope = "Polyglott"
    end
    if scope == "string" then
        v = v or ""
    end
    if type( align ) == "number"  and  align > 0 then
        indent = math.floor( align )
        if indent == 0 then
            indent = false
        end
    end
    if scope == "object"  or  not sign then
        liner = true
    elseif scope == "string" then
        local k = mw.ustring.len( sign ) + mw.ustring.len( v )
        if k > 60 then
            liner = true
        end
    end
    if liner then
        if indent then
            sep = "\n" .. string.rep( "  ", indent )
        else
            sep = "\n"
        end
    else
        sep = " "
    end
    if indent then
        indent = indent + 1
    end
    return  string.format( " \"%s\":%s%s,\n",
                           sign or "???",
                           sep,
                           JSONutil.Encoder[ scope ]( v, indent ) )
end -- JSONutil.Encoder.Component()



JSONutil.Encoder.Hash = function ( apply, adapt, alert )
    -- Create entries for mapping object
    -- Parameter:
    --     apply  -- table, with element value assignments
    --     adapt  -- table, with value types assignment, or not
    -- Returns:
    --     string, with JSON fragment, and comma
    local r = ""
    local s
    for k, v in pairs( apply ) do
        if type( adapt ) == "table" then
            s = adapt[ k ]
        end
        r = r .. JSONutil.Encoder.Component( tostring( k ), v, s )
    end -- for k, v
    return
end -- JSONutil.Encoder.Hash()



JSONutil.Encoder.I18N = function ( apply, align )
    -- Convert multilingual string table to JSON
    -- Parameter:
    --     apply  -- table, with mapping object
    --     align  -- number, of indentation level, or not
    -- Returns:
    --     string, with JSON object
    local r = type( apply )
    if r == "table" then
        local strange
        local fault = function ( a )
                  if strange then
                      strange = strange .. " *\n "
                  else
                      strange = ""
                  end
                  strange = strange .. a
              end
        local got, sep, indent
        for k, v in pairs( apply ) do
            if type( k ) == "string" then
                k = mw.text.trim( k )
                if type( v ) == "string" then
                    v = mw.text.trim( v )
                    if v == "" then
                        fault( string.format( "%s %s=",
                                              "Empty text", k ) )
                    end
                    if not ( k:match( "%l%l%l?" )  or
                             k:match( "%l%l%l?-%u%u" )  or
                             k:match( "%l%l%l?-%u%l%l%l+" ) ) then
                        fault( string.format( "%s %s=",
                                              "Strange language code",
                                              k ) )
                    end
                else
                    v = tostring( v )
                    fault( string.format( "%s %s=%s",
                                          "Bad type for text",
                                          k,
                                          type( v ) ) )
                end
                got = got  or  { }
                got[ k ] = v
            else
                fault( string.format( "%s %s: %s",
                                      "Bad language code type",
                                      type( k ),
                                      tostring( k ) ) )
            end
        end -- for k, v
        if not got then
            fault( "No language codes" )
            got = { }
        end
        if strange then
            got[ JSONutil.Encoder.scream ] = strange
        end
        r = false
        if type( align ) == "number"  and  align > 0 then
            indent = math.floor( align ) 
        else
            indent = 0
        end
        sep = string.rep( "  ",  indent + 1 ) 
        for k, v in pairs( got ) do
            if r then
                r = r .. ",\n"
            else
                r = ""
            end
            r = string.format( "%s  %s%s: %s",
                               r,
                               sep,
                               JSONutil.Encoder.string( k ),
                               JSONutil.Encoder.string( v ) )
        end -- for k, v
        r = string.format( "{\n%s\n%s}", r, sep )
    elseif r == "string" then
        r = JSONutil.Encoder.string( apply )
    else
        r = string.format( "{ \"%s\": \"%s: %s\" }",
                           JSONutil.Encoder.scream,
                           "Bad Lua type",
                           r )
    end
    return r
end -- JSONutil.Encoder.I18N()



JSONutil.Encoder.number = function ( apply )
    -- Convert string to JSON number
    -- Parameter:
    --     apply  -- string, with presumable number
    -- Returns:
    --     number, or "NaN"
    local s = mw.text.trim( apply )
    JSONutil.Encoder.minus = JSONutil.Encoder.minus  or
                             mw.ustring.char( 0x2212 )
    s = s:gsub( JSONutil.Encoder.minus, "-" )
    return tonumber( s:lower() )  or  "NaN"
end -- JSONutil.Encoder.number()



JSONutil.Encoder.object = function ( apply, align )
    -- Create mapping object
    -- Parameter:
    --     apply  -- string, with components, may end with comma
    --     align  -- number, of indentation level, or not
    -- Returns:
    --     string, with JSON fragment
    local story = mw.text.trim( apply )
    local start = ""
    if story:sub( -1 ) == "," then
        story = story:sub( 1, -2 )
    end
    if type( align ) == "number"  and  align > 0 then
        local indent = math.floor( align )
        if indent > 0 then
            start = string.rep( "  ", indent )
        end
    end
    return  string.format( "%s{ %s\n%s}", start, story, start )
end -- JSONutil.Encoder.object()



JSONutil.Encoder.Polyglott = function ( apply, align )
    -- Convert string or multilingual string table to JSON
    -- Parameter:
    --     apply  -- string, with string or object
    --     align  -- number, of indentation level, or not
    -- Returns:
    --     string
    local r = type( apply )
    if r == "string" then
        r = mw.text.trim( apply )
        if not r:match( "^{%s*\"" )  or
           not r:match( "\"%s*}$" ) then
            r = JSONutil.Encoder.string( r )
        end
    else
        r = string.format( "{ \"%s\": \"%s: %s\" }",
                           JSONutil.Encoder.scream,
                           "Bad Lua type",
                           r )
    end
    return r
end -- JSONutil.Encoder.Polyglott()



JSONutil.Encoder.string = function ( apply )
    -- Convert plain string to strict JSON string
    -- Parameter:
    --     apply  -- string, with plain string
    -- Returns:
    --     string, with quoted trimmed JSON string
    return  string.format( "\"%s\"",
                           mw.text.trim( apply )
                                  :gsub( "\\",  "\\\\" )
                                  :gsub( "\"",  "\\\"" )
                                  :gsub( JSONutil.Encoder.sep,  "\\n" )
                                  :gsub( JSONutil.Encoder.stab, "\\t" ) )
end -- JSONutil.Encoder.string()



JSONutil.fair = function ( apply )
    -- Reduce enhanced JSON data to strict JSON
    -- Parameter:
    --     apply  -- string, with enhanced JSON
    -- Returns:
    --     1    -- string|nil|false, with error keyword
    --     2    -- string, with JSON or context
    local m   = 0
    local n   = 0
    local s   = mw.text.trim( apply )
    local i, j, last, r, scan, sep0, sep1, start, stub, suffix
    local framework = function ( a )
                              -- syntax analysis outside strings
                              local k = 1
                              local c
                              while k do
                                  k = a:find( "[{%[%]}]", k )
                                  if k then
                                      c = a:byte( k, k )
                                      if c == 0x7B then    -- {
                                          m = m + 1
                                      elseif c == 0x7D then    -- }
                                          m = m - 1
                                      elseif c == 0x5B then    -- [
                                          n = n + 1
                                      else    -- ]
                                          n = n - 1
                                      end
                                      k = k + 1
                                  end
                              end   -- while k
                      end    -- framework()
    local free = function ( a, at, f )
                     -- Throws: error if /* is not matched by */
                     local s = a
                     local i = s:find( "//", at, true )
                     local k = s:find( "/*", at, true )
                     if i or k then
                         local m = s:find( sep0, at )
                         if i   and   ( not m  or  i < m ) then
                             k = s:find( "\n",  i + 2,  true )
                             if k then
                                 if i == 1 then
                                     s = s:sub( k + 1 )
                                 else
                                     s = s:sub( 1,  i - 1 )   ..
                                         s:sub( k + 1 )
                                 end
                             elseif i > 1 then
                                 s = s:sub( 1,  i - 1 )
                             else
                                 s = ""
                             end
                         elseif k   and   ( not m  or  k < m ) then
                             i = s:find( "*/",  k + 2,  true )
                             if i then
                                 if k == 1 then
                                     s = s:sub( i + 2 )
                                 else
                                     s = s:sub( 1,  k - 1 )   ..
                                         s:sub( i + 2 )
                                 end
                             else
                                 error( s:sub( k + 2 ), 0 )
                             end
                             i = k
                         else
                             i = false
                         end
                         if i then
                             s = mw.text.trim( s )
                             if s:find( "/", 1, true ) then
                                 s = f( s, i, f )
                             end
                         end
                     end
                     return s
                 end    -- free()
    if s:sub( 1, 1 ) == '{' then
        s    = s:gsub( string.char( 13, 10 ),  JSONutil.Encoder.sep )
                :gsub( string.char( 13 ),  JSONutil.Encoder.sep )
        stub = s:gsub( JSONutil.Encoder.sep, "" )
                :gsub( JSONutil.Encoder.stab, "" )
        scan = string.char( 0x5B, 0x01, 0x2D, 0x1F, 0x5D )    -- [ \-\ ]
        j    = stub:find( scan )
        if j then
            r = "ControlChar"
            s = mw.text.trim( s:sub( j + 1 ) )
            s = mw.ustring.sub( s, 1, JSONutil.more )
        else
            i    = true
            j    = 1
            last = ( stub:sub( -1 ) == "}" )
            sep0 = string.char( 0x5B, 0x22, 0x27, 0x5D )    -- [ " ' ]
            sep1 = string.char( 0x5B, 0x5C, 0x22, 0x5D )    -- [ \ " ]
        end
    else
        r = "Bracket0"
        s = mw.ustring.sub( s, 1, JSONutil.more )
    end
    while i do
        i, s = pcall( free, s, j, free )
        if i then
            i = s:find( sep0, j )
        else
            r = "CommentEnd"
            s = mw.text.trim( s )
            s = mw.ustring.sub( s, 1, JSONutil.more )
        end
        if i then
            if j == 1 then
                framework( s:sub( 1, i - 1 ) )
            end
            if s:sub( i, i ) == '"' then
                stub = s:sub( j,  i - 1 )
                if stub:find( '[^"]*,%s*[%]}]' ) then
                    r = "CommaEnd"
                    s = mw.text.trim( stub )
                    s = mw.ustring.sub( s, 1, JSONutil.more )
                    i = false
                    j = false
                else
                    if j > 1 then
                        framework( stub )
                    end
                    i = i + 1
                    j = i
                end
                while j do
                    j = s:find( sep1, j )
                    if j then
                        if s:sub( j, j ) == '"' then
                            start  = s:sub( 1,  i - 1 )
                            suffix = s:sub( j )
                            if j > i then
                                stub = s:sub( i,  j - 1 )
                                        :gsub( JSONutil.Encoder.sep,
                                               "\\n" )
                                        :gsub( JSONutil.Encoder.stab,
                                               "\\t" )
                                j = i + stub:len()
                                s = string.format( "%s%s%s",
                                                   start, stub, suffix )
                            else
                                s = start .. suffix
                            end
                            j = j + 1
                            break   -- while j
                        else
                            j = j + 2
                        end
                    else
                        r = "QouteEnd"
                        s = mw.text.trim( s:sub( i ) )
                        s = mw.ustring.sub( s, 1, JSONutil.more )
                        i = false
                    end
                end   -- while j
            else
                r = "Qoute"
                s = mw.text.trim( s:sub( i ) )
                s = mw.ustring.sub( s, 1, JSONutil.more )
                i = false
            end
        elseif not r then
            stub = s:sub( j )
            if stub:find( '[^"]*,%s*[%]}]' ) then
                r = "CommaEnd"
                s = mw.text.trim( stub )
                s = mw.ustring.sub( s, 1, JSONutil.more )
            else
                framework( stub )
            end
        end
    end   -- while i
    if not r   and   ( m ~= 0  or  n ~= 0 ) then
        if m ~= 0 then
            s = "}"
            if m > 0 then
                r = "BracketCloseLack"
                j = m
            elseif m < 0 then
                r = "BracketClosePlus"
                j = -m
            end
        else
            s = "]"
            if n > 0 then
                r = "BracketCloseLack"
                j = n
            else
                r = "BracketClosePlus"
                j = -n
            end
        end
        if j > 1 then
            s =  string.format( "%d %s", j, s )
        end
    elseif not ( r or last ) then
        stub = suffix or apply or ""
        j    = stub:find( "/", 1, true )
        if j then
            i, stub = pcall( free, stub, j, free )
        else
            i = true
        end
        stub = mw.text.trim( stub )
        if i then
            if stub:sub( - 1 ) ~= "}" then
                r = "Trailing"
                s = stub:match( "%}%s*(%S[^%}]*)$" )
                if s then
                    s = mw.ustring.sub( s, 1, JSONutil.more )
                else
                    s = mw.ustring.sub( stub,  - JSONutil.more )
                end
            end
        else
            r = "CommentEnd"
            s = mw.ustring.sub( stub, 1, JSONutil.more )
        end
    end
    if r and s then
        s = s:gsub( JSONutil.Encoder.sep,  " " )
        s = mw.text.encode( s ):gsub( "|", "&#124;" )
    end
    return r, s
end -- JSONutil.fair()



JSONutil.fault = function ( alert, add, adapt )
    -- Retrieve formatted message
    -- Parameter:
    --     alert  -- string, with error keyword, or other text
    --     add    -- string|nil|false, with context
    --     adapt  -- function|string|table|nil|false, for I18N
    -- Returns string, with HTML span
    local e = mw.html.create( "span" )
                     :addClass( "error" )
    local s = alert
    if type( s ) == "string" then
        s = mw.text.trim( s )
        if s == "" then
            s = "EMPTY JSONutil.fault key"
        end
        if not s:find( " ", 1, true ) then
            local storage = string.format( "I18n/Module:%s.tab",
                                           JSONutil.suite )
            local lucky, t = pcall( mw.ext.data.get, storage, "_" )
            if type( t ) == "table" then
                t = t.data
                if type( t ) == "table" then
                    local e
                    s = "err_" .. s
                    for i = 1, #t do
                        e = t[ i ]
                        if type( e ) == "table" then
                            if e[ 1 ] == s then
                                e = e[ 2 ]
                                if type( e ) == "table" then
                                    local q = type( adapt )
                                    if q == "function" then
                                        s = adapt( e, s )
                                        t = false
                                    elseif q == "string" then
                                        t = mw.text.split( adapt, "%s+" )
                                    elseif q == "table" then
                                        t = adapt
                                    else
                                        t = { }
                                    end
                                    if t then
                                        table.insert( t, Fallback() )
                                        table.insert( t, "en" )
                                        for k = 1, #t do
                                            q = e[ t[ k ] ]
                                            if type( q ) == "string" then
                                                s = q
                                                break   -- for k
                                            end
                                        end   -- for k
                                    end
                                else
                                    s = "JSONutil.fault I18N bad #" ..
                                        tostring( i )
                                end
                                break   -- for i
                            end
                        else
                            break   -- for i
                        end
                    end   -- for i
                else
                    s = "INVALID JSONutil.fault I18N corrupted"
                end
            else
                s = "INVALID JSONutil.fault commons:Data: " .. type( t )
            end
        end
    else
        s = "INVALID JSONutil.fault key: " .. tostring( s )
    end
    if type( add ) == "string" then
        s = string.format( "%s &#8211; %s", s, add )
    end
    e:wikitext( s )
    return tostring( e )
end -- JSONutil.fault()



JSONutil.fetch = function ( apply, always, adapt )
    -- Retrieve JSON data, or error message
    -- Parameter:
    --     apply   -- string, with presumable JSON text
    --     always  -- true, if apply is expected to need preprocessing
    --     adapt   -- function|string|table|nil|false, for I18N
    -- Returns table, with data, or string, with error as HTML span
    local lucky, r
    if not always then
        lucky, r = pcall( mw.text.jsonDecode, apply )
    end
    if not lucky then
        lucky, r = JSONutil.fair( apply )
        if lucky then
            r = JSONutil.fault( lucky, r, adapt )
        else
            lucky, r = pcall( mw.text.jsonDecode, r )
            if not lucky then
                r = JSONutil.fault( r, false, adapt )
            end
        end
    end
    return r
end -- JSONutil.fetch()



Failsafe.failsafe = function ( atleast )
    -- Retrieve versioning and check for compliance
    -- Precondition:
    --     atleast  -- string, with required version
    --                         or "wikidata" or "~" or "@" 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 = { }

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.encodeArray = function ( frame )
    return JSONutil.Encoder.Array( frame:getParent().args,
                                   frame.args.type,
                                   frame.args.error == "1" )
end -- p.encodeArray


p.encodeComponent = function ( frame )
    return JSONutil.Encoder.Component( frame.args.sign,
                                       frame.args.value,
                                       frame.args.type,
                                       flip( frame ),
                                       frame.args.error == "1" )
end -- p.encodeComponent


p.encodeHash = function ( frame )
    return JSONutil.Encoder.Hash( frame:getParent().args,
                                  frame.args )
end -- p.encodeHash


p.encodeI18N = function ( frame )
    return JSONutil.Encoder.I18N( frame:getParent().args,
                                  flip( frame ) )
end -- p.encodeI18N


p.encodeObject = function ( frame )
    return JSONutil.Encoder.object( flat( frame.args[ 1 ] ),
                                    flip( frame ) )
end -- p.encodeObject


p.encodePolyglott = function ( frame )
    return JSONutil.Encoder.Polyglott( flat( frame.args[ 1 ] ),
                                       flip( frame ) )
end -- p.encodePolyglott


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

return p