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

мНет описания правки
мНет описания правки
 
(не показано 80 промежуточных версий этого же участника)
Строка 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)
    local okJson, json = pcall(mw.text.jsonEncode, v)
local okJson, json = pcall(mw.text.jsonEncode, v)
    if okJson and json == "null" then
if okJson and json == "null" then
        return "null"
return "null"
    end
end


    if v == nil then return "" end
if v == nil then
return ""
end


    local t = type(v)
local t = type(v)
    if t == "string" or t == "number" or t == "boolean" then
if t == "string" or t == "number" or t == "boolean" then
        return tostring(v)
return tostring(v)
    elseif t == "table" then
elseif t == "table" then
        local ok, json2 = pcall(mw.text.jsonEncode, v)
local ok, json2 = pcall(mw.text.jsonEncode, v)
        if ok and json2 then
if ok and json2 then
            return json2
return json2
        end
end
        return ""
return ""
    else
else
        return tostring(v)
return tostring(v)
    end
end
end
end


local function to_nowiki(v)
local function to_nowiki(v)
    local s = tostring(v)
return "<nowiki>" .. v .. "</nowiki>"
    return "<nowiki>" .. s .. "</nowiki>"
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
end


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


    if id and id ~= "" then
if id and id ~= "" then
        local direct = data[id]
local direct = data[id]
        if direct ~= nil then
if direct ~= nil then
            return direct
return direct
        end
end


        local idsTable = data.id
local idsTable = data.id
        if type(idsTable) == "table" then
if type(idsTable) == "table" then
            local specific = idsTable[id]
local specific = idsTable[id]
            if type(specific) == "table" then
if type(specific) == "table" then
                local base = data["default"]
local base = data["default"]
                if type(base) == "table" then
if type(base) == "table" then
                    local merged = deep_copy(base)
local merged = deep_copy(base)
                    deep_merge(merged, specific)
deep_merge(merged, specific)
                    return merged
return merged
                end
end
                return deep_copy(specific)
return deep_copy(specific)
            end
end
        end
end
    end
end


    local base = data["default"]
local base = data["default"]
    if type(base) == "table" then
if type(base) == "table" then
        return deep_copy(base)
return deep_copy(base)
    end
end
    return nil
 
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
 
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
end


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


    if searchId == "" or fieldId == "" then
if searchId == "" or fieldId == "" then
        return ""
return ""
    end
end
    if kind ~= "prototype" and kind ~= "component" then
if kind ~= "prototype" and kind ~= "component" then
        return ""
return ""
    end
end


    local baseUser = "IanComradeBot/"
local storeName = (kind == "prototype") and "prototype_store.json" or "component_store.json"
    local storeName
local moduleName = get_module_name(storeName)
    if kind == "prototype" then
local data = load_cached_data(moduleName)
        storeName = "prototype_store.json"
if not data then
    else
return ""
        storeName = "component_store.json"
end
    end
    local moduleName = "Module:" .. baseUser .. storeName .. "/data"


    local data = cache[moduleName]
local entry = data[searchId]
    if not data then
if type(entry) ~= "table" then
        local ok, loaded = pcall(mw.loadData, moduleName)
return ""
        if not ok or not loaded then
end
            return ""
        end
        data = loaded
        cache[moduleName] = data
    end


    local entry = data[searchId]
local value = get_field_loose(entry, fieldId)
    if type(entry) ~= "table" then
if value == nil then
        return ""
return ""
    end
end


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


    local out = {}
