Модуль:JsonLoader: различия между версиями
Pok (обсуждение | вклад) Содержимое страницы заменено на «local p = {} function p.getFromTitle(titleName) local title = mw.title.new(titleName) local jsonText = title and title:getContent() or "" local ok, data = pcall(mw.text.jsonDecode, jsonText) if ok and type(data) == "table" then return data else return {} end end function p.invoke(frame) local name = frame.args.title return p.getFromTitle(name) end return p» Метки: замена ручная отмена отменено |
Pok (обсуждение | вклад) Отмена версии 315679, сделанной Pok (обсуждение) Метки: отмена отменено |
||
| Строка 1: | Строка 1: | ||
local p = {} | local p = {} | ||
local function skip_ws(text, pos) | |||
local len = #text | |||
while pos <= len do | |||
local ch = string.sub(text, pos, pos) | |||
if ch ~= " " and ch ~= "\n" and ch ~= "\r" and ch ~= "\t" then | |||
return pos | |||
end | |||
pos = pos + 1 | |||
end | |||
return pos | |||
end | |||
local function skip_string(text, pos) | |||
pos = pos + 1 | |||
local len = #text | |||
while pos <= len do | |||
local ch = string.sub(text, pos, pos) | |||
if ch == "\\" then | |||
pos = pos + 2 | |||
elseif ch == "\"" then | |||
return pos | |||
else | |||
pos = pos + 1 | |||
end | |||
end | |||
return nil | |||
end | |||
local function find_value_end(text, pos) | |||
local ch = string.sub(text, pos, pos) | |||
if ch == "\"" then | |||
return skip_string(text, pos) | |||
end | |||
if ch == "{" or ch == "[" then | |||
local depth = 0 | |||
local len = #text | |||
while pos <= len do | |||
ch = string.sub(text, pos, pos) | |||
if ch == "\"" then | |||
pos = skip_string(text, pos) | |||
if not pos then | |||
return nil | |||
end | |||
elseif ch == "{" or ch == "[" then | |||
depth = depth + 1 | |||
elseif ch == "}" or ch == "]" then | |||
depth = depth - 1 | |||
if depth == 0 then | |||
return pos | |||
end | |||
end | |||
pos = pos + 1 | |||
end | |||
return nil | |||
end | |||
local len = #text | |||
while pos <= len do | |||
ch = string.sub(text, pos, pos) | |||
if ch == "," or ch == "}" or ch == "]" or ch == "\n" or ch == "\r" then | |||
return pos - 1 | |||
end | |||
pos = pos + 1 | |||
end | |||
return len | |||
end | |||
local function decode_json(text) | |||
local ok, data = pcall(mw.text.jsonDecode, text) | |||
if ok then | |||
return data | |||
end | |||
return nil | |||
end | |||
local function find_member_range(text, objectStart, keyName) | |||
local pos = skip_ws(text, objectStart) | |||
if string.sub(text, pos, pos) ~= "{" then | |||
return nil, nil | |||
end | |||
pos = skip_ws(text, pos + 1) | |||
while pos <= #text and string.sub(text, pos, pos) ~= "}" do | |||
if string.sub(text, pos, pos) ~= "\"" then | |||
return nil, nil | |||
end | |||
local keyStart = pos | |||
local keyEnd = skip_string(text, pos) | |||
if not keyEnd then | |||
return nil, nil | |||
end | |||
local key = decode_json(string.sub(text, keyStart, keyEnd)) | |||
pos = skip_ws(text, keyEnd + 1) | |||
if string.sub(text, pos, pos) ~= ":" then | |||
return nil, nil | |||
end | |||
local valueStart = skip_ws(text, pos + 1) | |||
local valueEnd = find_value_end(text, valueStart) | |||
if not valueEnd then | |||
return nil, nil | |||
end | |||
if key == keyName then | |||
return valueStart, valueEnd | |||
end | |||
pos = skip_ws(text, valueEnd + 1) | |||
local sep = string.sub(text, pos, pos) | |||
if sep == "," then | |||
pos = skip_ws(text, pos + 1) | |||
elseif sep == "}" then | |||
return nil, nil | |||
else | |||
return nil, nil | |||
end | |||
end | |||
return nil, nil | |||
end | |||
local function deep_copy(src) | |||
local dst = {} | |||
for k, v in pairs(src) do | |||
if type(v) == "table" then | |||
dst[k] = deep_copy(v) | |||
else | |||
dst[k] = v | |||
end | |||
end | |||
return dst | |||
end | |||
local function deep_merge(dst, src) | |||
for k, v in pairs(src) do | |||
if type(v) == "table" and type(dst[k]) == "table" then | |||
deep_merge(dst[k], v) | |||
elseif type(v) == "table" then | |||
dst[k] = deep_copy(v) | |||
else | |||
dst[k] = v | |||
end | |||
end | |||
end | |||
local function new_loader(titleName) | |||
local loader = {} | |||
local title = mw.title.new(titleName) | |||
local jsonData | |||
local fullData | |||
local defaultData | |||
local idDataCache = {} | |||
local function get_json() | |||
if jsonData == nil then | |||
jsonData = title and title:getContent() or "" | |||
end | |||
return jsonData | |||
end | |||
local function decode_root_member(keyName) | |||
local text = get_json() | |||
local rootStart = string.find(text, "{", 1, true) | |||
if not rootStart then | |||
return nil | |||
end | |||
local valueStart, valueEnd = find_member_range(text, rootStart, keyName) | |||
if not valueStart then | |||
return nil | |||
end | |||
return decode_json(string.sub(text, valueStart, valueEnd)) | |||
end | |||
local function decode_id_member(id) | |||
if idDataCache[id] ~= nil then | |||
return idDataCache[id] | |||
end | |||
local text = get_json() | |||
local rootStart = string.find(text, "{", 1, true) | |||
if not rootStart then | |||
idDataCache[id] = false | |||
return nil | |||
end | |||
local idsStart = find_member_range(text, rootStart, "id") | |||
if not idsStart then | |||
idDataCache[id] = false | |||
return nil | |||
end | |||
local valueStart, valueEnd = find_member_range(text, idsStart, id) | |||
if not valueStart then | |||
idDataCache[id] = false | |||
return nil | |||
end | |||
local data = decode_json(string.sub(text, valueStart, valueEnd)) | |||
idDataCache[id] = data or false | |||
return data | |||
end | |||
function loader.all() | |||
if fullData == nil then | |||
local data = decode_json(get_json()) | |||
fullData = type(data) == "table" and data or {} | |||
end | |||
return fullData | |||
end | |||
function loader.get(id) | |||
id = tostring(id or "") | |||
if defaultData == nil then | |||
local data = decode_root_member("default") | |||
defaultData = type(data) == "table" and data or false | |||
end | |||
local base = defaultData ~= false and defaultData or nil | |||
if id == "" then | |||
return base | |||
end | |||
local specific = decode_id_member(id) | |||
if type(specific) ~= "table" then | |||
return base | |||
end | |||
if type(base) == "table" then | |||
local merged = deep_copy(base) | |||
deep_merge(merged, specific) | |||
return merged | |||
end | |||
return specific | |||
end | |||
return loader | |||
end | |||
function p.getFromTitle(titleName) | function p.getFromTitle(titleName) | ||
return new_loader(titleName) | |||
end | |||
function p.getAllFromTitle(titleName) | |||
return new_loader(titleName).all() | |||
end | end | ||
function p.invoke(frame) | function p.invoke(frame) | ||
local name = frame.args.title | |||
return p.getAllFromTitle(name) | |||
end | end | ||
return p | return p | ||