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

Материал из Space Station 14 Вики
Отмена версии 199136, сделанной Pok (обсуждение)
Метка: отмена
Нет описания правки
Строка 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
end


local function get_by_path(tbl, path)
local function get_by_path(tbl, path)
    if not tbl or path == "" then return nil end
if not tbl or path == "" then
    local cur = tbl
return nil
    for part in string.gmatch(path, "([^%.]+)") do
end
        local key, idx = parse_indexed_part(part)
 
        if key and key ~= "" then
local cur = tbl
            if type(cur) ~= "table" then return nil end
for part in string.gmatch(path, "([^%.]+)") do
            local nextCur = cur[key]
local key, idx = parse_indexed_part(part)
            if nextCur == nil then
 
                nextCur = cur["!type:" .. key]
if key and key ~= "" then
            end
if type(cur) ~= "table" then
            cur = nextCur
return nil
        end
end
        if idx then
 
            if type(cur) ~= "table" then return nil end
local nextCur = cur[key]
            cur = cur[idx]
if nextCur == nil then
        end
nextCur = cur["!type:" .. key]
        if cur == nil then return nil end
end
    end
cur = nextCur
    return cur
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 collect_id_keys(data)
    if type(data) ~= "table" then
if type(data) ~= "table" then
        return {}
return {}
    end
end
 
local idsTable = data.id
local ids = {}


    local idsTable = data.id
if type(idsTable) == "table" then
    local ids = {}