return mw.text.jsonEncode(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 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 baseUser = "IanComradeBot/"
local moduleName = get_module_name(pagePath)
    local moduleName = "Module:" .. baseUser .. pagePath .. "/data"
local data = load_cached_data(moduleName)
if not data then
return ""
end


    local data = cache[moduleName]
local entry = resolve_entry(data, id) or {}
    if not data then
return flatten_entry(entry)
        local ok, loaded = pcall(mw.loadData, moduleName)
end
        if not ok or not loaded then return "" end
        data = loaded
        cache[moduleName] = data
    end


    local entry = resolve_entry(data, id) or {}
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


    if type(entry) ~= "table" 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 parts = {}
local moduleName = get_module_name(pagePath)
    local function walk(tbl, prefix)
local data = load_cached_data(moduleName)
        local keys = {}
if not data then
        for k in pairs(tbl) do keys[#keys + 1] = k end
return ""
        table.sort(keys, function(a, b) return tostring(a) < tostring(b) end)
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 next(v) == nil then
                elseif 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 .. "=" .. tostring(v)
            end
        end
    end


    walk(entry, "")
local entry = resolve_entry(data, id) or {}
local parts = flatten_selected_parts(entry, keys)
if #parts == 0 then
return ""
end


    return table.concat(parts, "|")
return table.concat(parts, "|")
end
end


function p.get(frame)
function p.get(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 keyPath = args[3] 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


    if pagePath == "" then return "" end
local function collect_by_parsed_path(tbl, parsedPath, pos, out)
if pos > #parsedPath then
out[#out + 1] = tbl
return
end


    local baseUser = "IanComradeBot/"
if type(tbl) ~= "table" then
    local moduleName = "Module:" .. baseUser .. pagePath .. "/data"
return
end


    local data = cache[moduleName]
local token = parsedPath[pos]
    if not data then
local key = token[1]
        local ok, loaded = pcall(mw.loadData, moduleName)
local idx = token[2]
        if not ok or not loaded then return "" end
        data = loaded
        cache[moduleName] = data
    end


    local entryKey = moduleName .. "|" .. (id ~= "" and id or "default")
if key == "*" then
    local entry = entryCache[entryKey]
for _, child in pairs(tbl) do
    if not entry then
local nextCur = child
        entry = resolve_entry(data, id)
        entryCache[entryKey] = entry
    end
    if entry == nil then return "" end


    if keyPath == "" then
if idx then
        return format_value(entry)
if type(nextCur) ~= "table" then
    end
nextCur = nil
else
nextCur = nextCur[idx]
end
end


    local value = get_by_path(entry, keyPath)
collect_by_parsed_path(nextCur, parsedPath, pos + 1, out)
    return format_value(value)
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
end


function p.getId(frame)
local function get_by_parsed_path_multi(tbl, parsedPath)
    local args = frame.args or {}
local out = {}
    local searchValue = args[1] or ""
collect_by_parsed_path(tbl, parsedPath, 1, out)
    local pagePath = args[2] or ""
return out
    local keyPath = args[3] or ""
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


    if searchValue == "" or pagePath == "" or keyPath == "" then
return false
        return ""
end
    end


    local baseUser = "IanComradeBot/"
local function entry_has_any_nonempty_path(entry, parsedPath)
    local moduleName = "Module:" .. baseUser .. pagePath .. "/data"
local values = get_by_parsed_path_multi(entry, parsedPath)


    local data = cache[moduleName]
for _, v in ipairs(values) do
    if not data then
if is_nonempty_value(v) then
        local ok, loaded = pcall(mw.loadData, moduleName)
return true
        if not ok or not loaded then return "[]" end
end
        data = loaded
end
        cache[moduleName] = data
    end


    local idsTable = data.id
return false
    if type(idsTable) ~= "table" then
end
        return ""
    end


    local target = tostring(searchValue)
function p.searchId(frame)
    local matches = {}
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()


    for idKey, entry in pairs(idsTable) do
if searchValue == "" or pagePath == "" or keyPath == "" then
        if type(entry) == "table" then
return ""
            local v = get_by_path(entry, keyPath)
end
            if v ~= nil then
if searchType == "" then
                if type(v) == "table" then
searchType = "value"
                    if is_array(v) then
end
                        for _, item in ipairs(v) do
                            if tostring(item) == target then
                                matches[#matches + 1] = idKey
                                break
                            end
                        end
                    else
                        for _, item in pairs(v) do
                            if tostring(item) == target then
                                matches[#matches + 1] = idKey
                                break
                            end
                        end
                    end
                else
                    if tostring(v) == target then
                        matches[#matches + 1] = idKey
                    end
                end
            end
        end
    end


    if #matches == 0 then
local moduleName = get_module_name(pagePath)
        return ""
local data = load_cached_data(moduleName)
    end
if not data then
return "[]"
end


    local ok, json = pcall(mw.text.jsonEncode, matches)
local parsedPath = parse_path(keyPath)
    if ok and json then
if not parsedPath then
        return json
return ""
    end
end


    return ""
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
end


function p.getTplId(frame)
function p.searchIdTpl(frame)
    local args = frame.args or {}
local args = getArgs(frame, { removeBlanks = false })
    local searchValue = args[1] or ""
local searchValue = args[1] or ""
    local pagePath = args[2] or ""
local pagePath = args[2] or ""
    local keyPath = args[3] or ""
local keyPath = args[3] or ""
    local tplPath = mw.text.unstripNoWiki(args[4] or "")
local tplPath = mw.text.unstripNoWiki(args[4] or "")
local tplArgs = args.tplArgs or args.templateArgs or ""
local searchType = (args.searchType or ""):lower()


    if searchValue == "" or pagePath == "" or keyPath == "" or tplPath == "" then
if searchType == "" then
        return ""
searchType = "value"
    end
end


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


    local data = cache[moduleName]
tplPath, tplArgs = split_template_spec(tplPath, tplArgs)
    if not data then
        local ok, loaded = pcall(mw.loadData, moduleName)
        if not ok or not loaded then return "" end
        data = loaded
        cache[moduleName] = data
    end


    local idsTable = data.id
if pagePath == "" or keyPath == "" or tplPath == "" then
    if type(idsTable) ~= "table" then
return ""
        return ""
end
    end
if searchType ~= "path" and searchValue == "" then
return ""
end


    local target = tostring(searchValue)
local moduleName = get_module_name(pagePath)
    local matches = {}
local data = load_cached_data(moduleName)
if not data then
return ""
end


    for idKey, entry in pairs(idsTable) do
local parsedPath = parse_path(keyPath)
        if type(entry) == "table" then
if not parsedPath then
            local v = get_by_path(entry, keyPath)
return ""
            if v ~= nil then
end
                if type(v) == "table" then
                    if is_array(v) then
                        for _, item in ipairs(v) do
                            if tostring(item) == target then
                                matches[#matches + 1] = idKey
                                break
                            end
                        end
                    else
                        for _, item in pairs(v) do
                            if tostring(item) == target then
                                matches[#matches + 1] = idKey
                                break
                            end
                        end
                    end
                else
                    if tostring(v) == target then
                        matches[#matches + 1] = idKey
                    end
                end
            end
        end
    end


    if #matches == 0 then
local ids = collect_id_keys(data)
        return ""
if #ids == 0 then
    end
return ""
end


    local out = {}
local matches = {}
    for _, idKey in ipairs(matches) do
        local tpl = p.getTpl({ args = { idKey, pagePath, tplPath } })
        if tpl ~= "" then
            out[#out + 1] = tpl
        end
    end


    if #out == 0 then
if searchType == "path" then
        return ""
for _, idKey in ipairs(ids) do
    end
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 result = table.concat(out, " ")
if #matches == 0 then
    if type(frame.preprocess) == "function" then
return ""
        return frame:preprocess(result)
end
    end


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


    if id == "" or pagePath == "" or tplPath == "" then
local function is_wrapper_block_key(key)
        return ""
return type(key) == "string" and not key:match("^[%a_][%w_]*$")
    end
end


    local extra = p.flattenField({ args = { id, pagePath } }) or ""
local function is_array_of_primitives(tbl)
    local tplStr = "{{" .. tostring(tplPath) .. "|id=" .. tostring(id)
if type(tbl) ~= "table" or not is_array(tbl) then
    if extra ~= "" then
return false
        tplStr = tplStr .. "|" .. extra
end
    end
    tplStr = tplStr .. "}}"


    if type(frame.preprocess) == "function" then
for _, v in ipairs(tbl) do
        return frame:preprocess(tplStr)
if type(v) == "table" then
    end
return false
end
end


    return tplStr
return true
end
end


function p.getTplGenerator(frame)
local function append_table_fields(parts, value, options, prefix)
    local args = frame.args or {}
if type(value) ~= "table" or next(value) == nil then
    local searchId = args[1] or ""
return
    local kind = (args[2] or ""):lower()
end
    local generatorId = args[3] or ""
 
    local tplPath = args[4] or ""
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


    if searchId == "" or generatorId == "" or tplPath == "" then
local function makeCall(obj)
        return ""
if type(obj) ~= "table" then
    end
return
    if kind ~= "prototype" and kind ~= "component" then
end
        return ""
    end


    local dir = (kind == "prototype") and "prototype/" or "component/"
local idKey = choose_id_key(obj)
    local pagePath = dir .. generatorId .. ".json"
if not idKey then
return
end


    local idsJson = p.findInGenerator({ args = { searchId, kind, generatorId } })
local parts = { "{{Шаблон:" .. resolve_template_path(tplPath) }
    local ok, ids = pcall(mw.text.jsonDecode, idsJson or "")
    if not ok or type(ids) ~= "table" or #ids == 0 then
        return ""
    end


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


    local result = table.concat(out, " ")
parts[#parts + 1] = tostring(idKey)
    if type(frame.preprocess) == "function" then
local keys = collect_sorted_keys(obj, true)
        return frame:preprocess(result)
    end


    return result
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
if type(item) == "table" then
makeCall(item)
elseif item ~= nil and item ~= "" then
makeCall({ [item] = {} })
end
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.hasComp(frame)
function p.jsonList(frame)
    local args = frame.args or {}
local args = getArgs(frame, { removeBlanks = false })
    local entityId = args[1] or ""
local jsonStr = mw.text.unstripNoWiki(args[1] or args.json or "")
    local compName = args[2] 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")


    if entityId == "" or compName == "" then
local MARK_KEY = "\31KEY\31"
        return "false"
local vRepl = (valueReplace or "\\1"):gsub("\\2", MARK_KEY)
    end
local vStr0 = apply_pattern(vStr, valuePattern, vRepl)
vStr0 = tostring(vStr0):gsub(MARK_KEY, baseKey)


    local moduleName = "Module:IanComradeBot/component.json/data"
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)


    local data = cache[moduleName]
vStr = vStr0
    if not data then
        local ok, loaded = pcall(mw.loadData, moduleName)
        if not ok or not loaded then
            return "false"
        end
        data = loaded
        cache[moduleName] = data
    end


    if type(data) ~= "table" then
if vStr ~= "" then
        return "false"
local line
    end
if outputType == "enum" then
line = vStr .. " " .. keyStr
elseif outputType == "revertList" then
line = bullet .. vStr .. sep .. keyStr
else
line = bullet .. keyStr .. sep .. vStr
end


    local entry = data[entityId]
if pairPattern ~= "" then
    if type(entry) ~= "table" then
line = apply_pattern(line, pairPattern, pairReplace)
        return "false"
end
    end


    for _, v in ipairs(entry) do
table.insert(out, line)
        if tostring(v) == tostring(compName) then
end
            return "true"
end
        end
end
    end


    return "false"
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