Модуль:GetField: различия между версиями

мНет описания правки
Нет описания правки
 
(не показана 101 промежуточная версия этого же участника)
Строка 1: Строка 1:
local p = {}
local p = {}


local cache = {}
local JsonPaths = require('Module:JsonPaths')
local entryCache = {}
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+)%]$")
local key, idx = string.match(part, "^(.-)%[(%d+)%]$")
    if key then
if key then
        return key, tonumber(idx)
return key, tonumber(idx)
    end
end
    local num = tonumber(part)
 
    if num then
local num = tonumber(part)
        return nil, num
if num then
    end
return nil, num
    return part, nil
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)
    if not tbl or path == "" then return nil end
return get_by_parsed_path(tbl, parse_path(path))
    local cur = tbl
    for part in string.gmatch(path, "([^%.]+)") do
        local key, idx = parse_indexed_part(part)
        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 format_value(v)
local function format_value(v)
    if v == nil then return "" end
local okJson, json = pcall(mw.text.jsonEncode, v)
    local t = type(v)
if okJson and json == "null" then
    if t == "string" or t == "number" or t == "boolean" then
return "null"
        return tostring(v)
end
    elseif t == "table" then
 
        local max = 0
if v == nil then
        local hasNonNumber = false
return ""
        for k in pairs(v) do
end
            if type(k) ~= "number" then
 
                hasNonNumber = true
local t = type(v)
                break
if t == "string" or t == "number" or t == "boolean" then
            else
return tostring(v)
                if k > max then max = k end
elseif t == "table" then
            end
local ok, json2 = pcall(mw.text.jsonEncode, v)
        end
if ok and json2 then
        if not hasNonNumber and max > 0 then
return json2
            local out = {}
end
            for i = 1, max do
