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

Материал из Space Station 14 Вики
Нет описания правки
Нет описания правки
Строка 201: Строка 201:


     return entry[string.upper(first) .. tail]
     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
end


Строка 531: Строка 553:
     local args = frame.args or {}
     local args = frame.args or {}
     local pagePath = args[1] or ""
     local pagePath = args[1] or ""
    local replace = mw.text.unstripNoWiki(args.replace or "")
    local pattern = mw.text.unstripNoWiki(args.pattern or "(.*)")


     if pagePath == "" then
     if pagePath == "" then
Строка 553: Строка 577:


     table.sort(ids)
     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)
     local ok, json = pcall(mw.text.jsonEncode, ids)

Версия от 18:37, 12 марта 2026

Документация

Модуль предназначен для получения данных из кэшированных JSON-страниц и их использования в шаблонах. С его помощью можно получить поле по пути, найти id по значению или сразу собрать вызов шаблона по найденным данным.

Поля берутся из json страниц

Для подпроектов

Основные функции

get

Возвращает запись целиком или отдельное поле по её id.

Использование:

  • {{#invoke:GetField|get|id|pagePath|keyPath}}

Простые значения возвращаются как текст, а таблицы в JSON-виде.

Например, json сущности MopItem из Участник:IanComradeBot/component/meleeWeapon.json выглядит так:

"MopItem": {
    "damage": {
        "types": {
            "Blunt": 10
        }
    },
...
},

то мы можем получить значение как как:

  • {{#invoke:GetField|get|MopItem|component/meleeWeapon.json|damage}} -> "types": {"Blunt": 10}}
  • {{#invoke:GetField|get|MopItem|component/meleeWeapon.json|damage.types}} -> {"Blunt": 10}
  • {{#invoke:GetField|get|MopItem|component/meleeWeapon.json|damage.types.Blunt}} -> 10
ПараметрОписаниеОбязателен?
|1 =Id записи.Да
|2 =Путь до JSON-страницы, например component/meleeWeapon.json.Да
|3 =Путь до поля внутри записи. Поддерживает вложенность через точку и индексы вида field.1Да

getTpl

Строит вызов шаблона для одного id передавая в него развёрнутые поля записи.

Использование:

  • {{#invoke:GetField|getTpl|id|pagePath|template}}

Шаблон вызывается в виде {{Имя шаблона|id=...|...поля записи...}}. Вложенные таблицы передаются как плоские параметры, а также в JSON-виде там, где это нужно для сохранения структуры.

Пример:

  • {{#invoke:GetField|getTpl|MopItem|сomponent/spillable.json|сomponent/spillable}}
ПараметрОписаниеОбязателен?
|1 =Id записи.Да
|2 =Путь до JSON-страницы.Да
|3 =Имя шаблона, который будет вызван как {{Имя шаблона|id=...|...}}.Да

searchId / searchIdTpl

Обе функции ищут id по значению в указанном поле. Разница в результате:

  • searchId возвращает найденные id в виде JSON-массива.
  • searchIdTpl по найденным id сразу вызывает шаблон.

Использование:

  • {{#invoke:GetField|searchId|searchValue|pagePath|keyPath}}
  • {{#invoke:GetField|searchIdTpl|searchValue|pagePath|keyPath|template}}
  • {{#invoke:GetField|searchIdTpl|pagePath|keyPath|template|searchType=path}}

Режимы поиска:

  • searchType=value — ищет id, у которых значение поля равно указанному значению. Это режим по умолчанию.
  • searchType=key — ищет id, у которых в поле-таблице существует ключ с указанным именем.
  • searchType=path — только для searchIdTpl; выводит все id, у которых поле по указанному пути существует и не пустое. В этом режиме первым параметром передаётся pagePath, а не значение для поиска.

searchIdTpl вызывает шаблон в виде {{Имя шаблона|id=...|...поля записи...}}. Если найдено несколько id, вызовы собираются подряд через пробел.

Примеры:

  • {{#invoke:GetField|searchId|Elements|prototype/reaction.json|group}} -> Ошибка скрипта: Функции «searchId» не существует.
ПараметрОписаниеОбязателен?
|1 =Значение для поиска.Да
|2 =Путь до JSON-страницы.Да
|3 =Путь до поля внутри записи. Поддерживает вложенность через точку и индексы вида field.1Да
|4 =Имя шаблона, который будет вызван для каждого найденного id.Только для searchIdTpl
|searchType =Режим поиска: value, key или path.Нет; value

hasComp

Проверяет, есть ли у сущности указанный компонент.

Использование:

  • {{#invoke:GetField|hasComp|entityId|componentName}}

Возвращает строку true или false.

Пример:

  • {{#invoke:GetField|hasComp|MopItem|Item}}
ПараметрОписаниеОбязателен?
|1 =Id сущности.Да
|2 =Имя компонента для проверки.Да

searchStore / searchStoreTpl

Эти функции находят прототипы или компоненты содержащие указанный id, используя Участник:IanComradeBot/prototype_store.json или Участник:IanComradeBot/component_store.json.

Использование:

  • {{#invoke:GetField|searchStore|searchId|prototype|Название}}
  • {{#invoke:GetField|searchStore|searchId|component|Название}}
  • {{#invoke:GetField|searchStoreTpl|searchId|prototype|Название|Шаблон}}

searchStore возвращает JSON-массив id, найденных в компонентах/прототипах. searchStoreTpl по тем же id сразу вызывает шаблон, используя страницу вида prototype/Название.json или component/Название.json.

Примеры:

  • {{#invoke:GetField|searchStore|MopItem|component|itemBorgModule}} -> Ошибка скрипта: Функции «searchStore» не существует.
  • {{#invoke:GetField|searchStore|MopItem|prototype|latheRecipe}} -> Ошибка скрипта: Функции «searchStore» не существует.
ПараметрОписаниеОбязателен?
|1 =Id, который ищется в хранилище.Да
|2 =Тип: prototype или component.Да
|3 =Имя хранилищя без .json.Да
|4 =Имя шаблона, который будет вызван для каждого найденного id.Только для searchStoreTpl

getAll / getAllTpl

Эти функции получают все id прототипов или компонентов.

Использование:

  • {{#invoke:GetField|getAll|pagePath}}
  • {{#invoke:GetField|getAllTpl|pagePath|template}}

getAll по умолчанию возвращает JSON-массив id. getAllTpl вызывает шаблон для каждого id в виде {{Имя шаблона|id=...|...поля записи...}}.

Примеры:

  • {{#invoke:GetField|getAll|component/staticPrice.json}} -> выводит все id сущностей с этим компонентом в формате JSON
  • {{#invoke:GetField|getAllTpl|component/staticPrice.json|component/staticPrice/wrapper}} -> выводит все id сущностей с этим компонентом обёрнутым в шаблон {{component/staticPrice/wrapper}}
ПараметрОписаниеОбязателен?
|1 =Путь до JSON-страницы.Да
|2 =Имя шаблона для getAllTpl.Только для getAllTpl
|replace =Строка замены для getAll. Если задана, результат выводится построчно вместо JSON-массива.Нет

jsonList

Преобразует JSON в список, перечисление или простой текст. Подходит для быстрого вывода данных без отдельного шаблона.

Использование:

  • {{#invoke:GetField|jsonList|["a","b","c"]}}
  • {{#invoke:GetField|jsonList|{"MopItem":"Швабра"}}}
  • {{#invoke:GetField|jsonList|{"MopItem":"Швабра"}|type=enum}}

Основные параметры:

  • type — формат вывода: list, enum или none.
  • prefix — префикс строки для режима списка. По умолчанию * .
  • sep — разделитель между ключом и значением. По умолчанию : .
  • replace — дополнительная обработка регулярным выражением уже собранной строки.
  • key_replace, value_replace — обработка ключей и значений регулярным выражением по отдельности.

Примеры:

  • {{#invoke:GetField|jsonList|["a","b","c"]}}
  • {{#invoke:GetField|jsonList|{"MopItem":"Швабра"}|type=list}}
  • {{#invoke:GetField|jsonList|{"MopItem":"Швабра"}|type=none|key_replace=<nowiki>[[\1]]</nowiki>}}
ПараметрОписаниеОбязателен?
|1 =JSON-строка. Можно также передать именованным параметром json.Да
|type =Формат вывода: list, enum или none.Нет; list
|prefix =Префикс строки для режима list.Нет; *
|sep =Разделитель между ключом и значением.Нет; :
|key_replace =Строка замены для ключей.Нет; \1
|value_replace =Строка замены для значений.Нет; \1
|replace =Строка замены для всего вывода.Нет; \1

json

Преобразует JSON-объект или JSON-массив объектов в набор вызовов шаблона и сразу обрабатывает их.

Использование:

  • {{#invoke:GetField|json|{"MopItem":{"name":"Швабра"}}|Предмет}}

Если значение по id является объектом, его поля разворачиваются в параметры шаблона. Если значение простое, оно передаётся как value=....

Пример вызова, который будет собран функцией:

  • {{Предмет|id=MopItem|name=Швабра}}
ПараметрОписаниеОбязателен?
|1 =JSON-строка.Да
|2 =Имя шаблона.Да

См. также

Примечания

  • Если запись, поле или JSON-страница не найдены, функции обычно возвращают пустую строку.
  • Функция get возвращает таблицы в JSON-виде.
  • В searchId и searchIdTpl значения сравниваются как строки.
  • Параметр keyPath поддерживает доступ к вложенным полям и индексам.
  • getTpl и searchIdTpl удобны, когда нужно не получить сырые данные, а сразу отрендерить карточку или другой шаблон.
  • Функции с searchStore работают только с генераторными страницами и хранилищами, где структура данных уже подготовлена под поиск по id.
  • json и jsonList ожидают корректный JSON; если строка не разбирается, результат будет пустым.
local p = {}

local BASE_USER = "IanComradeBot/"

local function get_module_name(pagePath)
    return "Module:" .. BASE_USER .. pagePath .. "/data"
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 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 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.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_entry(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
                else
                    append_table_json(key, v)
                    if not is_array(v) then
                        walk(v, key)
                    end
                end
            else
                parts[#parts + 1] = key .. "=" .. tostring(v)
            end
        end
    end

    walk(entry, "")

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

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

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

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

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

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

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

    return mw.text.jsonEncode(out)
end

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

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

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

function p.get(frame)
    local args = frame.args or {}
    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 = frame.args or {}
    local searchValue = args[1] or ""
    local pagePath = args[2] or ""
    local keyPath = args[3] or ""

    if searchValue == "" or pagePath == "" or keyPath == "" 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 matches = find_matching_ids(idsTable, keyPath, searchValue)

    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 = frame.args or {}
    local searchValue = args[1] or ""
    local pagePath = args[2] or ""
    local keyPath = args[3] or ""
    local tplPath = mw.text.unstripNoWiki(args[4] or "")

    if searchValue == "" or pagePath == "" or keyPath == "" 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 matches = find_matching_ids(idsTable, keyPath, searchValue)

    if #matches == 0 then
        return ""
    end

    local out = {}
    for _, idKey in ipairs(matches) do
        local tpl = p.getTpl({ args = { idKey, pagePath, tplPath }, data = 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 = frame.args or {}
    local id = args[1] or ""
    local pagePath = args[2] or ""
    local tplPath = 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 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 preprocess_or_return(frame, tplStr)
end

function p.getTplGenerator(frame)
    local args = frame.args or {}
    local searchId = args[1] or ""
    local kind = (args[2] or ""):lower()
    local generatorId = args[3] or ""
    local tplPath = 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 = p.getTpl({ args = { id, pagePath, tplPath }, data = data })
        if tpl ~= "" then
            out[#out + 1] = tpl
        end
    end

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

function p.getGenerator(frame)
    local args = frame.args or {}
    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 = frame.args or {}
    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 = frame.args or {}
    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 = frame.args or {}
    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 = p.getTpl({ args = { idKey, pagePath, tplPath }, data = 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

return p