Modul:JSONutil: Unterschied zwischen den Versionen
Keine Bearbeitungszusammenfassung Markierungen: Manuelle Zurücksetzung Zurückgesetzt |
Keine Bearbeitungszusammenfassung Markierung: Manuelle Zurücksetzung |
||
| (Eine dazwischenliegende Version desselben Benutzers wird nicht angezeigt) | |||
| Zeile 1: | Zeile 1: | ||
local JSONutil = { suite = "JSONutil", | local JSONutil = { suite = "JSONutil", | ||
serial = " | 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 | ||
| Zeile 21: | Zeile 25: | ||
local flat = function ( adjust ) | |||
-- | -- Clean template argument string | ||
-- | -- Parameter: | ||
-- | -- adjust -- string, or not | ||
-- | -- Returns: | ||
-- | -- string | ||
-- Returns | 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 | 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 | 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 | r = string.format( "[ \"%s * %s\" ]", | ||
r = JSONutil. | 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 | else | ||
r = | 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. | 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 68: | Zeile 447: | ||
local n = 0 | local n = 0 | ||
local s = mw.text.trim( apply ) | local s = mw.text.trim( apply ) | ||
local i, j, last, r, scan, sep0, sep1, start, stub, suffix | |||
local i, j, last, r, scan, sep0, sep1 | |||
local framework = function ( a ) | local framework = function ( a ) | ||
-- syntax analysis outside strings | -- syntax analysis outside strings | ||
| Zeile 90: | 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 | ||
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 114: | Zeile 538: | ||
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 142: | Zeile 573: | ||
if j > i then | if j > i then | ||
stub = s:sub( i, j - 1 ) | stub = s:sub( i, j - 1 ) | ||
:gsub( sep, | :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 168: | Zeile 601: | ||
i = false | i = false | ||
end | end | ||
elseif not r then | |||
stub = s:sub( j ) | stub = s:sub( j ) | ||
if stub:find( '[^"]*,%s*[%]}]' ) then | if stub:find( '[^"]*,%s*[%]}]' ) then | ||
| Zeile 203: | Zeile 636: | ||
end | end | ||
elseif not ( r or last ) then | elseif not ( r or last ) then | ||
stub = suffix or apply or "" | stub = suffix or apply or "" | ||
s | 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 | else | ||
s = mw.ustring.sub( stub, | 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 = | s = s:gsub( JSONutil.Encoder.sep, " " ) | ||
s = mw.text.encode( s ):gsub( "|", "|" ) | |||
end | end | ||
return r, s | return r, s | ||
| Zeile 325: | 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 346: | Zeile 852: | ||
end | end | ||
end | end | ||
return | 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, 21:12 Uhr
Modul:Vorlage:LuaModuleDoc:142: attempt to index field 'wikibase' (a nil value)
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