Module:JSONutil/sandbox
| This is the module sandbox page for Module:JSONutil. |
local JSONutil = {
item = 63869449,
serial = "2020-11-08",
suite = "JSONutil"
}
--[=[
Preprocess or generate JSON data.
]=]
local Failsafe = JSONutil
JSONutil.Encoder = {
scream = "@error@JSONencoder@",
sep = string.char(10),
stab = string.char(9)
}
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
Content Disclaimer
Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.
- The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
- There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
- It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
- Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
- Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.