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

Нет описания правки
Метка: отменено
мНет описания правки
 
(не показана 51 промежуточная версия этого же участника)
Строка 1: Строка 1:
local p = {}
local p = {}
local JsonPaths = require('Module:JsonPaths')
local JsonPaths = require('Module:JsonPaths')
local getArgs = require('Module:Arguments').getArgs
local getArgs = require('Module:Arguments').getArgs


local function get_module_name(pagePath)
local function get_module_name(pagePath)
    return JsonPaths.get(pagePath)
return JsonPaths.get(pagePath)
end
end


local function load_cached_data(moduleName)
local function load_cached_data(moduleName)
    local ok, loaded = pcall(mw.loadData, moduleName)
local ok, loaded = pcall(mw.loadData, moduleName)
    if not ok or not loaded then
if not ok or not loaded then
        return nil
return nil
    end
end
    return loaded
return loaded
end
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)
    return "<nowiki>" .. v .. "</nowiki>"
return "<nowiki>" .. v .. "</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
end


local function collect_id_keys(data)
local function resolve_path_value(data, id, parsedPath)
    if type(data) ~= "table" then
if type(data) ~= "table" or not parsedPath or not id or id == "" then
        return {}
return nil
    end
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
local idsTable = data.id
    local ids = {}
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


    if type(idsTable) == "table" then
local base = data["default"]
        for k in pairs(idsTable) do
