Модуль:GetField: различия между версиями
Pok (обсуждение | вклад) Нет описания правки Метка: отменено |
Pok (обсуждение | вклад) Нет описания правки |
||
| (не показаны 22 промежуточные версии этого же участника) | |||
| Строка 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 | ||
| Строка 32: | Строка 30: | ||
end | end | ||
local function | local function parse_path(path) | ||
if not | 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 | for i = 1, #parsedPath do | ||
local key | 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 = | 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 = | local templatePath = resolve_template_path(tplPath) | ||
local tplStr = "{{Шаблон:" .. templatePath | |||
if extraTplArgs ~= "" then | |||
tplStr = tplStr .. "|" .. extraTplArgs | |||
end | end | ||
tplStr = tplStr .. "|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 | local v = resolve_path_value(data, idKey, parsedPath) | ||
if type(v) == "table" and v[target] ~= nil then | |||
matches[#matches + 1] = idKey | |||
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 | ||
matches = find_matching_ids(idsTable, keyPath, searchValue) | |||
else | |||
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 | local v = resolve_entry_path_value(data, idKey, parsedPath) | ||
if is_nonempty_value(v) then | |||
matches[#matches + 1] = idKey | |||
end | end | ||
end | end | ||
| Строка 584: | Строка 758: | ||
matches = {} | matches = {} | ||
for _, idKey in ipairs(ids) do | for _, idKey in ipairs(ids) do | ||
local | local v = resolve_path_value(data, idKey, parsedPath) | ||
if type( | if type(v) == "table" and v[target] ~= nil then | ||
local | matches[#matches + 1] = idKey | ||
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 | ||
else | |||
local target = tostring(searchValue) | |||
matches = {} | |||
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 | ||
| Строка 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 calls = {} | local calls = {} | ||
local nestedOptions = { | |||
includeJsonAtPrefix = true, | |||
nestedKeyMode = "prefixed", | |||
skipPrimitiveRoot = false, | |||
} | |||
local rawTypeOptions = { | |||
includeJsonAtPrefix = false, | |||
nestedKeyMode = "raw", | |||
skipPrimitiveRoot = true, | |||
} | |||
local | local function makeCall(obj) | ||
if type(obj) ~= "table" then | |||
return | |||
end | |||
local idKey = choose_id_key(obj) | |||
if | if not idKey then | ||
return | return | ||
end | end | ||
local parts = { "{{Шаблон:" .. resolve_template_path(tplPath) } | |||
if tplArgs ~= "" then | |||
if | parts[#parts + 1] = tplArgs | ||
end | end | ||
local parts = | 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 next(v) ~= nil then | |||
append_table_fields(parts, v, nestedOptions, k) | |||
append_table_fields(parts, v, rawTypeOptions, nil) | |||
end | |||
parts[#parts + 1] = | elseif v ~= nil then | ||
parts[#parts + 1] = "value=" .. tostring(v) | |||
end | end | ||
else | else | ||
if type(v) == "table" then | |||
if v ~= nil then | if next(v) ~= nil then | ||
parts | append_table_fields(parts, v, nestedOptions, k) | ||
end | end | ||
elseif v ~= nil then | |||
parts[#parts + 1] = k .. "=" .. tostring(v) | |||
end | end | ||
end | end | ||
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 | ||
makeCall(item) | |||
end | end | ||
else | else | ||
makeCall(data) | |||
end | end | ||
| Строка 916: | Строка 1268: | ||
end | end | ||
return frame:preprocess(table.concat(calls, " ")) | |||
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 | ||