Модуль:JsonLoader: различия между версиями
Материал из Space Station 14 Вики
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 (обсуждение | вклад) Нет описания правки Метка: отменено |
||
| Строка 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 | end | ||
function p.invoke(frame) | function p.invoke(frame) | ||
local name = frame.args.title | |||
local loader = p.getFromTitle(name) | |||
return loader.all() | |||
end | end | ||
return p | return p | ||
Версия от 19:08, 18 июня 2026
Для документации этого модуля может быть создана страница Модуль:JsonLoader/doc
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)
return new_loader(titleName)
end
function p.invoke(frame)
local name = frame.args.title
local loader = p.getFromTitle(name)
return loader.all()
end
return p