|
|
| Строка 1: |
Строка 1: |
| local p = {}
| |
| local JsonPaths = require('Module:JsonPaths')
| |
|
| |
|
| 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.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
| |
| 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, "")
| |
|
| |
| return parts
| |
| end
| |
|
| |
| local function flatten_entry(entry)
| |
| local parts = flatten_parts(entry)
| |
| if #parts == 0 then
| |
| return ""
| |
| end
| |
| 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 ""
| |
| 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 = 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 "")
| |
| 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 = 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 = 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 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 = 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 = 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.flattenParams(entry)
| |
| return flatten_parts(entry)
| |
| 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
| |