Modul:JSONutil: Unterschied zwischen den Versionen
te>PerfektesChaos (2019-05-29) |
Admin (Diskussion | Beiträge) K (9 Versionen importiert) |
||
(6 dazwischenliegende Versionen von einem anderen Benutzer werden nicht angezeigt) | |||
Zeile 1: | Zeile 1: | ||
local JSONutil = { suite = "JSONutil", | local JSONutil = { suite = "JSONutil", | ||
− | serial = "2019- | + | serial = "2019-07-18", |
item = 63869449 } | item = 63869449 } | ||
--[=[ | --[=[ | ||
preprocess JSON data | preprocess JSON data | ||
]=] | ]=] | ||
+ | local Failsafe = JSONutil | ||
Zeile 12: | Zeile 13: | ||
− | + | local Fallback = function () | |
− | -- Retrieve | + | -- Retrieve current default language code |
− | + | -- Returns string | |
− | + | return mw.language.getContentLanguage():getCode() | |
− | + | :lower() | |
− | + | end -- Fallback() | |
− | -- Returns string | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | end -- | ||
Zeile 54: | Zeile 27: | ||
-- apply -- string, with enhanced JSON | -- apply -- string, with enhanced JSON | ||
-- Returns: | -- Returns: | ||
− | -- 1 -- string | + | -- 1 -- string|nil|false, with error keyword |
-- 2 -- string, with JSON or context | -- 2 -- string, with JSON or context | ||
local m = 0 | local m = 0 | ||
Zeile 60: | Zeile 33: | ||
local s = mw.text.trim( apply ) | local s = mw.text.trim( apply ) | ||
local sep = string.char( 10 ) | local sep = string.char( 10 ) | ||
− | local i, j, r, scan, sep0, sep1, stab, start, stub, suffix | + | local i, j, last, r, scan, sep0, sep1, stab, start, stub, suffix |
local framework = function ( a ) | local framework = function ( a ) | ||
-- syntax analysis outside strings | -- syntax analysis outside strings | ||
Zeile 81: | Zeile 54: | ||
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 | ||
stab = string.char( 9 ) | stab = string.char( 9 ) | ||
Zeile 87: | Zeile 106: | ||
:gsub( string.char( 13 ), sep ) | :gsub( string.char( 13 ), sep ) | ||
stub = s:gsub( sep, "" ):gsub( stab, "" ) | stub = s:gsub( sep, "" ):gsub( 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 ) | ||
if j then | if j then | ||
r = "ControlChar" | r = "ControlChar" | ||
− | s = s:sub( j + 1, | + | s = mw.text.trim( s:sub( j + 1 ) ) |
+ | s = mw.ustring.sub( s, 1, JSONutil.more ) | ||
else | else | ||
i = true | i = true | ||
j = 1 | j = 1 | ||
+ | last = ( stub:sub( -1 ) == "}" ) | ||
sep0 = string.char( 0x5B, 0x22, 0x27, 0x5D ) -- [ " ' ] | sep0 = string.char( 0x5B, 0x22, 0x27, 0x5D ) -- [ " ' ] | ||
sep1 = string.char( 0x5B, 0x5C, 0x22, 0x5D ) -- [ \ " ] | sep1 = string.char( 0x5B, 0x5C, 0x22, 0x5D ) -- [ \ " ] | ||
Zeile 100: | Zeile 121: | ||
else | else | ||
r = "Bracket0" | r = "Bracket0" | ||
− | s = | + | s = mw.ustring.sub( s, 1, JSONutil.more ) |
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 112: | Zeile 140: | ||
if stub:find( '[^"]*,%s*[%]}]' ) then | if stub:find( '[^"]*,%s*[%]}]' ) then | ||
r = "CommaEnd" | r = "CommaEnd" | ||
− | s = stub | + | s = mw.text.trim( stub ) |
+ | s = mw.ustring.sub( s, 1, JSONutil.more ) | ||
i = false | i = false | ||
j = false | j = false | ||
Zeile 145: | Zeile 174: | ||
else | else | ||
r = "QouteEnd" | r = "QouteEnd" | ||
− | s = s:sub( i, | + | s = mw.text.trim( s:sub( i ) ) |
+ | s = mw.ustring.sub( s, 1, JSONutil.more ) | ||
i = false | i = false | ||
end | end | ||
Zeile 151: | Zeile 181: | ||
else | else | ||
r = "Qoute" | r = "Qoute" | ||
− | s = s:sub( i, | + | s = mw.text.trim( s:sub( i ) ) |
+ | s = mw.ustring.sub( s, 1, JSONutil.more ) | ||
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 | ||
r = "CommaEnd" | r = "CommaEnd" | ||
− | s = stub | + | s = mw.text.trim( stub ) |
+ | s = mw.ustring.sub( s, 1, JSONutil.more ) | ||
else | else | ||
framework( stub ) | framework( stub ) | ||
Zeile 186: | Zeile 218: | ||
if j > 1 then | if j > 1 then | ||
s = string.format( "%d %s", j, s ) | 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 | ||
end | end | ||
Zeile 196: | Zeile 251: | ||
− | JSONutil.fault = function ( alert, add, | + | JSONutil.fault = function ( alert, add, adapt ) |
-- Retrieve formatted message | -- Retrieve formatted message | ||
-- Parameter: | -- Parameter: | ||
-- alert -- string, with error keyword, or other text | -- alert -- string, with error keyword, or other text | ||
− | -- add -- string | + | -- add -- string|nil|false, with context |
− | -- | + | -- adapt -- function|string|table|nil|false, for I18N |
-- Returns string, with HTML span | -- Returns string, with HTML span | ||
local e = mw.html.create( "span" ) | local e = mw.html.create( "span" ) | ||
:addClass( "error" ) | :addClass( "error" ) | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
local s = alert | local s = alert | ||
if type( s ) == "string" then | if type( s ) == "string" then | ||
Zeile 235: | Zeile 281: | ||
e = e[ 2 ] | e = e[ 2 ] | ||
if type( e ) == "table" then | if type( e ) == "table" then | ||
− | local q | + | local q = type( adapt ) |
− | if | + | 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 | 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 | end | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
else | else | ||
s = "JSONutil.fault I18N bad #" .. | s = "JSONutil.fault I18N bad #" .. | ||
Zeile 279: | Zeile 332: | ||
− | JSONutil.fetch = function ( apply, always, | + | JSONutil.fetch = function ( apply, always, adapt ) |
-- Retrieve JSON data, or error message | -- Retrieve JSON data, or error message | ||
-- Parameter: | -- Parameter: | ||
-- apply -- string, with presumable JSON text | -- apply -- string, with presumable JSON text | ||
-- always -- true, if apply is expected to need preprocessing | -- 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 | -- Returns table, with data, or string, with error as HTML span | ||
local lucky, r | local lucky, r | ||
Zeile 293: | Zeile 346: | ||
lucky, r = JSONutil.fair( apply ) | lucky, r = JSONutil.fair( apply ) | ||
if lucky then | if lucky then | ||
− | r = JSONutil.fault( lucky, r, | + | r = JSONutil.fault( lucky, r, adapt ) |
else | else | ||
lucky, r = pcall( mw.text.jsonDecode, r ) | lucky, r = pcall( mw.text.jsonDecode, r ) | ||
if not lucky then | if not lucky then | ||
− | r = JSONutil.fault( r, false, | + | r = JSONutil.fault( r, false, adapt ) |
end | end | ||
end | end | ||
Zeile 303: | Zeile 356: | ||
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 false | ||
+ | -- Postcondition: | ||
+ | -- Returns string -- with queried version, also if problem | ||
+ | -- false -- if appropriate | ||
+ | local last = ( atleast == "~" ) | ||
+ | local since = atleast | ||
+ | local r | ||
+ | if last or since == "wikidata" then | ||
+ | local item = Failsafe.item | ||
+ | since = false | ||
+ | if type( item ) == "number" and item > 0 then | ||
+ | local entity = mw.wikibase.getEntity( string.format( "Q%d", | ||
+ | item ) ) | ||
+ | if type( entity ) == "table" then | ||
+ | local vsn = entity:formatPropertyValues( "P348" ) | ||
+ | if type( vsn ) == "table" and | ||
+ | type( vsn.value ) == "string" and | ||
+ | vsn.value ~= "" then | ||
+ | if last and vsn.value == Failsafe.serial then | ||
+ | r = false | ||
+ | else | ||
+ | r = vsn.value | ||
+ | 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 324: | Zeile 420: | ||
end | end | ||
end | end | ||
− | return | + | return Failsafe.failsafe( since ) or "" |
end -- p.failsafe() | end -- p.failsafe() | ||
Aktuelle Version vom 6. September 2019, 12:53 Uhr
Die Dokumentation für dieses Modul kann unter Modul:JSONutil/Doku erstellt werden
local JSONutil = { suite = "JSONutil",
serial = "2019-07-18",
item = 63869449 }
--[=[
preprocess JSON data
]=]
local Failsafe = JSONutil
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()
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 sep = string.char( 10 )
local i, j, last, r, scan, sep0, sep1, stab, 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
stab = string.char( 9 )
s = s:gsub( string.char( 13, 10 ), sep )
:gsub( string.char( 13 ), sep )
stub = s:gsub( sep, "" ):gsub( 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( sep, "\\n" )
:gsub( 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 = mw.text.encode( s:gsub( sep, " " ) ):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 false
-- Postcondition:
-- Returns string -- with queried version, also if problem
-- false -- if appropriate
local last = ( atleast == "~" )
local since = atleast
local r
if last or since == "wikidata" then
local item = Failsafe.item
since = false
if type( item ) == "number" and item > 0 then
local entity = mw.wikibase.getEntity( string.format( "Q%d",
item ) )
if type( entity ) == "table" then
local vsn = entity:formatPropertyValues( "P348" )
if type( vsn ) == "table" and
type( vsn.value ) == "string" and
vsn.value ~= "" then
if last and vsn.value == Failsafe.serial then
r = false
else
r = vsn.value
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.JSONutil = function ()
-- Module interface
return JSONutil
end
return p