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

Нет описания правки
Метка: отменено
Нет описания правки
 
(не показаны 22 промежуточные версии этого же участника)
Строка 1: Строка 1:
local p = {}
local p = {}
local lang = mw.language.getContentLanguage()


local JsonPaths = require('Module:JsonPaths')
local JsonPaths = require('Module:JsonPaths')
local project = JsonPaths.project()
local getArgs = require('Module:Arguments').getArgs
local getArgs = require('Module:Arguments').getArgs


Строка 32: Строка 30:
end
end


local function get_by_path(tbl, path)
local function parse_path(path)
if not tbl or path == "" then
if not path or path == "" then
return nil
end
 
local parsed = {}
for part in string.gmatch(path, "([^%.]+)") do
parsed[#parsed + 1] = { parse_indexed_part(part) }
end
 
return parsed
end
 
local function get_by_parsed_path(tbl, parsedPath)
if not tbl or not parsedPath then
return nil
return nil
end
end


local cur = tbl
local cur = tbl
for part in string.gmatch(path, "([^%.]+)") do
for i = 1, #parsedPath do
local key, idx = parse_indexed_part(part)
local token = parsedPath[i]
local key = token[1]
local idx = token[2]


if key and key ~= "" then
if key and key ~= "" then
Строка 66: Строка 79:


return cur
return cur
end
local function get_by_path(tbl, path)
return get_by_parsed_path(tbl, parse_path(path))
end
end


Строка 167: Строка 184:


return nil
return nil
end
local function resolve_path_value(data, id, parsedPath)
if type(data) ~= "table" or not parsedPath or not id or id == "" then
return nil
end
local direct = data[id]
if direct ~= nil then
local value = get_by_parsed_path(direct, parsedPath)
if value ~= nil then
return value
end
end
local idsTable = data.id
if type(idsTable) == "table" then
local specific = idsTable[id]
if type(specific) == "table" then
local value = get_by_parsed_path(specific, parsedPath)
if value ~= nil then
return value
end
end
end
local base = data["default"]
if type(base) == "table" then
return get_by_parsed_path(base, parsedPath)
end
return nil
end
local function resolve_entry_path_value(data, id, parsedPath)
if type(data) ~= "table" or not parsedPath or not id or id == "" then
return nil
end
local entry = resolve_entry(data, id)
if type(entry) ~= "table" then
return nil
end
return get_by_parsed_path(entry, parsedPath)
end
end


Строка 226: Строка 288:


local function find_matching_ids(idsTable, keyPath, searchValue)
local function find_matching_ids(idsTable, keyPath, searchValue)
local parsedPath = parse_path(keyPath)
local target = tostring(searchValue)
local target = tostring(searchValue)
local matches = {}
local matches = {}
Строка 231: Строка 294:
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_parsed_path(entry, parsedPath)
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
Строка 348: Строка 411:
end
end


local function build_tpl(id, pagePath, tplPath, data)
local function append_flattened_part(parts, key, value)
if value == nil then
return
end
 
if type(value) == "table" then
if next(value) == nil then
return
end
 
local ok, json = pcall(mw.text.jsonEncode, value)
if ok and json then
parts[#parts + 1] = key .. "=" .. to_nowiki(json)
end
return
end
 
parts[#parts + 1] = key .. "=" .. tostring(value)
end
 
local function flatten_selected_parts(entry, keys)
if type(entry) ~= "table" or type(keys) ~= "table" then
return {}
end
 
local parts = {}
local seen = {}
 
for i = 1, #keys do
local key = keys[i]
if type(key) == "string" and key ~= "" and not seen[key] then
seen[key] = true
append_flattened_part(parts, key, get_by_path(entry, key))
end
end
 
return parts
end
 
local function resolve_template_path(tplPath)
local templatePath = tplPath
local project = JsonPaths.project()
if project ~= nil and project ~= "" then
templatePath = tplPath .. "/" .. project
templatePath = "{{#ifexist:Шаблон:" .. templatePath .. "|" .. templatePath .. "|" .. tplPath .. "}}"
end
 
return templatePath
end
 
local function split_template_spec(tplPath, tplArgs)
tplPath = mw.text.unstripNoWiki(tplPath or "")
tplArgs = mw.text.unstripNoWiki(tplArgs or "")
if tplArgs ~= "" and string.sub(tplArgs, 1, 1) == "|" then
tplArgs = string.sub(tplArgs, 2)
end
 
if tplArgs == "" then
local pipePos = string.find(tplPath, "|", 1, true)
if pipePos then
tplArgs = mw.text.unstripNoWiki(string.sub(tplPath, pipePos + 1))
if tplArgs ~= "" and string.sub(tplArgs, 1, 1) == "|" then
tplArgs = string.sub(tplArgs, 2)
end
tplPath = string.sub(tplPath, 1, pipePos - 1)
end
end
 
return tplPath, tplArgs
end
 
local function build_tpl(id, pagePath, tplPath, data, tplArgs)
if id == "" or pagePath == "" or tplPath == "" then
if id == "" or pagePath == "" or tplPath == "" then
return ""
return ""
Строка 361: Строка 495:
local entry = resolve_entry(data, id)
local entry = resolve_entry(data, id)
local extra = flatten_entry(entry)
local extra = flatten_entry(entry)
local extraTplArgs = tplArgs or ""


local templatePath = tplPath
local templatePath = resolve_template_path(tplPath)
if project ~= nil and project ~= "" then
 
templatePath = project .. ":" .. lang:ucfirst(tplPath)
local tplStr = "{{Шаблон:" .. templatePath
templatePath = "{{#ifexist:Шаблон:" .. templatePath .. "|" .. templatePath .. "|" .. tplPath .. "}}"
if extraTplArgs ~= "" then
tplStr = tplStr .. "|" .. extraTplArgs
end
end
 
tplStr = tplStr .. "|id=" .. tostring(id)
local tplStr = "{{Шаблон:" .. templatePath .. "|id=" .. tostring(id)
if extra ~= "" then
if extra ~= "" then
tplStr = tplStr .. "|" .. extra
tplStr = tplStr .. "|" .. extra
Строка 436: Строка 571:
local entry = resolve_entry(data, id) or {}
local entry = resolve_entry(data, id) or {}
return flatten_entry(entry)
return flatten_entry(entry)
end
function p.flattenFieldSelective(frame)
local args = frame.args or {}
local id = args[1] or ""
local pagePath = args[2] or ""
local keysJson = mw.text.unstripNoWiki(args[3] or args.keys or "")
if id == "" or pagePath == "" or keysJson == "" then
return ""
end
local okKeys, keys = pcall(mw.text.jsonDecode, keysJson)
if not okKeys or type(keys) ~= "table" or #keys == 0 then
return ""
end
local moduleName = get_module_name(pagePath)
local data = load_cached_data(moduleName)
if not data then
return ""
end
local entry = resolve_entry(data, id) or {}
local parts = flatten_selected_parts(entry, keys)
if #parts == 0 then
return ""
end
return table.concat(parts, "|")
end
end


Строка 487: Строка 651:
end
end


local parsedPath = parse_path(keyPath)
if not parsedPath then
return ""
end
local idsTable = type(data.id) == "table" and data.id or data
local ids = collect_id_keys(data)
local ids = collect_id_keys(data)
if #ids == 0 then
if #ids == 0 then
Строка 497: Строка 667:
matches = {}
matches = {}
for _, idKey in ipairs(ids) do
for _, idKey in ipairs(ids) do
local entry = resolve_entry(data, idKey)
local v = resolve_path_value(data, idKey, parsedPath)
if type(entry) == "table" then
if type(v) == "table" and v[target] ~= nil then
local v = get_by_path(entry, keyPath)
matches[#matches + 1] = idKey
if type(v) == "table" and v[target] ~= nil then
matches[#matches + 1] = idKey
end
end
end
end
end
Строка 508: Строка 675:
local target = tostring(searchValue)
local target = tostring(searchValue)
matches = {}
matches = {}
for _, idKey in ipairs(ids) do
if idsTable == data then
local entry = resolve_entry(data, idKey)
matches = find_matching_ids(idsTable, keyPath, searchValue)
if type(entry) == "table" then
else
local v = get_by_path(entry, keyPath)
for _, idKey in ipairs(ids) do
local v = resolve_path_value(data, idKey, parsedPath)
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
Строка 537: Строка 705:
local keyPath = args[3] or ""
local keyPath = args[3] or ""
local tplPath = mw.text.unstripNoWiki(args[4] or "")
local tplPath = mw.text.unstripNoWiki(args[4] or "")
local tplArgs = args.tplArgs or args.templateArgs or ""
local searchType = (args.searchType or ""):lower()
local searchType = (args.searchType or ""):lower()


Строка 548: Строка 717:
keyPath = args[2] or ""
keyPath = args[2] or ""
tplPath = mw.text.unstripNoWiki(args[3] or "")
tplPath = mw.text.unstripNoWiki(args[3] or "")
tplArgs = args[4] or args.tplArgs or args.templateArgs or ""
end
end
tplPath, tplArgs = split_template_spec(tplPath, tplArgs)


if pagePath == "" or keyPath == "" or tplPath == "" then
if pagePath == "" or keyPath == "" or tplPath == "" then
Строка 560: Строка 732:
local data = load_cached_data(moduleName)
local data = load_cached_data(moduleName)
if not data then
if not data then
return ""
end
local parsedPath = parse_path(keyPath)
if not parsedPath then
return ""
return ""
end
end
Строка 572: Строка 749:
matches = {}
matches = {}
for _, idKey in ipairs(ids) do
for _, idKey in ipairs(ids) do
local entry = resolve_entry(data, idKey)
local v = resolve_entry_path_value(data, idKey, parsedPath)
if type(entry) == "table" then
if is_nonempty_value(v) then
local v = get_by_path(entry, keyPath)
matches[#matches + 1] = idKey
if is_nonempty_value(v) then
matches[#matches + 1] = idKey
end
end
end
end
end
Строка 584: Строка 758:
matches = {}
matches = {}
for _, idKey in ipairs(ids) do
for _, idKey in ipairs(ids) do
local entry = resolve_entry(data, idKey)
local v = resolve_path_value(data, idKey, parsedPath)
if type(entry) == "table" then
if type(v) == "table" and v[target] ~= nil then
local v = get_by_path(entry, keyPath)
matches[#matches + 1] = idKey
if type(v) == "table" and v[target] ~= nil then
end
end
else
local idsTable = data.id
if type(idsTable) == "table" then
local directMatches = find_matching_ids(idsTable, keyPath, searchValue)
local defaultValue = nil
local includeDefault = false
local base = data["default"]
if type(base) == "table" then
defaultValue = get_by_parsed_path(base, parsedPath)
includeDefault = defaultValue ~= nil and contains_target(defaultValue, tostring(searchValue))
end
 
if includeDefault and #directMatches < #ids then
local seen = {}
for i = 1, #directMatches do
seen[directMatches[i]] = true
end
 
matches = {}
for _, idKey in ipairs(directMatches) do
matches[#matches + 1] = idKey
matches[#matches + 1] = idKey
end
end
for _, idKey in ipairs(ids) do
if not seen[idKey] then
local specific = idsTable[idKey]
local specificValue = type(specific) == "table" and get_by_parsed_path(specific, parsedPath) or
nil
if specificValue == nil then
matches[#matches + 1] = idKey
end
end
end
else
matches = directMatches
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 v = resolve_path_value(data, idKey, parsedPath)
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
if v ~= nil and contains_target(v, target) then
matches[#matches + 1] = idKey
matches[#matches + 1] = idKey
Строка 612: Строка 816:
local out = {}
local out = {}
for _, idKey in ipairs(matches) do
for _, idKey in ipairs(matches) do
local tpl = build_tpl(idKey, pagePath, tplPath, data)
local tpl = build_tpl(idKey, pagePath, tplPath, data, tplArgs)
if tpl ~= "" then
if tpl ~= "" then
out[#out + 1] = tpl
out[#out + 1] = tpl
Строка 631: Строка 835:
local pagePath = args[2] or ""
local pagePath = args[2] or ""
local tplPath = mw.text.unstripNoWiki(args[3] or "")
local tplPath = mw.text.unstripNoWiki(args[3] or "")
local tplArgs = args[4] or args.tplArgs or args.templateArgs or ""
tplPath, tplArgs = split_template_spec(tplPath, tplArgs)


if id == "" or pagePath == "" or tplPath == "" then
if id == "" or pagePath == "" or tplPath == "" then
Строка 645: Строка 851:
end
end


local tplStr = build_tpl(id, pagePath, tplPath, data)
local tplStr = build_tpl(id, pagePath, tplPath, data, tplArgs)
return preprocess_or_return(frame, tplStr)
return preprocess_or_return(frame, tplStr)
end
end
Строка 655: Строка 861:
local generatorId = args[3] or ""
local generatorId = args[3] or ""
local tplPath = mw.text.unstripNoWiki(args[4] or "")
local tplPath = mw.text.unstripNoWiki(args[4] or "")
local tplArgs = args[5] or args.tplArgs or args.templateArgs or ""
tplPath, tplArgs = split_template_spec(tplPath, tplArgs)


if searchId == "" or generatorId == "" or tplPath == "" then
if searchId == "" or generatorId == "" or tplPath == "" then
Строка 680: Строка 888:
local out = {}
local out = {}
for _, id in ipairs(ids) do
for _, id in ipairs(ids) do
local tpl = build_tpl(id, pagePath, tplPath, data)
local tpl = build_tpl(id, pagePath, tplPath, data, tplArgs)
if tpl ~= "" then
if tpl ~= "" then
out[#out + 1] = tpl
out[#out + 1] = tpl
Строка 809: Строка 1017:
local pagePath = args[1] or ""
local pagePath = args[1] or ""
local tplPath = args[2] or ""
local tplPath = args[2] or ""
local tplArgs = args[3] or args.tplArgs or args.templateArgs or ""
tplPath, tplArgs = split_template_spec(tplPath, tplArgs)


if pagePath == "" or tplPath == "" then
if pagePath == "" or tplPath == "" then
Строка 828: Строка 1038:


for idKey in pairs(idsTable) do
for idKey in pairs(idsTable) do
local tpl = build_tpl(idKey, pagePath, tplPath, data)
local tpl = build_tpl(idKey, pagePath, tplPath, data, tplArgs)
if tpl ~= "" then
if tpl ~= "" then
out[#out + 1] = tpl
out[#out + 1] = tpl
Строка 838: Строка 1048:
local result = table.concat(out, " ")
local result = table.concat(out, " ")
return preprocess_or_return(frame, result)
return preprocess_or_return(frame, result)
end
local function encode_nowiki_json(value)
local ok, json = pcall(mw.text.jsonEncode, value)
if ok and json then
return to_nowiki(json)
end
return nil
end
local function collect_sorted_keys(tbl, stringOnly)
local keys = {}
for k in pairs(tbl) do
if not stringOnly or type(k) == "string" then
keys[#keys + 1] = k
end
end
table.sort(keys, function(a, b)
return tostring(a) < tostring(b)
end)
return keys
end
local function choose_id_key(obj)
local keys = {}
for k in pairs(obj) do
if type(k) == "string" then
keys[#keys + 1] = k
end
end
if #keys == 0 then
return nil
end
table.sort(keys, function(a, b)
local av = obj[a]
local bv = obj[b]
local aPrimitive = type(av) ~= "table"
local bPrimitive = type(bv) ~= "table"
if aPrimitive ~= bPrimitive then
return aPrimitive
end
return tostring(a) < tostring(b)
end)
return keys[1]
end
local function is_array_of_primitives(tbl)
if type(tbl) ~= "table" or not is_array(tbl) then
return false
end
for _, v in ipairs(tbl) do
if type(v) == "table" then
return false
end
end
return true
end
local function append_table_fields(parts, value, options, prefix)
if type(value) ~= "table" or next(value) == nil then
return
end
local valueIsPrimitiveArray = is_array_of_primitives(value)
if options.skipPrimitiveRoot and is_array_of_primitives(value) then
return
end
if prefix and options.includeJsonAtPrefix then
local json = encode_nowiki_json(value)
if json then
parts[#parts + 1] = prefix .. "=" .. tostring(json)
end
end
local keys = collect_sorted_keys(value, false)
for _, k in ipairs(keys) do
if not (options.nestedKeyMode == "raw" and type(k) == "number") then
local v = value[k]
local key
if prefix then
key = prefix .. "." .. tostring(k)
else
key = tostring(k)
end
if type(v) == "table" then
if is_array_of_primitives(v) then
local json = encode_nowiki_json(v)
if json then
parts[#parts + 1] = key .. "=" .. tostring(json)
end
elseif options.nestedKeyMode == "raw" then
local json = encode_nowiki_json(v)
if json then
parts[#parts + 1] = key .. "=" .. tostring(json)
end
end
if next(v) ~= nil and not is_array_of_primitives(v) then
local childPrefix = (type(k) == "string") and key or nil
append_table_fields(parts, v, options, childPrefix)
end
else
parts[#parts + 1] = key .. "=" .. tostring(v)
end
end
end
end
end


Строка 844: Строка 1173:
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 "")
local tplArgs = args[3] or args.tplArgs or args.templateArgs or ""
tplPath, tplArgs = split_template_spec(tplPath, tplArgs)


if jsonStr == "" or tplPath == "" then
if jsonStr == "" or tplPath == "" then
Строка 854: Строка 1185:
end
end


local okDp, dp = pcall(require, "Module:GetField")
local calls = {}
local calls = {}
local nestedOptions = {
includeJsonAtPrefix = true,
nestedKeyMode = "prefixed",
skipPrimitiveRoot = false,
}
local rawTypeOptions = {
includeJsonAtPrefix = false,
nestedKeyMode = "raw",
skipPrimitiveRoot = true,
}


local projectPath = nil
local function makeCall(obj)
if project ~= nil and project ~= "" then
if type(obj) ~= "table" then
projectPath = project .. ":" .. lang:ucfirst(tplPath)
return
end
end


local function makeTemplatePrefix()
local idKey = choose_id_key(obj)
if projectPath then
if not idKey then
return "{{Шаблон:{{#ifexist:Шаблон:" .. projectPath .. "|" .. projectPath .. "|" .. tplPath .. "}}"
return
end
end


return "{{" .. tplPath
local parts = { "{{Шаблон:" .. resolve_template_path(tplPath) }
end


local function makeCall(id, obj)
if tplArgs ~= "" then
if type(id) ~= "string" then
parts[#parts + 1] = tplArgs
return
end
end


local parts = { makeTemplatePrefix(), "id=" .. id }
parts[#parts + 1] = tostring(idKey)
local keys = collect_sorted_keys(obj, true)
 
for _, k in ipairs(keys) do
local v = obj[k]
 
if k == idKey then
if type(k) == "string" and string.sub(k, 1, 6) == "!type:" then
if type(v) == "table" then
append_table_fields(parts, v, rawTypeOptions, nil)
elseif v ~= nil then
parts[#parts + 1] = "value=" .. tostring(v)
end
elseif type(v) == "table" then
if is_array_of_primitives(v) then
local json = encode_nowiki_json(v)
if json then
parts[#parts + 1] = "value=" .. tostring(json)
end
end


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


Строка 900: Строка 1258:
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
makeCall(item)
for k, v in pairs(item) do
makeCall(k, v)
end
end
end
end
else
else
for k, v in pairs(data) do
makeCall(data)
makeCall(k, v)
end
end
end


Строка 916: Строка 1268:
end
end


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


Строка 1047: Строка 1398:
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