Модуль:GetField: различия между версиями
Pok (обсуждение | вклад) мНет описания правки |
Pok (обсуждение | вклад) Нет описания правки |
||
| (не показаны 103 промежуточные версии этого же участника) | |||
| Строка 1: | Строка 1: | ||
local p = {} | local p = {} | ||
local | local JsonPaths = require('Module:JsonPaths') | ||
local | local getArgs = require('Module:Arguments').getArgs | ||
local function get_module_name(pagePath) | |||
return JsonPaths.get(pagePath) | |||
end | |||
local function load_cached_data(moduleName) | |||
local ok, loaded = pcall(mw.loadData, moduleName) | |||
if not ok or not loaded then | |||
return nil | |||
end | |||
return loaded | |||
end | |||
local function parse_indexed_part(part) | local function parse_indexed_part(part) | ||
local key, idx = string.match(part, "^(.-)%[(%d+)%]$") | |||
if key then | |||
return key, tonumber(idx) | |||
end | |||
local num = tonumber(part) | |||
if num then | |||
return nil, num | |||
end | |||
return part, nil | |||
end | |||
local function parse_path(path) | |||
if not path or path == "" then | |||
return nil | |||
end | |||
local parsed = {} | |||
for part in string.gmatch(path, "([^%.]+)") do | |||
parsed[#parsed + 1] = { parse_indexed_part(part) } | |||
end | |||
return parsed | |||
end | |||
local function get_by_parsed_path(tbl, parsedPath) | |||
if not tbl or not parsedPath then | |||
return nil | |||
end | |||
local cur = tbl | |||
for i = 1, #parsedPath do | |||
local token = parsedPath[i] | |||
local key = token[1] | |||
local idx = token[2] | |||
if key and key ~= "" then | |||
if type(cur) ~= "table" then | |||
return nil | |||
end | |||
local nextCur = cur[key] | |||
if nextCur == nil then | |||
nextCur = cur["!type:" .. key] | |||
end | |||
cur = nextCur | |||
end | |||
if idx then | |||
if type(cur) ~= "table" then | |||
return nil | |||
end | |||
cur = cur[idx] | |||
end | |||
if cur == nil then | |||
return nil | |||
end | |||
end | |||
return cur | |||
end | end | ||
local function get_by_path(tbl, path) | local function get_by_path(tbl, path) | ||
return get_by_parsed_path(tbl, parse_path(path)) | |||
end | end | ||
local function format_value(v) | local function format_value(v) | ||
local okJson, json = pcall(mw.text.jsonEncode, v) | |||
if okJson and json == "null" then | |||
return "null" | |||
end | |||
if v == nil then | |||
return "" | |||
end | |||
local t = type(v) | |||
if t == "string" or t == "number" or t == "boolean" then | |||
return tostring(v) | |||
elseif t == "table" then | |||
local ok, json2 = pcall(mw.text.jsonEncode, v) | |||
if ok and json2 then | |||
return json2 | |||
end | |||
return "" | |||
else | |||
return tostring(v) | |||
end | |||
end | end | ||
local function | local function to_nowiki(v) | ||
return "<nowiki>" .. v .. "</nowiki>" | |||
end | end | ||
local function is_array(tbl) | local function is_array(tbl) | ||
local max = 0 | |||
local count = 0 | |||
for k in pairs(tbl) do | |||
if type(k) ~= "number" then | |||
return false | |||
end | |||
if k > max then | |||
max = k | |||
end | |||
count = count + 1 | |||
end | |||
return count > 0 and max == count | |||
end | end | ||
local function deep_copy(src) | 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 | end | ||
local function deep_merge(dst, src) | 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 resolve_entry(data, id) | |||
if type(data) ~= "table" then | |||
return nil | |||
end | |||
if id and id ~= "" then | |||
local direct = data[id] | |||
if direct ~= nil then | |||
return direct | |||
end | |||
local idsTable = data.id | |||
if type(idsTable) == "table" then | |||
local specific = idsTable[id] | |||
if type(specific) == "table" then | |||
local base = data["default"] | |||
if type(base) == "table" then | |||
local merged = deep_copy(base) | |||
deep_merge(merged, specific) | |||
return merged | |||
end | |||
return deep_copy(specific) | |||
end | |||
end | |||
end | |||
local base = data["default"] | |||
if type(base) == "table" then | |||
return deep_copy(base) | |||
end | |||
return nil | |||
end | end | ||
function | local function resolve_path_value(data, id, parsedPath) | ||
if type(data) ~= "table" or not parsedPath or not id or id == "" then | |||
return nil | |||
end | |||
local direct = data[id] | |||
if direct ~= nil then | |||
local value = get_by_parsed_path(direct, parsedPath) | |||
if value ~= nil then | |||
return value | |||
end | |||
end | |||
local idsTable = data.id | |||
if type(idsTable) == "table" then | |||
local specific = idsTable[id] | |||
if type(specific) == "table" then | |||
local value = get_by_parsed_path(specific, parsedPath) | |||
if value ~= nil then | |||
return value | |||
end | |||
end | |||
end | |||
local base = data["default"] | |||
if type(base) == "table" then | |||
return get_by_parsed_path(base, parsedPath) | |||
end | |||
return nil | |||
end | |||
local function resolve_entry_path_value(data, id, parsedPath) | |||
if type(data) ~= "table" or not parsedPath or not id or id == "" then | |||
return nil | |||
end | |||
local entry = resolve_entry(data, id) | |||
if type(entry) ~= "table" then | |||
return nil | |||
end | |||
return get_by_parsed_path(entry, parsedPath) | |||
end | |||
local function collect_id_keys(data) | |||
if type(data) ~= "table" then | |||
return {} | |||
end | |||
local idsTable = data.id | |||
local ids = {} | |||
if type(idsTable) == "table" then | |||
for k in pairs(idsTable) do | |||
ids[#ids + 1] = k | |||
end | |||
return ids | |||
end | |||
for k in pairs(data) do | |||
if k ~= "default" and k ~= "id" then | |||
ids[#ids + 1] = k | |||
end | |||
end | |||
return ids | |||
end | |||
local function contains_target(v, target) | |||
if type(v) == "table" then | |||
if is_array(v) then | |||
for _, item in ipairs(v) do | |||
if tostring(item) == target then | |||
return true | |||
end | |||
end | |||
return false | |||
end | |||
for _, item in pairs(v) do | |||
if tostring(item) == target then | |||
return true | |||
end | |||
end | |||
return false | |||
end | |||
return tostring(v) == target | |||
end | |||
local function is_nonempty_value(v) | |||
if v == nil then | |||
return false | |||
end | |||
if type(v) == "table" then | |||
return next(v) ~= nil | |||
end | |||
return true | |||
end | |||
local function preprocess_or_return(frame, text) | |||
if type(frame) == "table" and type(frame.preprocess) == "function" then | |||
return frame:preprocess(text) | |||
end | |||
return text | |||
end | |||
local function get_field_loose(entry, fieldId) | |||
local value = entry[fieldId] | |||
if value ~= nil then | |||
return value | |||
end | |||
if fieldId == "" then | |||
return nil | |||
end | |||
local first = string.sub(fieldId, 1, 1) | |||
local tail = string.sub(fieldId, 2) | |||
value = entry[string.lower(first) .. tail] | |||
if value ~= nil then | |||
return value | |||
end | |||
return entry[string.upper(first) .. tail] | |||
end | |||
local function apply_pattern(s, pattern, repl) | |||
if not pattern or pattern == "" or not s then | |||
return s | |||
end | |||
local text = tostring(s) | |||
local replacement | |||
if repl and repl ~= "" then | |||
replacement = tostring(repl) | |||
replacement = replacement:gsub("\\(%d)", "%%%1") | |||
else | |||
replacement = "%1" | |||
end | |||
local patt = pattern | |||
if not patt:find("%^") and not patt:find("%$") then | |||
patt = "^" .. patt .. "$" | |||
end | |||
return (text:gsub(patt, replacement)) | |||
end | |||
local function flatten_parts(entry) | |||
if type(entry) ~= "table" then | |||
return {} | |||
end | |||
local parts = {} | |||
local function append_table_json(key, value) | |||
local ok, json = pcall(mw.text.jsonEncode, value) | |||
if ok and json then | |||
parts[#parts + 1] = key .. "=" .. to_nowiki(json) | |||
end | |||
end | |||
local function walk(tbl, prefix) | |||
local keys = {} | |||
for k in pairs(tbl) do | |||
keys[#keys + 1] = k | |||
end | |||
table.sort(keys, function(a, b) | |||
return tostring(a) < tostring(b) | |||
end) | |||
for _, k in ipairs(keys) do | |||
local v = tbl[k] | |||
local kStr = tostring(k) | |||
local key = (prefix == "" and kStr or prefix .. "." .. kStr) | |||
if type(v) == "table" then | |||
if next(v) ~= nil then | |||
append_table_json(key, v) | |||
if is_array(v) then | |||
local first = v[1] | |||
if type(first) == "table" then | |||
walk(first, key) | |||
end | |||
else | |||
walk(v, key) | |||
end | |||
end | |||
else | |||
parts[#parts + 1] = key .. "=" .. tostring(v) | |||
end | |||
end | |||
end | |||
walk(entry, "") | |||
return parts | |||
end | |||
local function flatten_entry(entry) | |||
local parts = flatten_parts(entry) | |||
if #parts == 0 then | |||
return "" | |||
end | |||
return table.concat(parts, "|") | |||
end | |||
local function append_flattened_part(parts, key, value) | |||
if value == nil then | |||
return | |||
end | |||
if type(value) == "table" then | |||
if next(value) == nil then | |||
return | |||
end | |||
local ok, json = pcall(mw.text.jsonEncode, value) | |||
if ok and json then | |||
parts[#parts + 1] = key .. "=" .. to_nowiki(json) | |||
end | |||
return | |||
end | |||
parts[#parts + 1] = key .. "=" .. tostring(value) | |||
end | |||
local function flatten_selected_parts(entry, keys) | |||
if type(entry) ~= "table" or type(keys) ~= "table" then | |||
return {} | |||
end | |||
local parts = {} | |||
local seen = {} | |||
for i = 1, #keys do | |||
local key = keys[i] | |||
if type(key) == "string" and key ~= "" and not seen[key] then | |||
seen[key] = true | |||
append_flattened_part(parts, key, get_by_path(entry, key)) | |||
end | |||
end | |||
return parts | |||
end | |||
local function resolve_template_path(tplPath) | |||
local templatePath = tplPath | |||
local project = JsonPaths.project() | |||
if project ~= nil and project ~= "" then | |||
templatePath = tplPath .. "/" .. project | |||
templatePath = "{{#ifexist:Шаблон:" .. templatePath .. "|" .. templatePath .. "|" .. tplPath .. "}}" | |||
end | |||
return templatePath | |||
end | |||
local function split_template_spec(tplPath, tplArgs) | |||
tplPath = mw.text.unstripNoWiki(tplPath or "") | |||
tplArgs = mw.text.unstripNoWiki(tplArgs or "") | |||
if tplArgs ~= "" and string.sub(tplArgs, 1, 1) == "|" then | |||
tplArgs = string.sub(tplArgs, 2) | |||
end | |||
if tplArgs == "" then | |||
local pipePos = string.find(tplPath, "|", 1, true) | |||
if pipePos then | |||
tplArgs = mw.text.unstripNoWiki(string.sub(tplPath, pipePos + 1)) | |||
if tplArgs ~= "" and string.sub(tplArgs, 1, 1) == "|" then | |||
tplArgs = string.sub(tplArgs, 2) | |||
end | |||
tplPath = string.sub(tplPath, 1, pipePos - 1) | |||
end | |||
end | |||
return tplPath, tplArgs | |||
end | |||
local function build_tpl(id, pagePath, tplPath, data, tplArgs) | |||
if id == "" or pagePath == "" or tplPath == "" then | |||
return "" | |||
end | |||
local moduleName = get_module_name(pagePath) | |||
data = data or load_cached_data(moduleName) | |||
if not data then | |||
return "" | |||
end | |||
local entry = resolve_entry(data, id) | |||
local extra = flatten_entry(entry) | |||
local extraTplArgs = tplArgs or "" | |||
local templatePath = resolve_template_path(tplPath) | |||
local tplStr = "{{Шаблон:" .. templatePath | |||
if extraTplArgs ~= "" then | |||
tplStr = tplStr .. "|" .. extraTplArgs | |||
end | |||
tplStr = tplStr .. "|id=" .. tostring(id) | |||
if extra ~= "" then | |||
tplStr = tplStr .. "|" .. extra | |||
end | |||
tplStr = tplStr .. "}}" | |||
return tplStr | |||
end | |||
function p.findInGenerator(frame) | |||
local args = frame.args or {} | |||
local searchId = args[1] or "" | |||
local kind = (args[2] or ""):lower() | |||
local fieldId = args[3] or "" | |||
if searchId == "" or fieldId == "" then | |||
return "" | |||
end | |||
if kind ~= "prototype" and kind ~= "component" then | |||
return "" | |||
end | |||
local storeName = (kind == "prototype") and "prototype_store.json" or "component_store.json" | |||
local moduleName = get_module_name(storeName) | |||
local data = load_cached_data(moduleName) | |||
if not data then | |||
return "" | |||
end | |||
local entry = data[searchId] | |||
if type(entry) ~= "table" then | |||
return "" | |||
end | |||
local value = get_field_loose(entry, fieldId) | |||
if value == nil then | |||
return "" | |||
end | |||
local out = {} | |||
local t = type(value) | |||
if t == "table" then | |||
for _, v in ipairs(value) do | |||
out[#out + 1] = v | |||
end | |||
else | |||
out[1] = value | |||
end | |||
return mw.text.jsonEncode(out) | |||
end | end | ||
function p.flattenField(frame) | function p.flattenField(frame) | ||
local args = frame.args or {} | |||
local id = args[1] or "" | |||
local pagePath = args[2] or "" | |||
if id == "" or pagePath == "" then | |||
return "" | |||
end | |||
local moduleName = get_module_name(pagePath) | |||
local data = load_cached_data(moduleName) | |||
if not data then | |||
return "" | |||
end | |||
local entry = resolve_entry(data, id) or {} | |||
return flatten_entry(entry) | |||
end | |||
function p.flattenFieldSelective(frame) | |||
local args = frame.args or {} | |||
local id = args[1] or "" | |||
local pagePath = args[2] or "" | |||
local keysJson = mw.text.unstripNoWiki(args[3] or args.keys or "") | |||
if id == "" or pagePath == "" or keysJson == "" then | |||
return "" | |||
end | |||
local okKeys, keys = pcall(mw.text.jsonDecode, keysJson) | |||
if not okKeys or type(keys) ~= "table" or #keys == 0 then | |||
return "" | |||
end | |||
local moduleName = get_module_name(pagePath) | |||
local data = load_cached_data(moduleName) | |||
if not data then | |||
return "" | |||
end | |||
local entry = resolve_entry(data, id) or {} | |||
local parts = flatten_selected_parts(entry, keys) | |||
if #parts == 0 then | |||
return "" | |||
end | |||
return table.concat(parts, "|") | |||
end | |||
function p.get(frame) | |||
local args = getArgs(frame, { removeBlanks = false }) | |||
local id = args[1] or "" | |||
local pagePath = args[2] or "" | |||
local keyPath = args[3] or "" | |||
if pagePath == "" then | |||
return "" | |||
end | |||
local moduleName = get_module_name(pagePath) | |||
local data = load_cached_data(moduleName) | |||
if not data then | |||
return "" | |||
end | |||
local entry = resolve_entry(data, id) | |||
if entry == nil then | |||
return "" | |||
end | |||
if keyPath == "" then | |||
return format_value(entry) | |||
end | |||
local value = get_by_path(entry, keyPath) | |||
return format_value(value) | |||
end | end | ||
function p. | local function collect_by_parsed_path(tbl, parsedPath, pos, out) | ||
if pos > #parsedPath then | |||
out[#out + 1] = tbl | |||
return | |||
end | |||
if type(tbl) ~= "table" then | |||
return | |||
end | |||
local token = parsedPath[pos] | |||
local key = token[1] | |||
local idx = token[2] | |||
if key == "*" then | |||
for _, child in pairs(tbl) do | |||
local nextCur = child | |||
if idx then | |||
if type(nextCur) ~= "table" then | |||
nextCur = nil | |||
else | |||
nextCur = nextCur[idx] | |||
end | |||
end | |||
collect_by_parsed_path(nextCur, parsedPath, pos + 1, out) | |||
end | |||
return | |||
end | |||
local nextCur | |||
if key and key ~= "" then | |||
nextCur = tbl[key] | |||
if nextCur == nil then | |||
nextCur = tbl["!type:" .. key] | |||
end | |||
else | |||
nextCur = tbl | |||
end | |||
if idx then | |||
if type(nextCur) ~= "table" then | |||
return | |||
end | |||
nextCur = nextCur[idx] | |||
end | |||
collect_by_parsed_path(nextCur, parsedPath, pos + 1, out) | |||
end | |||
local function get_by_parsed_path_multi(tbl, parsedPath) | |||
local out = {} | |||
collect_by_parsed_path(tbl, parsedPath, 1, out) | |||
return out | |||
end | |||
local function entry_matches_path(entry, parsedPath, searchValue, searchType) | |||
local values = get_by_parsed_path_multi(entry, parsedPath) | |||
local target = tostring(searchValue) | |||
for _, v in ipairs(values) do | |||
if searchType == "key" then | |||
if type(v) == "table" and v[target] ~= nil then | |||
return true | |||
end | |||
else | |||
if contains_target(v, target) then | |||
return true | |||
end | |||
end | |||
end | |||
return false | |||
end | |||
local function entry_has_any_nonempty_path(entry, parsedPath) | |||
local values = get_by_parsed_path_multi(entry, parsedPath) | |||
for _, v in ipairs(values) do | |||
if is_nonempty_value(v) then | |||
return true | |||
end | |||
end | |||
return false | |||
end | |||
function p.searchId(frame) | |||
local args = getArgs(frame, { removeBlanks = false }) | |||
local searchValue = args[1] or "" | |||
local pagePath = args[2] or "" | |||
local keyPath = args[3] or "" | |||
local searchType = (args.searchType or ""):lower() | |||
if searchValue == "" or pagePath == "" or keyPath == "" then | |||
return "" | |||
end | |||
if searchType == "" then | |||
searchType = "value" | |||
end | |||
local moduleName = get_module_name(pagePath) | |||
local data = load_cached_data(moduleName) | |||
if not data then | |||
return "[]" | |||
end | |||
local parsedPath = parse_path(keyPath) | |||
if not parsedPath then | |||
return "" | |||
end | |||
local ids = collect_id_keys(data) | |||
if #ids == 0 then | |||
return "" | |||
end | |||
local matches = {} | |||
local target = tostring(searchValue) | |||
for _, idKey in ipairs(ids) do | |||
local entry = resolve_entry(data, idKey) | |||
if type(entry) == "table" and entry_matches_path(entry, parsedPath, target, searchType) then | |||
matches[#matches + 1] = idKey | |||
end | |||
end | |||
if #matches == 0 then | |||
return "" | |||
end | |||
local ok, json = pcall(mw.text.jsonEncode, matches) | |||
if ok and json then | |||
return json | |||
end | |||
return "" | |||
end | |||
function p.searchIdTpl(frame) | |||
local args = getArgs(frame, { removeBlanks = false }) | |||
local searchValue = args[1] or "" | |||
local pagePath = args[2] or "" | |||
local keyPath = args[3] or "" | |||
local tplPath = mw.text.unstripNoWiki(args[4] or "") | |||
local tplArgs = args.tplArgs or args.templateArgs or "" | |||
local searchType = (args.searchType or ""):lower() | |||
if searchType == "" then | |||
searchType = "value" | |||
end | |||
if searchType == "path" then | |||
searchValue = "" | |||
pagePath = args[1] or "" | |||
keyPath = args[2] or "" | |||
tplPath = mw.text.unstripNoWiki(args[3] or "") | |||
tplArgs = args[4] or args.tplArgs or args.templateArgs or "" | |||
end | |||
tplPath, tplArgs = split_template_spec(tplPath, tplArgs) | |||
if pagePath == "" or keyPath == "" or tplPath == "" then | |||
return "" | |||
end | |||
if searchType ~= "path" and searchValue == "" then | |||
return "" | |||
end | |||
local moduleName = get_module_name(pagePath) | |||
local data = load_cached_data(moduleName) | |||
if not data then | |||
return "" | |||
end | |||
local parsedPath = parse_path(keyPath) | |||
if not parsedPath then | |||
return "" | |||
end | |||
local ids = collect_id_keys(data) | |||
if #ids == 0 then | |||
return "" | |||
end | |||
local matches = {} | |||
if searchType == "path" then | |||
for _, idKey in ipairs(ids) do | |||
local entry = resolve_entry(data, idKey) | |||
if type(entry) == "table" and entry_has_any_nonempty_path(entry, parsedPath) then | |||
matches[#matches + 1] = idKey | |||
end | |||
end | |||
else | |||
local target = tostring(searchValue) | |||
for _, idKey in ipairs(ids) do | |||
local entry = resolve_entry(data, idKey) | |||
if type(entry) == "table" and entry_matches_path(entry, parsedPath, target, searchType) then | |||
matches[#matches + 1] = idKey | |||
end | |||
end | |||
end | |||
if #matches == 0 then | |||
return "" | |||
end | |||
local out = {} | |||
for _, idKey in ipairs(matches) do | |||
local tpl = build_tpl(idKey, pagePath, tplPath, data, tplArgs) | |||
if tpl ~= "" then | |||
out[#out + 1] = tpl | |||
end | |||
end | |||
if #out == 0 then | |||
return "" | |||
end | |||
local result = table.concat(out, " ") | |||
return preprocess_or_return(frame, result) | |||
end | end | ||
function p.getTpl(frame) | function p.getTpl(frame) | ||
local args = getArgs(frame, { removeBlanks = false }) | |||
local id = args[1] or "" | |||
local pagePath = args[2] or "" | |||
local tplPath = mw.text.unstripNoWiki(args[3] or "") | |||
local tplArgs = args[4] or args.tplArgs or args.templateArgs or "" | |||
tplPath, tplArgs = split_template_spec(tplPath, tplArgs) | |||
if id == "" or pagePath == "" or tplPath == "" then | |||
return "" | |||
end | |||
local moduleName = get_module_name(pagePath) | |||
local data = frame.data | |||
if not data then | |||
data = load_cached_data(moduleName) | |||
end | |||
if not data then | |||
return "" | |||
end | |||
local tplStr = build_tpl(id, pagePath, tplPath, data, tplArgs) | |||
return preprocess_or_return(frame, tplStr) | |||
end | |||
function p.searchStoreTpl(frame) | |||
local args = getArgs(frame, { removeBlanks = false }) | |||
local searchId = args[1] or "" | |||
local kind = (args[2] or ""):lower() | |||
local generatorId = args[3] or "" | |||
local tplPath = mw.text.unstripNoWiki(args[4] or "") | |||
local tplArgs = args[5] or args.tplArgs or args.templateArgs or "" | |||
tplPath, tplArgs = split_template_spec(tplPath, tplArgs) | |||
if searchId == "" or generatorId == "" or tplPath == "" then | |||
return "" | |||
end | |||
if kind ~= "prototype" and kind ~= "component" then | |||
return "" | |||
end | |||
local dir = (kind == "prototype") and "prototype/" or "component/" | |||
local pagePath = dir .. generatorId .. ".json" | |||
local idsJson = p.findInGenerator({ args = { searchId, kind, generatorId } }) | |||
local ok, ids = pcall(mw.text.jsonDecode, idsJson or "") | |||
if not ok or type(ids) ~= "table" or #ids == 0 then | |||
return "" | |||
end | |||
local moduleName = get_module_name(pagePath) | |||
local data = load_cached_data(moduleName) | |||
if not data then | |||
return "" | |||
end | |||
local out = {} | |||
for _, id in ipairs(ids) do | |||
local tpl = build_tpl(id, pagePath, tplPath, data, tplArgs) | |||
if tpl ~= "" then | |||
out[#out + 1] = tpl | |||
end | |||
end | |||
local result = table.concat(out, " ") | |||
return preprocess_or_return(frame, result) | |||
end | |||
function p.flattenParams(entry) | |||
return flatten_parts(entry) | |||
end | |||
function p.searchStore(frame) | |||
local args = getArgs(frame, { removeBlanks = false }) | |||
local searchId = args[1] or "" | |||
local kind = (args[2] or ""):lower() | |||
local generatorId = args[3] or "" | |||
if searchId == "" or generatorId == "" then | |||
return "" | |||
end | |||
if kind ~= "prototype" and kind ~= "component" then | |||
return "" | |||
end | |||
local idsJson = p.findInGenerator({ args = { searchId, kind, generatorId } }) | |||
local ok, ids = pcall(mw.text.jsonDecode, idsJson or "") | |||
if not ok or type(ids) ~= "table" or #ids == 0 then | |||
return "" | |||
end | |||
local okOut, outJson = pcall(mw.text.jsonEncode, ids) | |||
if okOut and outJson then | |||
return outJson | |||
end | |||
return "" | |||
end | |||
function p.hasComp(frame) | |||
local args = getArgs(frame, { removeBlanks = false }) | |||
local entityId = args[1] or "" | |||
local compName = args[2] or "" | |||
if entityId == "" or compName == "" then | |||
return "false" | |||
end | |||
local moduleName = get_module_name("component.json") | |||
local data = load_cached_data(moduleName) | |||
if not data then | |||
return "false" | |||
end | |||
if type(data) ~= "table" then | |||
return "false" | |||
end | |||
local entry = data[entityId] | |||
if type(entry) ~= "table" then | |||
return "false" | |||
end | |||
local target = tostring(compName) | |||
for _, v in ipairs(entry) do | |||
if tostring(v) == target then | |||
return "true" | |||
end | |||
end | |||
return "false" | |||
end | |||
function p.getAll(frame) | |||
local args = getArgs(frame, { removeBlanks = false }) | |||
local pagePath = args[1] or "" | |||
local replace = mw.text.unstripNoWiki(args.replace or "") | |||
local pattern = mw.text.unstripNoWiki(args.pattern or "(.*)") | |||
if pagePath == "" then | |||
return "" | |||
end | |||
local moduleName = get_module_name(pagePath) | |||
local data = load_cached_data(moduleName) | |||
if not data then | |||
return "" | |||
end | |||
local idsTable = data.id | |||
if type(idsTable) ~= "table" then | |||
return "" | |||
end | |||
local ids = {} | |||
for k in pairs(idsTable) do | |||
ids[#ids + 1] = k | |||
end | |||
table.sort(ids) | |||
if replace ~= "" then | |||
local out = {} | |||
for _, id in ipairs(ids) do | |||
local text = apply_pattern(id, pattern, replace) | |||
if text ~= "" then | |||
out[#out + 1] = text | |||
end | |||
end | |||
if #out == 0 then | |||
return "" | |||
end | |||
return preprocess_or_return(frame, table.concat(out, "\n")) | |||
end | |||
local ok, json = pcall(mw.text.jsonEncode, ids) | |||
if ok and json then | |||
return json | |||
end | |||
return "" | |||
end | |||
function p.getAllTpl(frame) | |||
local args = getArgs(frame, { removeBlanks = false }) | |||
local pagePath = args[1] or "" | |||
local tplPath = args[2] or "" | |||
local tplArgs = args[3] or args.tplArgs or args.templateArgs or "" | |||
tplPath, tplArgs = split_template_spec(tplPath, tplArgs) | |||
if pagePath == "" or tplPath == "" then | |||
return "" | |||
end | |||
local moduleName = get_module_name(pagePath) | |||
local data = load_cached_data(moduleName) | |||
if not data then | |||
return "" | |||
end | |||
local idsTable = data.id | |||
if type(idsTable) ~= "table" then | |||
return "" | |||
end | |||
local out = {} | |||
for idKey in pairs(idsTable) do | |||
local tpl = build_tpl(idKey, pagePath, tplPath, data, tplArgs) | |||
if tpl ~= "" then | |||
out[#out + 1] = tpl | |||
end | |||
end | |||
table.sort(out) | |||
local result = table.concat(out, " ") | |||
return preprocess_or_return(frame, result) | |||
end | |||
local function encode_nowiki_json(value) | |||
local ok, json = pcall(mw.text.jsonEncode, value) | |||
if ok and json then | |||
return to_nowiki(json) | |||
end | |||
return nil | |||
end | |||
local function collect_sorted_keys(tbl, stringOnly) | |||
local keys = {} | |||
for k in pairs(tbl) do | |||
if not stringOnly or type(k) == "string" then | |||
keys[#keys + 1] = k | |||
end | |||
end | |||
table.sort(keys, function(a, b) | |||
return tostring(a) < tostring(b) | |||
end) | |||
return keys | |||
end | |||
local function choose_id_key(obj) | |||
local keys = {} | |||
for k in pairs(obj) do | |||
if type(k) == "string" then | |||
keys[#keys + 1] = k | |||
end | |||
end | |||
if #keys == 0 then | |||
return nil | |||
end | |||
table.sort(keys, function(a, b) | |||
local av = obj[a] | |||
local bv = obj[b] | |||
local aPrimitive = type(av) ~= "table" | |||
local bPrimitive = type(bv) ~= "table" | |||
if aPrimitive ~= bPrimitive then | |||
return not aPrimitive | |||
end | |||
return tostring(a) < tostring(b) | |||
end) | |||
return keys[1] | |||
end | |||
local function is_wrapper_block_key(key) | |||
return type(key) == "string" and not key:match("^[%a_][%w_]*$") | |||
end | |||
local function is_array_of_primitives(tbl) | |||
if type(tbl) ~= "table" or not is_array(tbl) then | |||
return false | |||
end | |||
for _, v in ipairs(tbl) do | |||
if type(v) == "table" then | |||
return false | |||
end | |||
end | |||
return true | |||
end | |||
local function append_table_fields(parts, value, options, prefix) | |||
if type(value) ~= "table" or next(value) == nil then | |||
return | |||
end | |||
if options.skipPrimitiveRoot and is_array_of_primitives(value) then | |||
return | |||
end | |||
if prefix and options.includeJsonAtPrefix then | |||
local json = encode_nowiki_json(value) | |||
if json then | |||
parts[#parts + 1] = prefix .. "=" .. tostring(json) | |||
end | |||
end | |||
local keys = collect_sorted_keys(value, false) | |||
for _, k in ipairs(keys) do | |||
if not (options.nestedKeyMode == "raw" and type(k) == "number") then | |||
local v = value[k] | |||
local key | |||
if prefix then | |||
key = prefix .. "." .. tostring(k) | |||
else | |||
key = tostring(k) | |||
end | |||
if type(v) == "table" then | |||
if is_array_of_primitives(v) then | |||
local json = encode_nowiki_json(v) | |||
if json then | |||
parts[#parts + 1] = key .. "=" .. tostring(json) | |||
end | |||
elseif options.nestedKeyMode == "raw" then | |||
local json = encode_nowiki_json(v) | |||
if json then | |||
parts[#parts + 1] = key .. "=" .. tostring(json) | |||
end | |||
end | |||
if next(v) ~= nil and not is_array_of_primitives(v) then | |||
local childPrefix | |||
if options.nestedKeyMode == "prefixed" then | |||
childPrefix = key | |||
elseif type(k) == "string" then | |||
childPrefix = key | |||
else | |||
childPrefix = nil | |||
end | |||
append_table_fields(parts, v, options, childPrefix) | |||
end | |||
else | |||
parts[#parts + 1] = key .. "=" .. tostring(v) | |||
end | |||
end | |||
end | |||
end | |||
function p.json(frame) | |||
local args = getArgs(frame, { removeBlanks = false }) | |||
local jsonStr = mw.text.unstripNoWiki(args[1] or args.json or "") | |||
local tplPath = mw.text.unstripNoWiki(args[2] or args.template or "") | |||
local tplArgs = args[3] or args.tplArgs or args.templateArgs or "" | |||
tplPath, tplArgs = split_template_spec(tplPath, tplArgs) | |||
if jsonStr == "" or tplPath == "" then | |||
return "" | |||
end | |||
local ok, data = pcall(mw.text.jsonDecode, jsonStr) | |||
if not ok or type(data) ~= "table" then | |||
return "" | |||
end | |||
local calls = {} | |||
local nestedOptions = { | |||
includeJsonAtPrefix = true, | |||
nestedKeyMode = "prefixed", | |||
skipPrimitiveRoot = false, | |||
} | |||
local rawTypeOptions = { | |||
includeJsonAtPrefix = false, | |||
nestedKeyMode = "raw", | |||
skipPrimitiveRoot = true, | |||
} | |||
local function is_object_map(tbl) | |||
local count = 0 | |||
for k, v in pairs(tbl) do | |||
if type(k) ~= "string" or type(v) ~= "table" then | |||
return false | |||
end | |||
count = count + 1 | |||
end | |||
return count > 1 | |||
end | |||
local function makeCall(obj) | |||
if type(obj) ~= "table" then | |||
return | |||
end | |||
local idKey = choose_id_key(obj) | |||
if not idKey then | |||
return | |||
end | |||
local parts = { "{{Шаблон:" .. resolve_template_path(tplPath) } | |||
if tplArgs ~= "" then | |||
parts[#parts + 1] = tplArgs | |||
end | |||
parts[#parts + 1] = tostring(idKey) | |||
local keys = collect_sorted_keys(obj, true) | |||
for _, k in ipairs(keys) do | |||
local v = obj[k] | |||
if k == idKey then | |||
if is_wrapper_block_key(k) then | |||
if type(v) == "table" then | |||
local json = encode_nowiki_json(v) | |||
if json then | |||
parts[#parts + 1] = "value=" .. tostring(json) | |||
end | |||
append_table_fields(parts, v, rawTypeOptions, nil) | |||
elseif v ~= nil then | |||
parts[#parts + 1] = "value=" .. tostring(v) | |||
end | |||
elseif type(v) == "table" then | |||
if is_array_of_primitives(v) then | |||
local json = encode_nowiki_json(v) | |||
if json then | |||
parts[#parts + 1] = "value=" .. tostring(json) | |||
end | |||
end | |||
if next(v) ~= nil then | |||
append_table_fields(parts, v, nestedOptions, k) | |||
append_table_fields(parts, v, rawTypeOptions, nil) | |||
end | |||
elseif v ~= nil then | |||
parts[#parts + 1] = "value=" .. tostring(v) | |||
parts[#parts + 1] = k .. "=" .. tostring(v) | |||
end | |||
else | |||
if type(v) == "table" then | |||
if next(v) ~= nil then | |||
append_table_fields(parts, v, nestedOptions, k) | |||
end | |||
elseif v ~= nil then | |||
parts[#parts + 1] = k .. "=" .. tostring(v) | |||
end | |||
end | |||
end | |||
parts[#parts + 1] = "}}" | |||
calls[#calls + 1] = table.concat(parts, "|") | |||
end | |||
if is_array(data) then | |||
for _, item in ipairs(data) do | |||
makeCall(item) | |||
end | |||
elseif is_object_map(data) then | |||
local keys = collect_sorted_keys(data, true) | |||
for _, k in ipairs(keys) do | |||
makeCall({ [k] = data[k] }) | |||
end | |||
else | |||
makeCall(data) | |||
end | |||
if #calls == 0 then | |||
return "" | |||
end | |||
return frame:preprocess(table.concat(calls, " ")) | |||
end | end | ||
function p. | function p.jsonList(frame) | ||
local args = getArgs(frame, { removeBlanks = false }) | |||
local jsonStr = mw.text.unstripNoWiki(args[1] or args.json or "") | |||
if jsonStr == "" then | |||
return "" | |||
end | |||
local ok, data = pcall(mw.text.jsonDecode, jsonStr) | |||
if not ok or type(data) ~= "table" then | |||
return "" | |||
end | |||
local outputType = (args.type or "list") | |||
local bullet = mw.text.unstripNoWiki(args.prefix or "* ") | |||
local sep = mw.text.unstripNoWiki(args.sep or ": ") | |||
if outputType == "none" then | |||
bullet = "" | |||
sep = "" | |||
elseif outputType == "revertList" then | |||
sep = mw.text.unstripNoWiki(args.sep or " ") | |||
end | |||
local keyPattern = mw.text.unstripNoWiki(args.key_pattern or "(.*)") | |||
local keyReplace = mw.text.unstripNoWiki(args.key_replace or "\\1") | |||
local valuePattern = mw.text.unstripNoWiki(args.value_pattern or "(.*)") | |||
local valueReplace = mw.text.unstripNoWiki(args.value_replace or "\\1") | |||
local pairPattern = mw.text.unstripNoWiki(args.pattern or "(.*)") | |||
local pairReplace = mw.text.unstripNoWiki(args.replace or "\\1") | |||
local out = {} | |||
if is_array(data) then | |||
for _, v in ipairs(data) do | |||
local text = "" | |||
if type(v) == "table" then | |||
if is_array(v) then | |||
text = table.concat(v, ", ") | |||
else | |||
local okJson, jsonVal = pcall(mw.text.jsonEncode, v) | |||
if okJson and jsonVal then | |||
text = jsonVal | |||
end | |||
end | |||
else | |||
text = tostring(v) | |||
end | |||
if text ~= "" then | |||
local patt = valuePattern ~= "" and valuePattern or keyPattern | |||
local repl = valueReplace ~= "" and valueReplace or keyReplace | |||
text = apply_pattern(text, patt, repl) | |||
local line | |||
if outputType == "enum" then | |||
line = text | |||
else | |||
line = bullet .. text | |||
end | |||
if pairPattern ~= "" then | |||
line = apply_pattern(line, pairPattern, pairReplace) | |||
end | |||
table.insert(out, line) | |||
end | |||
end | |||
else | |||
local keys = {} | |||
for k in pairs(data) do | |||
keys[#keys + 1] = k | |||
end | |||
table.sort(keys, function(a, b) | |||
return tostring(a) < tostring(b) | |||
end) | |||
for _, k in ipairs(keys) do | |||
local v = data[k] | |||
local vStr | |||
if type(v) == "table" then | |||
local okJson, jsonVal = pcall(mw.text.jsonEncode, v) | |||
if okJson and jsonVal then | |||
vStr = jsonVal | |||
else | |||
vStr = "" | |||
end | |||
else | |||
vStr = tostring(v) | |||
end | |||
local baseKey = apply_pattern(tostring(k), keyPattern, "\\1") | |||
local MARK_KEY = "\31KEY\31" | |||
local vRepl = (valueReplace or "\\1"):gsub("\\2", MARK_KEY) | |||
local vStr0 = apply_pattern(vStr, valuePattern, vRepl) | |||
vStr0 = tostring(vStr0):gsub(MARK_KEY, baseKey) | |||
local MARK_VAL = "\31VAL\31" | |||
local kRepl = (keyReplace or "\\1"):gsub("\\2", MARK_VAL) | |||
local keyStr0 = apply_pattern(tostring(k), keyPattern, kRepl) | |||
local keyStr = tostring(keyStr0):gsub(MARK_VAL, vStr0) | |||
vStr = vStr0 | |||
if vStr ~= "" then | |||
local line | |||
if outputType == "enum" then | |||
line = vStr .. " " .. keyStr | |||
elseif outputType == "revertList" then | |||
line = bullet .. vStr .. sep .. keyStr | |||
else | |||
line = bullet .. keyStr .. sep .. vStr | |||
end | |||
if pairPattern ~= "" then | |||
line = apply_pattern(line, pairPattern, pairReplace) | |||
end | |||
table.insert(out, line) | |||
end | |||
end | |||
end | |||
if outputType == "enum" then | |||
return frame:preprocess(table.concat(out, ", ")) | |||
elseif outputType == "list" or outputType == "revertList" then | |||
return frame:preprocess(table.concat(out, "\n")) | |||
else | |||
return frame:preprocess(table.concat(out, " ")) | |||
end | |||
end | end | ||
return p | return p | ||