for k in pairs(idsTable) do
ids[#ids + 1] = k
end
return ids
end


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


    for k in pairs(data) do
return ids
        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 contains_target(v, target)
    if type(v) == "table" then
if type(v) == "table" then
        if is_array(v) then
if is_array(v) then
            for _, item in ipairs(v) do
for _, item in ipairs(v) do
                if tostring(item) == target then
if tostring(item) == target then
                    return true
return true
                end
end
            end
end
            return false
return false
        end
end


        for _, item in pairs(v) do
for _, item in pairs(v) do
            if tostring(item) == target then
if tostring(item) == target then
                return true
return true
            end
end
        end
end
        return false
return false
    end
end


    return tostring(v) == target
return tostring(v) == target
end
end


local function is_nonempty_value(v)
local function is_nonempty_value(v)
    if v == nil then return false end
if v == nil then
    if type(v) == "table" then
return false
        return next(v) ~= nil
end
    end
if type(v) == "table" then
    return true
return next(v) ~= nil
end
return true
end
end


local function find_matching_ids(idsTable, keyPath, searchValue)
local function find_matching_ids(idsTable, keyPath, searchValue)
    local target = tostring(searchValue)
local target = tostring(searchValue)
    local matches = {}
local matches = {}


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


    return matches
return matches
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 tail = string.sub(fieldId, 2)


    local first = string.sub(fieldId, 1, 1)
value = entry[string.lower(first) .. tail]
    local tail = string.sub(fieldId, 2)
if value ~= nil then
    value = entry[string.lower(first) .. tail]
return value
    if value ~= nil then return value end
end


    return entry[string.upper(first) .. tail]
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 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 parts = {}
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
        if ok and json then
keys[#keys + 1] = k
            parts[#parts + 1] = key .. "=" .. to_nowiki(json)
end
        end
table.sort(keys, function(a, b)
    end
return tostring(a) < tostring(b)
end)


    local function walk(tbl, prefix)
for _, k in ipairs(keys) do
        local keys = {}
local v = tbl[k]
        for k in pairs(tbl) do keys[#keys + 1] = k end
local kStr = tostring(k)
        table.sort(keys, function(a, b) return tostring(a) < tostring(b) end)
local key = (prefix == "" and kStr or prefix .. "." .. kStr)
        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
                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, "")
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


    return parts
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 build_tpl(id, pagePath, tplPath, data)
if id == "" or pagePath == "" or tplPath == "" then
return ""
end
 
local moduleName = get_module_name(pagePath)
if not data then
data = load_cached_data(moduleName)
end
if not data then
return ""
end
 
local entry = resolve_entry(data, id)
local extra = flatten_entry(entry)
 
local tplStr = "{{" .. tostring(tplPath) .. "|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 = 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 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 = 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 ""
    if id == "" or pagePath == "" then return "" end
if id == "" or 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) or {}
local entry = resolve_entry(data, id) or {}
    return flatten_entry(entry)
return flatten_entry(entry)
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
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)
function p.getId(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 searchType = (args.searchType or ""):lower()
local searchType = (args.searchType or ""):lower()


    if searchValue == "" or pagePath == "" or keyPath == "" then
if searchValue == "" or pagePath == "" or keyPath == "" then
        return ""
return ""
    end
end
    if searchType == "" then
if searchType == "" then
        searchType = "value"
searchType = "value"
    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 return "[]" end
if not data then
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 == "key" then
if searchType == "key" then
        local target = tostring(searchValue)
local target = tostring(searchValue)
        matches = {}
matches = {}
        for _, idKey in ipairs(ids) do
for _, idKey in ipairs(ids) do
            local entry = resolve_entry(data, idKey)
local entry = resolve_entry(data, idKey)
            if type(entry) == "table" then
if type(entry) == "table" then
                local v = get_by_path(entry, keyPath)
local v = get_by_path(entry, keyPath)
                if type(v) == "table" and v[target] ~= nil then
if type(v) == "table" and v[target] ~= nil then
                    matches[#matches + 1] = idKey
matches[#matches + 1] = idKey
                end
end
            end
end
        end
end
    else
else
        local target = tostring(searchValue)
local target = tostring(searchValue)
        matches = {}
matches = {}
        for _, idKey in ipairs(ids) do
for _, idKey in ipairs(ids) do
            local entry = resolve_entry(data, idKey)
local entry = resolve_entry(data, idKey)
            if type(entry) == "table" then
if type(entry) == "table" then
                local v = get_by_path(entry, keyPath)
local v = get_by_path(entry, keyPath)
                if v ~= nil and contains_target(v, target) then
if v ~= nil and contains_target(v, target) then
                    matches[#matches + 1] = idKey
matches[#matches + 1] = idKey
                end
end
            end
end
        end
end
    end
end


    if #matches == 0 then
if #matches == 0 then
        return ""
return ""
    end
end


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


    return ""
return ""
end
end


function p.getTplId(frame)
function p.getTplId(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 searchType = (args.searchType or ""):lower()
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 "")
end


    if searchType == "" then
if pagePath == "" or keyPath == "" or tplPath == "" then
        searchType = "value"
return ""
    end
end
    if searchType == "path" then
if searchType ~= "path" and searchValue == "" then
        searchValue = ""
return ""
        pagePath = args[1] or ""
end
        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 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 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
if searchType == "path" then
        matches = {}
matches = {}
        for _, idKey in ipairs(ids) do
for _, idKey in ipairs(ids) do
            local entry = resolve_entry(data, idKey)
local entry = resolve_entry(data, idKey)
            if type(entry) == "table" then
if type(entry) == "table" then
                local v = get_by_path(entry, keyPath)
local v = get_by_path(entry, keyPath)
                if is_nonempty_value(v) then
if is_nonempty_value(v) then
                    matches[#matches + 1] = idKey
matches[#matches + 1] = idKey
                end
end
            end
end
        end
end
    elseif searchType == "key" then
elseif searchType == "key" then
        local target = tostring(searchValue)
local target = tostring(searchValue)
        matches = {}
matches = {}
        for _, idKey in ipairs(ids) do
for _, idKey in ipairs(ids) do
            local entry = resolve_entry(data, idKey)
local entry = resolve_entry(data, idKey)
            if type(entry) == "table" then
if type(entry) == "table" then
                local v = get_by_path(entry, keyPath)
local v = get_by_path(entry, keyPath)
                if type(v) == "table" and v[target] ~= nil then
if type(v) == "table" and v[target] ~= nil then
                    matches[#matches + 1] = idKey
matches[#matches + 1] = idKey
                end
end
            end
end
        end
end
    else
else
        local target = tostring(searchValue)
local target = tostring(searchValue)
        matches = {}
matches = {}
        for _, idKey in ipairs(ids) do
for _, idKey in ipairs(ids) do
            local entry = resolve_entry(data, idKey)
local entry = resolve_entry(data, idKey)
            if type(entry) == "table" then
if type(entry) == "table" then
                local v = get_by_path(entry, keyPath)
local v = get_by_path(entry, keyPath)
                if v ~= nil and contains_target(v, target) then
if v ~= nil and contains_target(v, target) then
                    matches[#matches + 1] = idKey
matches[#matches + 1] = idKey
                end
end
            end
end
        end
end
    end
end


    if #matches == 0 then
if #matches == 0 then
        return ""
return ""
    end
end


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


    if #out == 0 then
if #out == 0 then
        return ""
return ""
    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.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 = mw.text.unstripNoWiki(args[3] or "")
local tplPath = mw.text.unstripNoWiki(args[3] or "")
 
    if id == "" or pagePath == "" or tplPath == "" then
        return ""
    end


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


    local entry = resolve_entry(data, id)
local moduleName = get_module_name(pagePath)
    local extra = flatten_entry(entry)
local data = frame.data
    local tplStr = "{{" .. tostring(tplPath) .. "|id=" .. tostring(id)
if not data then
    if extra ~= "" then
data = load_cached_data(moduleName)
        tplStr = tplStr .. "|" .. extra
end
    end
if not data then
    tplStr = tplStr .. "}}"
return ""
end


    return preprocess_or_return(frame, tplStr)
local tplStr = build_tpl(id, pagePath, tplPath, data)
return preprocess_or_return(frame, tplStr)
end
end


function p.getTplGenerator(frame)
function p.getTplGenerator(frame)
    local args = frame.args or {}
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 "")


    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)
        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.getGenerator(frame)
    local args = frame.args or {}
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 = frame.args or {}
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.GeneratorId(frame)
    local args = frame.args or {}
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.GeneratorTplId(frame)
    local args = frame.args or {}
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 ""
 
    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 = p.getTpl({ args = { idKey, pagePath, tplPath }, data = data })
        if tpl ~= "" then
            out[#out + 1] = tpl
        end
    end


    table.sort(out)
if pagePath == "" or tplPath == "" then
return ""
end


    local result = table.concat(out, " ")
local moduleName = get_module_name(pagePath)
    return preprocess_or_return(frame, result)
local data = load_cached_data(moduleName)
end
if not data then
return ""
end


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


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


    local text = tostring(s)
for idKey in pairs(idsTable) do
    local replacement
local tpl = build_tpl(idKey, pagePath, tplPath, data)
    if repl and repl ~= "" then
if tpl ~= "" then
        replacement = tostring(repl)
out[#out + 1] = tpl
        replacement = replacement:gsub("\\(%d)", "%%%1")
end
    else
end
        replacement = "%1"
    end


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


    return (text:gsub(patt, replacement))
local result = table.concat(out, " ")
return preprocess_or_return(frame, result)
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
if jsonStr == "" or tplPath == "" then
return ""
end


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


    local okDp, dp = pcall(require, "Module:GetField")
local okDp, dp = pcall(require, "Module:GetField")


    local calls = {}
local calls = {}


    local function makeCall(id, obj)
local function makeCall(id, obj)
        if type(id) ~= "string" then return end
if type(id) ~= "string" then
return
end


        local parts = { "{{" .. tplPath, "id=" .. id }
local parts = { "{{" .. tplPath, "id=" .. id }


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


        parts[#parts + 1] = "}}"
parts[#parts + 1] = "}}"
        calls[#calls + 1] = table.concat(parts, "|")
calls[#calls + 1] = table.concat(parts, "|")
    end
end


    if is_array(data) then
if is_array(data) then
        for _, item in ipairs(data) do
for _, item in ipairs(data) do
            if type(item) == "table" then
if type(item) == "table" then
                for k, v in pairs(item) do
for k, v in pairs(item) do
                    makeCall(k, v)
makeCall(k, v)
                end
end
            end
end
        end
end
    else
else
        for k, v in pairs(data) do
for k, v in pairs(data) do
            makeCall(k, v)
makeCall(k, v)
        end
end
    end
end


    if #calls == 0 then
if #calls == 0 then
        return ""
return ""
    end
end


    local rendered = table.concat(calls, " ")
local rendered = table.concat(calls, " ")
    return frame:preprocess(rendered)
return frame:preprocess(rendered)
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 ok, data = pcall(mw.text.jsonDecode, jsonStr)
local outputType = (args.type or "list"):lower()
    if not ok or type(data) ~= "table" then
        return ""
    end


    local outputType = (args.type or "list"):lower()
local bullet = mw.text.unstripNoWiki(args.prefix or "* ")
local sep = mw.text.unstripNoWiki(args.sep or ": ")
if outputType == "none" then
bullet = ""
sep = ""
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
else
                    line = bullet .. keyStr .. sep .. vStr
line = bullet .. keyStr .. sep .. vStr
                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
    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" 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

Версия от 10:57, 25 марта 2026

Для документации этого модуля может быть создана страница Модуль:GetField/doc

local p = {}

local JsonPaths = require('Module:JsonPaths')
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 key, idx = string.match(part, "^(.-)%[(%d+)%]$")
	if key then
		return key, tonumber(idx)
	end

	local num = tonumber(part)
	if num then
		return nil, num
	end

	return part, nil
end

local function get_by_path(tbl, path)
	if not tbl or path == "" then
		return nil
	end

	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

local function format_value(v)
	local okJson, json = pcall(mw.text.jsonEncode, v)
	if okJson and json == "null" then
		return "null"
	end

	if v == nil then
		return ""
	end

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

local function to_nowiki(v)
	return "<nowiki>" .. v .. "</nowiki>"
end

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

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

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

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

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

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

	local base = data["default"]
	if type(base) == "table" then
		return deep_copy(base)
	end

	return nil
end

local function 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 find_matching_ids(idsTable, keyPath, searchValue)
	local target = tostring(searchValue)
	local matches = {}

	for idKey, entry in pairs(idsTable) do
		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

	return matches
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 build_tpl(id, pagePath, tplPath, data)
	if id == "" or pagePath == "" or tplPath == "" then
		return ""
	end

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

	local entry = resolve_entry(data, id)
	local extra = flatten_entry(entry)

	local tplStr = "{{" .. tostring(tplPath) .. "|id=" .. tostring(id)
	if extra ~= "" then
		tplStr = tplStr .. "|" .. extra
	end
	tplStr = tplStr .. "}}"

	return tplStr
end

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

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

	local storeName = (kind == "prototype") and "prototype_store.json" or "component_store.json"
	local moduleName = get_module_name(storeName)
	local data = load_cached_data(moduleName)
	if not data then
		return ""
	end

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

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

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

	return mw.text.jsonEncode(out)
end

function p.flattenField(frame)
	local args = getArgs(frame, { removeBlanks = false })
	local id = args[1] or ""
	local pagePath = args[2] or ""
	if id == "" or pagePath == "" then
		return ""
	end

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

	local entry = resolve_entry(data, id) or {}
	return flatten_entry(entry)
end

function p.get(frame)
	local args = getArgs(frame, { removeBlanks = false })
	local id = args[1] or ""
	local pagePath = args[2] or ""
	local keyPath = args[3] or ""

	if pagePath == "" then
		return ""
	end

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

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

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

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

function p.getId(frame)
	local args = getArgs(frame, { removeBlanks = false })
	local searchValue = args[1] or ""
	local pagePath = args[2] or ""
	local keyPath = args[3] or ""
	local searchType = (args.searchType or ""):lower()

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

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

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

	local matches
	if 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
		return ""
	end

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

	return ""
end

function p.getTplId(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 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 "")
	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 data = load_cached_data(moduleName)
	if not data then
		return ""
	end

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

	local matches
	if searchType == "path" then
		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
		return ""
	end

	local out = {}
	for _, idKey in ipairs(matches) do
		local tpl = build_tpl(idKey, pagePath, tplPath, data)
		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

function p.getTpl(frame)
	local args = getArgs(frame, { removeBlanks = false })
	local id = args[1] or ""
	local pagePath = args[2] or ""
	local tplPath = mw.text.unstripNoWiki(args[3] or "")

	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)
	return preprocess_or_return(frame, tplStr)
end

function p.getTplGenerator(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 "")

	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)
		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.getGenerator(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.GeneratorId(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.GeneratorTplId(frame)
	local args = getArgs(frame, { removeBlanks = false })
	local pagePath = args[1] or ""
	local tplPath = args[2] or ""

	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)
		if tpl ~= "" then
			out[#out + 1] = tpl
		end
	end

	table.sort(out)

	local result = table.concat(out, " ")
	return preprocess_or_return(frame, result)
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 "")
	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 okDp, dp = pcall(require, "Module:GetField")

	local calls = {}

	local function makeCall(id, obj)
		if type(id) ~= "string" then
			return
		end

		local parts = { "{{" .. tplPath, "id=" .. id }

		if type(obj) == "table" then
			if okDp and dp and type(dp.flattenParams) == "function" then
				local extra = dp.flattenParams(obj)
				for i = 1, #extra do
					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] = "}}"
		calls[#calls + 1] = table.concat(parts, "|")
	end

	if is_array(data) then
		for _, item in ipairs(data) do
			if type(item) == "table" then
				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
		return ""
	end

	local rendered = table.concat(calls, " ")
	return frame:preprocess(rendered)
end

function p.jsonList(frame)
	local args = getArgs(frame, { removeBlanks = false })
	local jsonStr = mw.text.unstripNoWiki(args[1] or args.json or "")
	if jsonStr == "" then
		return ""
	end

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

	local outputType = (args.type or "list"):lower()

	local bullet = mw.text.unstripNoWiki(args.prefix or "* ")
	local sep = mw.text.unstripNoWiki(args.sep or ": ")
	if outputType == "none" then
		bullet = ""
		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 pairReplace = mw.text.unstripNoWiki(args.replace or "\\1")

	local out = {}

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

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

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

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

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

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

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

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

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

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

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

			vStr = vStr0

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

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

				table.insert(out, line)
			end
		end
	end

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

return p