if type(base) == "table" then
            ids[#ids + 1] = k
return get_by_parsed_path(base, parsedPath)
        end
end
        return ids
    end


    for k in pairs(data) do
return nil
        if k ~= "default" and k ~= "id" then
            ids[#ids + 1] = k
        end
    end
    return ids
end
end


local function contains_target(v, target)
local function resolve_entry_path_value(data, id, parsedPath)
    if type(v) == "table" then
if type(data) ~= "table" or not parsedPath or not id or id == "" then
        if is_array(v) then
return nil
            for _, item in ipairs(v) do
end
                if tostring(item) == target then
                    return true
                end
            end
            return false
        end


        for _, item in pairs(v) do
local entry = resolve_entry(data, id)
            if tostring(item) == target then
if type(entry) ~= "table" then
                return true
return nil
            end
end
        end
        return false
    end


    return tostring(v) == target
return get_by_parsed_path(entry, parsedPath)
end
end


local function is_nonempty_value(v)
local function collect_id_keys(data)
    if v == nil then return false end
if type(data) ~= "table" then
    if type(v) == "table" then
return {}
        return next(v) ~= nil
end
    end
 
    return true
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
end


local function find_matching_ids(idsTable, keyPath, searchValue)
local function contains_target(v, target)
    local target = tostring(searchValue)
if type(v) == "table" then
    local matches = {}
if is_array(v) then
for _, item in ipairs(v) do
if tostring(item) == target then
return true
end
end
return false
end


    for idKey, entry in pairs(idsTable) do
for _, item in pairs(v) do
        if type(entry) == "table" then
if tostring(item) == target then
            local v = get_by_path(entry, keyPath)
return true
            if v ~= nil and contains_target(v, target) then
end
                matches[#matches + 1] = idKey
end
            end
return false
        end
end
    end


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


local function preprocess_or_return(frame, text)
local function preprocess_or_return(frame, text)
    if type(frame.preprocess) == "function" then
if type(frame) == "table" and type(frame.preprocess) == "function" then
        return frame:preprocess(text)
return frame:preprocess(text)
    end
end
    return text
return text
end
end


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


    local first = string.sub(fieldId, 1, 1)
local first = string.sub(fieldId, 1, 1)
    local tail = string.sub(fieldId, 2)
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]
value = entry[string.lower(first) .. tail]
if value ~= nil then
return value
end
 
return entry[string.upper(first) .. tail]
end
end


local function apply_pattern(s, pattern, repl)
local function apply_pattern(s, pattern, repl)
    if not pattern or pattern == "" or not s then
if not pattern or pattern == "" or not s then
        return s
return s
    end
end


    local text = tostring(s)
local text = tostring(s)
    local replacement
local replacement
    if repl and repl ~= "" then
if repl and repl ~= "" then
        replacement = tostring(repl)
replacement = tostring(repl)
        replacement = replacement:gsub("\\(%d)", "%%%1")
replacement = replacement:gsub("\\(%d)", "%%%1")
    else
else
        replacement = "%1"
replacement = "%1"
    end
end


    local patt = pattern
local patt = pattern
    if not patt:find("%^") and not patt:find("%$") then
if not patt:find("%^") and not patt:find("%$") then
        patt = "^" .. patt .. "$"
patt = "^" .. patt .. "$"
    end
end


    return (text:gsub(patt, replacement))
return (text:gsub(patt, replacement))
end
end


local function flatten_parts(entry)
local function flatten_parts(entry)
    if type(entry) ~= "table" then return {} end
if type(entry) ~= "table" then
return {}
end


    local parts = {}
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 function append_table_json(key, value)
        local keys = {}
local ok, json = pcall(mw.text.jsonEncode, value)
        for k in pairs(tbl) do keys[#keys + 1] = k end
if ok and json then
        table.sort(keys, function(a, b) return tostring(a) < tostring(b) end)
parts[#parts + 1] = key .. "=" .. to_nowiki(json)
        for _, k in ipairs(keys) do
end
            local v = tbl[k]
end
            local kStr = tostring(k)
            local key = (prefix == "" and kStr or prefix .. "." .. kStr)
            if type(v) == "table" then
                if next(v) == nil then
                else
                    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, "")
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)


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


local function flatten_entry(entry)
local function flatten_entry(entry)
    local parts = flatten_parts(entry)
local parts = flatten_parts(entry)
    if #parts == 0 then
if #parts == 0 then
        return ""
return ""
    end
end
    return table.concat(parts, "|")
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 = getArgs(frame, { removeBlanks = false })
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 storeName = (kind == "prototype") and "prototype_store.json" or "component_store.json"
local storeName = (kind == "prototype") and "prototype_store.json" or "component_store.json"
    local moduleName = get_module_name(storeName)
local moduleName = get_module_name(storeName)
    local data = load_cached_data(moduleName)
local data = load_cached_data(moduleName)
    if not data then
if not data then
        return ""
return ""
    end
end


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


    local value = get_field_loose(entry, 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 = getArgs(frame, { removeBlanks = false })
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 moduleName = get_module_name(pagePath)
local entry = resolve_entry(data, id) or {}
    local data = load_cached_data(moduleName)
local parts = flatten_selected_parts(entry, keys)
    if not data then return "" end
if #parts == 0 then
return ""
end


    local entry = resolve_entry(data, id) or {}
return table.concat(parts, "|")
    return flatten_entry(entry)
end
end


function p.get(frame)
function p.get(frame)
    local args = getArgs(frame, { removeBlanks = false })
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
if pagePath == "" then
return ""
end


    local moduleName = get_module_name(pagePath)
local moduleName = get_module_name(pagePath)
    local data = load_cached_data(moduleName)
local data = load_cached_data(moduleName)
    if not data then return "" end
if not data then
return ""
end


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


    if keyPath == "" then
if keyPath == "" then
        return format_value(entry)
return format_value(entry)
    end
end


    local value = get_by_path(entry, keyPath)
local value = get_by_path(entry, keyPath)
    return format_value(value)
return format_value(value)
end
end


function p.getId(frame)
local function collect_by_parsed_path(tbl, parsedPath, pos, out)
    local args = getArgs(frame, { removeBlanks = false })
if pos > #parsedPath then
    local searchValue = args[1] or ""
out[#out + 1] = tbl
    local pagePath = args[2] or ""
return
    local keyPath = args[3] or ""
end
    local searchType = (args.searchType or ""):lower()
 
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


    if searchValue == "" or pagePath == "" or keyPath == "" then
local function get_by_parsed_path_multi(tbl, parsedPath)
        return ""
local out = {}
    end
collect_by_parsed_path(tbl, parsedPath, 1, out)
    if searchType == "" then
return out
        searchType = "value"
end
    end


    local moduleName = get_module_name(pagePath)
local function entry_matches_path(entry, parsedPath, searchValue, searchType)
    local data = load_cached_data(moduleName)
local values = get_by_parsed_path_multi(entry, parsedPath)
    if not data then return "[]" end
local target = tostring(searchValue)


    local ids = collect_id_keys(data)
for _, v in ipairs(values) do
    if #ids == 0 then
if searchType == "key" then
        return ""
if type(v) == "table" and v[target] ~= nil then
    end
return true
end
else
if contains_target(v, target) then
return true
end
end
end


    local matches
return false
    if searchType == "key" then
end
        local target = tostring(searchValue)
        matches = {}
        for _, idKey in ipairs(ids) do
            local entry = resolve_entry(data, idKey)
            if type(entry) == "table" then
                local v = get_by_path(entry, keyPath)
                if type(v) == "table" and v[target] ~= nil then
                    matches[#matches + 1] = idKey
                end
            end
        end
    else
        local target = tostring(searchValue)
        matches = {}
        for _, idKey in ipairs(ids) do
            local entry = resolve_entry(data, idKey)
            if type(entry) == "table" then
                local v = get_by_path(entry, keyPath)
                if v ~= nil and contains_target(v, target) then
                    matches[#matches + 1] = idKey
                end
            end
        end
    end


    if #matches == 0 then
local function entry_has_any_nonempty_path(entry, parsedPath)
        return ""
local values = get_by_parsed_path_multi(entry, parsedPath)
    end


    local ok, json = pcall(mw.text.jsonEncode, matches)
for _, v in ipairs(values) do
    if ok and json then
if is_nonempty_value(v) then
        return json
return true
    end
end
end


    return ""
return false
end
end


function p.getTplId(frame)
function p.searchId(frame)
    local args = getArgs(frame, { removeBlanks = false })
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 searchType = (args.searchType or ""):lower()
    local searchType = (args.searchType or ""):lower()
 
if searchValue == "" or pagePath == "" or keyPath == "" then
return ""
end
if searchType == "" then
searchType = "value"
end


    if searchType == "" then
local moduleName = get_module_name(pagePath)
        searchType = "value"
local data = load_cached_data(moduleName)
    end
if not data then
    if searchType == "path" then
return "[]"
        searchValue = ""
end
        pagePath = args[1] or ""
        keyPath = args[2] or ""
        tplPath = mw.text.unstripNoWiki(args[3] or "")
    end
    if pagePath == "" or keyPath == "" or tplPath == "" then
        return ""
    end
    if searchType ~= "path" and searchValue == "" then
        return ""
    end


    local moduleName = get_module_name(pagePath)
local parsedPath = parse_path(keyPath)
    local data = load_cached_data(moduleName)
if not parsedPath then
    if not data then return "" end
return ""
end


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


    local matches
local matches = {}
    if searchType == "path" then
local target = tostring(searchValue)
        matches = {}
        for _, idKey in ipairs(ids) do
            local entry = resolve_entry(data, idKey)
            if type(entry) == "table" then
                local v = get_by_path(entry, keyPath)
                if is_nonempty_value(v) then
                    matches[#matches + 1] = idKey
                end
            end
        end
    elseif searchType == "key" then
        local target = tostring(searchValue)
        matches = {}
        for _, idKey in ipairs(ids) do
            local entry = resolve_entry(data, idKey)
            if type(entry) == "table" then
                local v = get_by_path(entry, keyPath)
                if type(v) == "table" and v[target] ~= nil then
                    matches[#matches + 1] = idKey
                end
            end
        end
    else
        local target = tostring(searchValue)
        matches = {}
        for _, idKey in ipairs(ids) do
            local entry = resolve_entry(data, idKey)
            if type(entry) == "table" then
                local v = get_by_path(entry, keyPath)
                if v ~= nil and contains_target(v, target) then
                    matches[#matches + 1] = idKey
                end
            end
        end
    end


    if #matches == 0 then
for _, idKey in ipairs(ids) do
        return ""
local entry = resolve_entry(data, idKey)
    end
if type(entry) == "table" and entry_matches_path(entry, parsedPath, target, searchType) then
matches[#matches + 1] = idKey
end
end


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


    if #out == 0 then
local ok, json = pcall(mw.text.jsonEncode, matches)
        return ""
if ok and json then
    end
return json
end


    local result = table.concat(out, " ")
return ""
    return preprocess_or_return(frame, result)
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 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 = mw.text.unstripNoWiki(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
if id == "" or pagePath == "" or tplPath == "" then
        return ""
return ""
    end
end


    local moduleName = get_module_name(pagePath)
local moduleName = get_module_name(pagePath)
    local data = frame.data
local data = frame.data
    if not data then
if not data then
        data = load_cached_data(moduleName)
data = load_cached_data(moduleName)
    end
end
    if not data then
if not data then
        return ""
return ""
    end
end


    local entry = resolve_entry(data, id)
local tplStr = build_tpl(id, pagePath, tplPath, data, tplArgs)
    local extra = flatten_entry(entry)
return preprocess_or_return(frame, tplStr)
    local tplStr = "{{" .. tostring(tplPath) .. "|id=" .. tostring(id)
    if extra ~= "" then
        tplStr = tplStr .. "|" .. extra
    end
    tplStr = tplStr .. "}}"
 
    return preprocess_or_return(frame, tplStr)
end
end


function p.getTplGenerator(frame)
function p.searchStoreTpl(frame)
    local args = getArgs(frame, { removeBlanks = false })
local args = getArgs(frame, { removeBlanks = false })
    local searchId = args[1] or ""
local searchId = args[1] or ""
    local kind = (args[2] or ""):lower()
local kind = (args[2] or ""):lower()
    local generatorId = args[3] or ""
local generatorId = args[3] or ""
    local tplPath = mw.text.unstripNoWiki(args[4] 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
if searchId == "" or generatorId == "" or tplPath == "" then
        return ""
return ""
    end
end
    if kind ~= "prototype" and kind ~= "component" then
if kind ~= "prototype" and kind ~= "component" then
        return ""
return ""
    end
end


    local dir = (kind == "prototype") and "prototype/" or "component/"
local dir = (kind == "prototype") and "prototype/" or "component/"
    local pagePath = dir .. generatorId .. ".json"
local pagePath = dir .. generatorId .. ".json"


    local idsJson = p.findInGenerator({ args = { searchId, kind, generatorId } })
local idsJson = p.findInGenerator({ args = { searchId, kind, generatorId } })
    local ok, ids = pcall(mw.text.jsonDecode, idsJson or "")
local ok, ids = pcall(mw.text.jsonDecode, idsJson or "")
    if not ok or type(ids) ~= "table" or #ids == 0 then
if not ok or type(ids) ~= "table" or #ids == 0 then
        return ""
return ""
    end
end


    local moduleName = get_module_name(pagePath)
local moduleName = get_module_name(pagePath)
    local data = load_cached_data(moduleName)
local data = load_cached_data(moduleName)
    if not data then
if not data then
        return ""
return ""
    end
end


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


    local result = table.concat(out, " ")
local result = table.concat(out, " ")
    return preprocess_or_return(frame, result)
return preprocess_or_return(frame, result)
end
end


function p.flattenParams(entry)
function p.flattenParams(entry)
    return flatten_parts(entry)
return flatten_parts(entry)
end
end


function p.getGenerator(frame)
function p.searchStore(frame)
    local args = getArgs(frame, { removeBlanks = false })
local args = getArgs(frame, { removeBlanks = false })
    local searchId = args[1] or ""
local searchId = args[1] or ""
    local kind = (args[2] or ""):lower()
local kind = (args[2] or ""):lower()
    local generatorId = args[3] or ""
local generatorId = args[3] or ""


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


    local idsJson = p.findInGenerator({ args = { searchId, kind, generatorId } })
local idsJson = p.findInGenerator({ args = { searchId, kind, generatorId } })
    local ok, ids = pcall(mw.text.jsonDecode, idsJson or "")
local ok, ids = pcall(mw.text.jsonDecode, idsJson or "")
    if not ok or type(ids) ~= "table" or #ids == 0 then
if not ok or type(ids) ~= "table" or #ids == 0 then
        return ""
return ""
    end
end


    local okOut, outJson = pcall(mw.text.jsonEncode, ids)
local okOut, outJson = pcall(mw.text.jsonEncode, ids)
    if okOut and outJson then
if okOut and outJson then
        return outJson
return outJson
    end
end


    return ""
return ""
end
end


function p.hasComp(frame)
function p.hasComp(frame)
    local args = getArgs(frame, { removeBlanks = false })
local args = getArgs(frame, { removeBlanks = false })
    local entityId = args[1] or ""
local entityId = args[1] or ""
    local compName = args[2] or ""
local compName = args[2] or ""


    if entityId == "" or compName == "" then
if entityId == "" or compName == "" then
        return "false"
return "false"
    end
end


    local moduleName = get_module_name("component.json")
local moduleName = get_module_name("component.json")
    local data = load_cached_data(moduleName)
local data = load_cached_data(moduleName)
    if not data then
if not data then
        return "false"
return "false"
    end
end


    if type(data) ~= "table" then
if type(data) ~= "table" then
        return "false"
return "false"
    end
end


    local entry = data[entityId]
local entry = data[entityId]
    if type(entry) ~= "table" then
if type(entry) ~= "table" then
        return "false"
return "false"
    end
end


    local target = tostring(compName)
local target = tostring(compName)
    for _, v in ipairs(entry) do
for _, v in ipairs(entry) do
        if tostring(v) == target then
if tostring(v) == target then
            return "true"
return "true"
        end
end
    end
end


    return "false"
return "false"
end
end


function p.GeneratorId(frame)
function p.getAll(frame)
    local args = getArgs(frame, { removeBlanks = false })
local args = getArgs(frame, { removeBlanks = false })
    local pagePath = args[1] or ""
local pagePath = args[1] or ""
    local replace = mw.text.unstripNoWiki(args.replace or "")
local replace = mw.text.unstripNoWiki(args.replace or "")
    local pattern = mw.text.unstripNoWiki(args.pattern or "(.*)")
local pattern = mw.text.unstripNoWiki(args.pattern or "(.*)")


    if pagePath == "" then
if pagePath == "" then
        return ""
return ""
    end
end


    local moduleName = get_module_name(pagePath)
local moduleName = get_module_name(pagePath)
    local data = load_cached_data(moduleName)
local data = load_cached_data(moduleName)
    if not data then
if not data then
        return ""
return ""
    end
end


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


    local ids = {}
local ids = {}
    for k in pairs(idsTable) do
for k in pairs(idsTable) do
        ids[#ids + 1] = k
ids[#ids + 1] = k
    end
end


    table.sort(ids)
table.sort(ids)


    if replace ~= "" then
if replace ~= "" then
        local out = {}
local out = {}
        for _, id in ipairs(ids) do
for _, id in ipairs(ids) do
            local text = apply_pattern(id, pattern, replace)
local text = apply_pattern(id, pattern, replace)
            if text ~= "" then
if text ~= "" then
                out[#out + 1] = text
out[#out + 1] = text
            end
end
        end
end
        if #out == 0 then
if #out == 0 then
            return ""
return ""
        end
end
        return preprocess_or_return(frame, table.concat(out, "\n"))
return preprocess_or_return(frame, table.concat(out, "\n"))
    end
end


    local ok, json = pcall(mw.text.jsonEncode, ids)
local ok, json = pcall(mw.text.jsonEncode, ids)
    if ok and json then
if ok and json then
        return json
return json
    end
end


    return ""
return ""
end
end


function p.GeneratorTplId(frame)
function p.getAllTpl(frame)
    local args = getArgs(frame, { removeBlanks = false })
local args = getArgs(frame, { removeBlanks = false })
    local pagePath = args[1] or ""
local pagePath = args[1] or ""
    local tplPath = args[2] 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
if pagePath == "" or tplPath == "" then
        return ""
return ""
    end
end


    local moduleName = get_module_name(pagePath)
local moduleName = get_module_name(pagePath)
    local data = load_cached_data(moduleName)
local data = load_cached_data(moduleName)
    if not data then
if not data then
        return ""
return ""
    end
end


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


    local out = {}
local out = {}


    for idKey in pairs(idsTable) do
for idKey in pairs(idsTable) do
        local tpl = p.getTpl({ args = { idKey, pagePath, tplPath }, data = data })
local tpl = build_tpl(idKey, pagePath, tplPath, data, tplArgs)
        if tpl ~= "" then
if tpl ~= "" then
            out[#out + 1] = tpl
out[#out + 1] = tpl
        end
end
    end
end


    table.sort(out)
table.sort(out)


    local result = table.concat(out, " ")
local result = table.concat(out, " ")
    return preprocess_or_return(frame, result)
return preprocess_or_return(frame, result)
end
end


local function is_array(tbl)
local function encode_nowiki_json(value)
    local max = 0
local ok, json = pcall(mw.text.jsonEncode, value)
    local count = 0
if ok and json then
    for k in pairs(tbl) do
return to_nowiki(json)
        if type(k) ~= "number" then
end
            return false
return nil
        end
end
        if k > max then max = k end
 
        count = count + 1
local function collect_sorted_keys(tbl, stringOnly)
    end
local keys = {}
    return count > 0 and max == count
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
end


local function apply_pattern(s, pattern, repl)
local function append_table_fields(parts, value, options, prefix)
    if not pattern or pattern == "" or not s then
if type(value) ~= "table" or next(value) == nil then
        return s
return
    end
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)


    local text = tostring(s)
for _, k in ipairs(keys) do
    local replacement
if not (options.nestedKeyMode == "raw" and type(k) == "number") then
    if repl and repl ~= "" then
local v = value[k]
        replacement = tostring(repl)
local key
        replacement = replacement:gsub("\\(%d)", "%%%1")
if prefix then
    else
key = prefix .. "." .. tostring(k)
        replacement = "%1"
else
    end
key = tostring(k)
end


    local patt = pattern
if type(v) == "table" then
    if not patt:find("%^") and not patt:find("%$") then
if is_array_of_primitives(v) then
        patt = "^" .. patt .. "$"
local json = encode_nowiki_json(v)
    end
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


    return (text:gsub(patt, replacement))
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
end


function p.json(frame)
function p.json(frame)
    local args = getArgs(frame, { removeBlanks = false })
local args = getArgs(frame, { removeBlanks = false })
    local jsonStr = mw.text.unstripNoWiki(args[1] or args.json or "")
local jsonStr = mw.text.unstripNoWiki(args[1] or args.json or "")
    local tplPath = mw.text.unstripNoWiki(args[2] or args.template or "")
local tplPath = mw.text.unstripNoWiki(args[2] or args.template or "")
    if jsonStr == "" or tplPath == "" then return "" end
local tplArgs = args[3] or args.tplArgs or args.templateArgs or ""
tplPath, tplArgs = split_template_spec(tplPath, tplArgs)


    local ok, data = pcall(mw.text.jsonDecode, jsonStr)
if jsonStr == "" or tplPath == "" then
    if not ok or type(data) ~= "table" then
return ""
        return ""
end
    end


    local okDp, dp = pcall(require, "Module:GetField")
local ok, data = pcall(mw.text.jsonDecode, jsonStr)
if not ok or type(data) ~= "table" then
return ""
end


    local calls = {}
local calls = {}
local nestedOptions = {
includeJsonAtPrefix = true,
nestedKeyMode = "prefixed",
skipPrimitiveRoot = false,
}
local rawTypeOptions = {
includeJsonAtPrefix = false,
nestedKeyMode = "raw",
skipPrimitiveRoot = true,
}


    local function makeCall(id, obj)
local function is_object_map(tbl)
        if type(id) ~= "string" then return end
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 parts = { "{{" .. tplPath, "id=" .. id }
local function makeCall(obj)
if type(obj) ~= "table" then
return
end


        if type(obj) == "table" then
local idKey = choose_id_key(obj)
            if okDp and dp and type(dp.flattenParams) == "function" then
if not idKey then
                local extra = dp.flattenParams(obj)
return
                for i = 1, #extra do
end
                    parts[#parts + 1] = extra[i]
                end
            else
                for k, v in pairs(obj) do
                    if v ~= nil then
                        parts[#parts + 1] = tostring(k) .. "=" .. tostring(v)
                    end
                end
            end
        elseif obj ~= nil then
            parts[#parts + 1] = "value=" .. tostring(obj)
        end


        parts[#parts + 1] = "}}"
local parts = { "{{Шаблон:" .. resolve_template_path(tplPath) }
        calls[#calls + 1] = table.concat(parts, "|")
    end


    if is_array(data) then
if tplArgs ~= "" then
        for _, item in ipairs(data) do
parts[#parts + 1] = tplArgs
            if type(item) == "table" then
end
                for k, v in pairs(item) do
                    makeCall(k, v)
                end
            end
        end
    else
        for k, v in pairs(data) do
            makeCall(k, v)
        end
    end


    if #calls == 0 then
parts[#parts + 1] = tostring(idKey)
        return ""
local keys = collect_sorted_keys(obj, true)
    end


    local rendered = table.concat(calls, " ")
for _, k in ipairs(keys) do
    return frame:preprocess(rendered)
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.jsonList(frame)
function p.jsonList(frame)
    local args = getArgs(frame, { removeBlanks = false })
local args = getArgs(frame, { removeBlanks = false })
    local jsonStr = mw.text.unstripNoWiki(args[1] or args.json or "")
local jsonStr = mw.text.unstripNoWiki(args[1] or args.json or "")
    if jsonStr == "" then return "" end
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 ok, data = pcall(mw.text.jsonDecode, jsonStr)
local bullet = mw.text.unstripNoWiki(args.prefix or "* ")
    if not ok or type(data) ~= "table" then
local sep = mw.text.unstripNoWiki(args.sep or ": ")
        return ""
    end


    local outputType = (args.type or "list"):lower()
if outputType == "none" then
bullet = ""
sep = ""
elseif outputType == "revertList" then
sep = mw.text.unstripNoWiki(args.sep or " ")
end


    local bullet = mw.text.unstripNoWiki(args.prefix or "* ")
local keyPattern = mw.text.unstripNoWiki(args.key_pattern or "(.*)")
    local sep = mw.text.unstripNoWiki(args.sep or ": ")
local keyReplace = mw.text.unstripNoWiki(args.key_replace or "\\1")
    if outputType == "none" then
local valuePattern = mw.text.unstripNoWiki(args.value_pattern or "(.*)")
        bullet = ""
local valueReplace = mw.text.unstripNoWiki(args.value_replace or "\\1")
        sep = ""
    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 pairPattern = mw.text.unstripNoWiki(args.pattern or "(.*)")
    local pairReplace = mw.text.unstripNoWiki(args.replace or "\\1")
local pairReplace = mw.text.unstripNoWiki(args.replace or "\\1")


    local out = {}
local out = {}


    if is_array(data) then
if is_array(data) then
        for _, v in ipairs(data) do
for _, v in ipairs(data) do
            local text = ""
local text = ""


            if type(v) == "table" then
if type(v) == "table" then
                if is_array(v) then
if is_array(v) then
                    text = table.concat(v, ", ")
text = table.concat(v, ", ")
                else
else
                    local okJson, jsonVal = pcall(mw.text.jsonEncode, v)
local okJson, jsonVal = pcall(mw.text.jsonEncode, v)
                    if okJson and jsonVal then
if okJson and jsonVal then
                        text = jsonVal
text = jsonVal
                    end
end
                end
end
            else
else
                text = tostring(v)
text = tostring(v)
            end
end


            if text ~= "" then
if text ~= "" then
                local patt = valuePattern ~= "" and valuePattern or keyPattern
local patt = valuePattern ~= "" and valuePattern or keyPattern
                local repl = valueReplace ~= "" and valueReplace or keyReplace
local repl = valueReplace ~= "" and valueReplace or keyReplace
                text = apply_pattern(text, patt, repl)
text = apply_pattern(text, patt, repl)


                local line
local line
                if outputType == "enum" then
if outputType == "enum" then
                    line = text
line = text
                else
else
                    line = bullet .. text
line = bullet .. text
                end
end


                if pairPattern ~= "" then
if pairPattern ~= "" then
                    line = apply_pattern(line, pairPattern, pairReplace)
line = apply_pattern(line, pairPattern, pairReplace)
                end
end


                table.insert(out, line)
table.insert(out, line)
            end
end
        end
end
    else
else
        local keys = {}
local keys = {}
        for k in pairs(data) do
for k in pairs(data) do
            keys[#keys + 1] = k
keys[#keys + 1] = k
        end
end
        table.sort(keys, function(a, b) return tostring(a) < tostring(b) end)
table.sort(keys, function(a, b)
return tostring(a) < tostring(b)
end)


        for _, k in ipairs(keys) do
for _, k in ipairs(keys) do
            local v = data[k]
local v = data[k]
            local vStr
local vStr


            if type(v) == "table" then
if type(v) == "table" then
                local okJson, jsonVal = pcall(mw.text.jsonEncode, v)
local okJson, jsonVal = pcall(mw.text.jsonEncode, v)
                if okJson and jsonVal then
if okJson and jsonVal then
                    vStr = jsonVal
vStr = jsonVal
                else
else
                    vStr = ""
vStr = ""
                end
end
            else
else
                vStr = tostring(v)
vStr = tostring(v)
            end
end


            local baseKey = apply_pattern(tostring(k), keyPattern, "\\1")
local baseKey = apply_pattern(tostring(k), keyPattern, "\\1")


            local MARK_KEY = "\31KEY\31"
local MARK_KEY = "\31KEY\31"
            local vRepl = (valueReplace or "\\1"):gsub("\\2", MARK_KEY)
local vRepl = (valueReplace or "\\1"):gsub("\\2", MARK_KEY)
            local vStr0 = apply_pattern(vStr, valuePattern, vRepl)
local vStr0 = apply_pattern(vStr, valuePattern, vRepl)
            vStr0 = tostring(vStr0):gsub(MARK_KEY, baseKey)
vStr0 = tostring(vStr0):gsub(MARK_KEY, baseKey)


            local MARK_VAL = "\31VAL\31"
local MARK_VAL = "\31VAL\31"
            local kRepl = (keyReplace or "\\1"):gsub("\\2", MARK_VAL)
local kRepl = (keyReplace or "\\1"):gsub("\\2", MARK_VAL)
            local keyStr0 = apply_pattern(tostring(k), keyPattern, kRepl)
local keyStr0 = apply_pattern(tostring(k), keyPattern, kRepl)
            local keyStr = tostring(keyStr0):gsub(MARK_VAL, vStr0)
local keyStr = tostring(keyStr0):gsub(MARK_VAL, vStr0)


            vStr = vStr0
vStr = vStr0


            if vStr ~= "" then
if vStr ~= "" then
                local line
local line
                if outputType == "enum" then
if outputType == "enum" then
                    line = vStr .. " " .. keyStr
line = vStr .. " " .. keyStr
                else
elseif outputType == "revertList" then
                    line = bullet .. keyStr .. sep .. vStr
line = bullet .. vStr .. sep .. keyStr
                end
else
line = bullet .. keyStr .. sep .. vStr
end


                if pairPattern ~= "" then
if pairPattern ~= "" then
                    line = apply_pattern(line, pairPattern, pairReplace)
line = apply_pattern(line, pairPattern, pairReplace)
                end
end


                table.insert(out, line)
table.insert(out, line)
            end
end
        end
end
    end
end


    if outputType == "enum" then
if outputType == "enum" then
        return frame:preprocess(table.concat(out, ", "))
return frame:preprocess(table.concat(out, ", "))
    elseif outputType == "list" then
elseif outputType == "list" or outputType == "revertList" then
        return frame:preprocess(table.concat(out, "\n"))
return frame:preprocess(table.concat(out, "\n"))
    else  
else
    return frame:preprocess(table.concat(out, ""))
return frame:preprocess(table.concat(out, " "))
    end
end
end
end


return p
return p