Modul:JSONutil
Dokumentation für das Modul JSONutil[Ansicht] [Bearbeiten] [Versionsgeschichte] [ ]
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