Dokumentation für das Modul JSONutil[Ansicht] [Bearbeiten] [Versionsgeschichte] [Aktualisieren]

Preprocess or generate JSON data

Verwendung in anderen Modulen

Dieses Modul ist notwendig für die Ausführung folgender Module. Bei Anpassungen sollte die Funktionstüchtigkeit der folgenden Module geprüft werden. Benutze dazu auch diese Tracking-Kategorie um Fehler zu finden, die sich dann auf Artikel auswirken:

Hinweise
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