モジュール:JSONutil

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  1 then
s = s:sub( 1,  i - 1 )
else
s = ""
end
elseif k   and   ( not m  or  k  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 = "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