return ""
                out[#out + 1] = format_value(v[i])
else
            end
return tostring(v)
            return table.concat(out, ", ")
end
        else
            local out = {}
            for k, val in pairs(v) do
                out[#out + 1] = tostring(k) .. ": " .. format_value(val)
            end
            return table.concat(out, ", ")
        end
    else
        return tostring(v)
    end
end
end


local function to_nowiki(v)
local function to_nowiki(v)
    local s = tostring(v)
return "<nowiki>" .. v .. "</nowiki>"
    local ok, res = pcall(mw.text.nowiki, s)
    if ok and res then
        return res
    end
    return mw.text.nowiki(s)
end
end


local function is_array(tbl)
local function is_array(tbl)
    local max = 0
local max = 0
    local count = 0
local count = 0
    for k in pairs(tbl) do
for k in pairs(tbl) do
        if type(k) ~= "number" then
if type(k) ~= "number" then
            return false
return false
        end
end
        if k > max then max = k end
if k > max then
        count = count + 1
max = k
    end
end
    return count > 0 and max == count
count = count + 1
end
return count > 0 and max == count
end
end


local function deep_copy(src)
local function deep_copy(src)
    local dst = {}
local dst = {}
    for k, v in pairs(src) do
for k, v in pairs(src) do
        if type(v) == "table" then
if type(v) == "table" then
            dst[k] = deep_copy(v)
dst[k] = deep_copy(v)
        else
else
            dst[k] = v
dst[k] = v
        end
end
    end
end
    return dst
return dst
end
end


local function deep_merge(dst, src)
local function deep_merge(dst, src)
    for k, v in pairs(src) do
for k, v in pairs(src) do
        if type(v) == "table" and type(dst[k]) == "table" then
if type(v) == "table" and type(dst[k]) == "table" then
            deep_merge(dst[k], v)
deep_merge(dst[k], v)
        elseif type(v) == "table" then
elseif type(v) == "table" then
            dst[k] = deep_copy(v)
dst[k] = deep_copy(v)
        else
else
            dst[k] = v
dst[k] = v
        end
end
    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
 
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
end


function p.findInProto(frame)
local function flatten_selected_parts(entry, keys)
    local args = frame.args or {}
if type(entry) ~= "table" or type(keys) ~= "table" then
    local protoKey = args[1] or ""
return {}
    local fieldId = args[2] or ""
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


    if protoKey == "" or fieldId == "" then
function p.findInGenerator(frame)
        return "[]"
local args = frame.args or {}
    end
local searchId = args[1] or ""
local kind = (args[2] or ""):lower()
local fieldId = args[3] or ""


    local moduleName = "Module:IanComradeBot/prototype.json/data"
if searchId == "" or fieldId == "" then
return ""
end
if kind ~= "prototype" and kind ~= "component" then
return ""
end


    local data = cache[moduleName]
local storeName = (kind == "prototype") and "prototype_store.json" or "component_store.json"
    if not data then
local moduleName = get_module_name(storeName)
        local ok, loaded = pcall(mw.loadData, moduleName)
local data = load_cached_data(moduleName)
        if not ok or not loaded then
if not data then
            return "[]"
return ""
        end
end
        data = loaded
        cache[moduleName] = data
    end


    local proto = data[protoKey]
local entry = data[searchId]
    if type(proto) ~= "table" then
if type(entry) ~= "table" then
        return "[]"
return ""
    end
end


    local value = proto[fieldId]
local value = get_field_loose(entry, fieldId)
    if value == nil then
if value == nil then
        return "[]"
return ""
    end
end


    local out = {}
local out = {}
    local t = type(value)
local t = type(value)
    if t == "table" then
if t == "table" then
        for _, v in ipairs(value) do
for _, v in ipairs(value) do
            out[#out + 1] = v
out[#out + 1] = v
        end
end
    else
else
        out[1] = value
out[1] = value
    end
end


    return mw.text.jsonEncode(out)
return mw.text.jsonEncode(out)
end
end


function p.flattenField(frame)
function p.flattenField(frame)
    local args = frame.args or {}
local args = frame.args or {}
    local id = args[1] or ""
local id = args[1] or ""
    local pagePath = args[2] or ""
local pagePath = args[2] or ""
    if id == "" or pagePath == "" then return "" end
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 baseUser = "IanComradeBot/"
local entry = resolve_entry(data, id) or {}
    local moduleName = "Module:" .. baseUser .. pagePath .. "/data"
local parts = flatten_selected_parts(entry, keys)
if #parts == 0 then
return ""
end


    local data = cache[moduleName]
return table.concat(parts, "|")
    if not data then
end
        local ok, loaded = pcall(mw.loadData, moduleName)
        if not ok or not loaded then return "" end
        data = loaded
        cache[moduleName] = data
    end


    local entry = data[id]
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 entry == nil then
if pagePath == "" then
        local idsTable = data.id
return ""
        if type(idsTable) == "table" then
end
            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)
                    entry = merged
                else
                    entry = deep_copy(specific)
                end
            end
        end
    end


    if entry == nil then
local moduleName = get_module_name(pagePath)
        local base = data["default"]
local data = load_cached_data(moduleName)
        if type(base) == "table" then
if not data then
            entry = deep_copy(base)
return ""
        else
end
            entry = {}
        end
    end


    if type(entry) ~= "table" then return "" end
local entry = resolve_entry(data, id)
if entry == nil then
return ""
end


    local parts = {}
if keyPath == "" then
    local function walk(tbl, prefix)
return format_value(entry)
        local keys = {}
end
        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 key = (prefix == "" and tostring(k) or prefix .. "." .. tostring(k))
            if type(v) == "table" then
                if is_array(v) then
                    local ok, json = pcall(mw.text.jsonEncode, v)
                    if ok and json then
                        parts[#parts + 1] = key .. "=" .. to_nowiki(json)
                    end
                else
                    local ok, json = pcall(mw.text.jsonEncode, v)
                    if ok and json then
                        parts[#parts + 1] = key .. "=" .. to_nowiki(json)
                    end
                    walk(v, key)
                end
            else
                parts[#parts + 1] = key .. "=" .. to_nowiki(v)
            end
        end
    end


    walk(entry, "")
local value = get_by_path(entry, keyPath)
    return table.concat(parts, "|")
return format_value(value)
end
end


function p.get(frame)
local function collect_by_parsed_path(tbl, parsedPath, pos, out)
    local args = frame.args or {}
if pos > #parsedPath then
    local id = args[1] or ""
out[#out + 1] = tbl
    local pagePath = args[2] or ""
return
    local keyPath = args[3] or ""
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


    if pagePath == "" then return "" end
local matches = {}


    local baseUser = "IanComradeBot/"
if searchType == "path" then
    local moduleName = "Module:" .. baseUser .. pagePath .. "/data"
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


    local data = cache[moduleName]
if #matches == 0 then
    if not data then
return ""
        local ok, loaded = pcall(mw.loadData, moduleName)
end
        if not ok or not loaded then return "" end
        data = loaded
        cache[moduleName] = data
    end


    local entryKey = moduleName .. "|" .. (id ~= "" and id or "default")
local out = {}
    local entry = entryCache[entryKey]
for _, idKey in ipairs(matches) do
    if not entry then
local tpl = build_tpl(idKey, pagePath, tplPath, data, tplArgs)
        if id ~= "" then entry = data[id] end
if tpl ~= "" then
        if entry == nil then entry = data["default"] end
out[#out + 1] = tpl
        entryCache[entryKey] = entry
end
    end
end
    if entry == nil then return "" end


    if keyPath == "" then
if #out == 0 then
        return format_value(entry)
return ""
    end
end


    local value = get_by_path(entry, keyPath)
local result = table.concat(out, " ")
    return format_value(value)
return preprocess_or_return(frame, result)
end
end


function p.getTpl(frame)
function p.getTpl(frame)
    local args = frame.args or {}
local args = getArgs(frame, { removeBlanks = false })
    local id = args[1] or ""
local id = args[1] or ""
    local pagePath = args[2] or ""
local pagePath = args[2] or ""
    local tplPath = args[3] 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 id == "" or pagePath == "" or tplPath == "" then
if type(data) ~= "table" then
        return ""
return "false"
    end
end


    local extra = p.flattenField({ args = { id, pagePath } }) or ""
local entry = data[entityId]
    local tplStr = "{{" .. tostring(tplPath) .. "|id=" .. tostring(id)
if type(entry) ~= "table" then
    if extra ~= "" then
return "false"
        tplStr = tplStr .. "|" .. extra
end
    end
    tplStr = tplStr .. "}}"


    if type(frame.preprocess) == "function" then
local target = tostring(compName)
        return frame:preprocess(tplStr)
for _, v in ipairs(entry) do
    end
if tostring(v) == target then
return "true"
end
end


    return tplStr
return "false"
end
end


function p.getTplProto(frame)
function p.getAll(frame)
    local args = frame.args or {}
local args = getArgs(frame, { removeBlanks = false })
    local searchId = args[1] or ""
local pagePath = args[1] or ""
    local protoId = args[2] or ""
local replace = mw.text.unstripNoWiki(args.replace or "")
    local tplPath = args[3] or ""
local pattern = mw.text.unstripNoWiki(args.pattern or "(.*)")
    local pagePath = "prototype/" .. protoId .. ".json"
 
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
 
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)


    if searchId == "" or protoId == "" or tplPath == "" then
vStr = vStr0
        return ""
    end


    local idsJson = p.findInProto({ args = { searchId, protoId } })
if vStr ~= "" then
    local ok, ids = pcall(mw.text.jsonDecode, idsJson or "")
local line
    if not ok or type(ids) ~= "table" or #ids == 0 then
if outputType == "enum" then
        return ""
line = vStr .. " " .. keyStr
    end
elseif outputType == "revertList" then
line = bullet .. vStr .. sep .. keyStr
else
line = bullet .. keyStr .. sep .. vStr
end


    local out = {}
if pairPattern ~= "" then
    for _, id in ipairs(ids) do
line = apply_pattern(line, pairPattern, pairReplace)
        local tpl = p.getTpl({ args = { id, pagePath, tplPath } })
end
        if tpl ~= "" then
            out[#out + 1] = tpl
        end
    end


    local result = table.concat(out, "\n")
table.insert(out, line)
    if type(frame.preprocess) == "function" then
end
        return frame:preprocess(result)
end
    end
end


    return result
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