Modul:JSONutil: Unterschied zwischen den Versionen

keine Bearbeitungszusammenfassung
(2019-07-18)
Keine Bearbeitungszusammenfassung
Markierung: Manuelle Zurücksetzung
 
(8 dazwischenliegende Versionen von 3 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
local JSONutil = { suite  = "JSONutil",
local JSONutil = { suite  = "JSONutil",
                   serial = "2019-07-18",
                   serial = "2020-11-08",
                   item  = 63869449 }
                   item  = 63869449 }
--[=[
--[=[
preprocess JSON data
preprocess or generate JSON data
]=]
]=]
local Failsafe = JSONutil






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


Zeile 19: Zeile 22:
                                           :lower()
                                           :lower()
end -- Fallback()
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()




Zeile 32: Zeile 447:
     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, last, 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 102: Zeile 516:
                 end    -- free()
                 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 )
Zeile 159: 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 244: Zeile 660:
     end
     end
     if r and s then
     if r and s then
         s = mw.text.encode( s:gsub( sep,  " " ) ):gsub( "|", "|" )
         s = s:gsub( JSONutil.Encoder.sep,  " " )
        s = mw.text.encode( s ):gsub( "|", "|" )
     end
     end
     return r, s
     return r, s
Zeile 362: Zeile 779:
     -- Retrieve versioning and check for compliance
     -- Retrieve versioning and check for compliance
     -- Precondition:
     -- Precondition:
     --    atleast  -- string, with required version or "wikidata" or "~"
     --    atleast  -- string, with required version
    --                or false
    --                        or "wikidata" or "~" or "@" or false
     -- Postcondition:
     -- Postcondition:
     --    Returns  string  -- with queried version, also if problem
     --    Returns  string  -- with queried version/item, also if problem
     --              false  -- if appropriate
     --              false  -- if appropriate
     local last  = ( atleast == "~" )
     -- 2020-08-17
     local since = atleast
     local since = atleast
    local last    = ( since == "~" )
    local linked  = ( since == "@" )
    local link    = ( since == "item" )
     local r
     local r
     if last  or  since == "wikidata" then
     if last or  link  or  linked or  since == "wikidata" then
         local item = Failsafe.item
         local item = Failsafe.item
         since = false
         since = false
         if type( item ) == "number"  and  item > 0 then
         if type( item ) == "number"  and  item > 0 then
             local entity = mw.wikibase.getEntity( string.format( "Q%d",
             local suited = string.format( "Q%d", item )
                                                                item ) )
            if link then
            if type( entity ) == "table" then
                r = suited
                local vsn = entity:formatPropertyValues( "P348" )
            else
                if type( vsn ) == "table"  and
                local entity = mw.wikibase.getEntity( suited )
                  type( vsn.value ) == "string"  and
                if type( entity ) == "table" then
                  vsn.value ~= "" then
                    local seek = Failsafe.serialProperty or "P348"
                    if last  and  vsn.value == Failsafe.serial then
                    local vsn = entity:formatPropertyValues( seek )
                         r = false
                    if type( vsn ) == "table"  and
                    else
                      type( vsn.value ) == "string"  and
                         r = vsn.value
                      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
Zeile 421: Zeile 853:
     end
     end
     return Failsafe.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 ()