
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:
- TemplateData
- Modul benötigt das Modul JSONutil – Wartungskategorie, in der nochmals alle Module gelistet sind, die von diesem Modul abhängig sind.|}}
Hinweise
- Die obige Dokumentation wurde aus der Seite Modul:JSONutil/Doku eingefügt. (bearbeiten | Versionsgeschichte) Die Kategorien für dieses Modul sollten in der Dokumentation eingetragen werden. Die Interwiki-Links sollten auf Wikidata eingepflegt werden.
- Liste der Unterseiten
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( "|", "|" ) 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 – %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