Модуль:Сущность/data
Материал из Space Station 14 Вики
Для документации этого модуля может быть создана страница Модуль:Сущность/data/doc
local p = {}
local getArgs = require('Module:Arguments').getArgs
local JsonPaths = require('Module:JsonPaths')
local dpOk, dpModule = pcall(require, "Module:GetField")
local dp = dpOk and dpModule or nil
local switchModeRegistry = {}
local switchModeOrder = {}
local function trim(s)
if not s then return s end
return (s:gsub("^%s*(.-)%s*$", "%1"))
end
local function each_csv_value(str, fn)
if not str or str == "" then
return
end
for item in string.gmatch(str, "[^,]+") do
local value = trim(item)
if value ~= "" then
fn(value)
end
end
end
local function load_module_data(page)
local moduleName = JsonPaths.get(page)
local ok, data = pcall(mw.loadData, moduleName)
if ok and type(data) == "table" and type(data.all) ~= "function" then
return data
end
ok, data = pcall(require, moduleName)
if ok and type(data) == "table" and type(data.all) == "function" then
return data.all()
end
return nil
end
local function load_template_content(path)
local title = mw.title.new("Template:" .. path)
if not title then
return nil
end
local ok, content = pcall(title.getContent, title)
if not ok then
return nil
end
return content
end
local function lcfirst(s)
if not s or s == "" then return s end
return string.lower(s:sub(1, 1)) .. (s:sub(2) or "")
end
local function makeTplCall(tplPath, sw, key, id, extra)
local tplStr = "{{" .. tplPath .. "|" .. sw .. "|" .. key
tplStr = tplStr .. "|id=" .. tostring(id)
if extra and extra ~= "" then tplStr = tplStr .. "|" .. extra end
tplStr = tplStr .. "}}"
return tplStr
end
local function add_template_param(params, seen, raw)
local param = trim(raw or "")
if param == "" or param == "id" or param:match("^%d+$") then
return
end
if not seen[param] then
seen[param] = true
params[#params + 1] = param
end
end
local function collect_template_params(content)
local params = {}
local seen = {}
if not content or content == "" then
return params
end
for param in content:gmatch("{{{%s*([^|}]+)%s*|") do
add_template_param(params, seen, param)
end
for param in content:gmatch("{{{%s*([^|}]+)%s*}}") do
add_template_param(params, seen, param)
end
return params
end
local function get_template_params(tplPath, content)
return collect_template_params(content)
end
local function sort_entries_by_priority(entries)
table.sort(entries, function(a, b)
if a.priority == b.priority then return a.idx < b.idx end
return a.priority > b.priority
end)
end
local function make_source(kind, name, pathName, tplPath)
return { kind = kind, name = name, pathName = pathName, tplPath = tplPath }
end
local function register_switch_mode(name, cfg)
switchModeRegistry[name] = cfg or {}
switchModeOrder[#switchModeOrder + 1] = name
end
local function new_switch_state()
local state = { keyOrder = {}, keyToTemplates = {}, keySources = {} }
for _, sw in ipairs(switchModeOrder) do
state.keyOrder[sw] = {}
state.keyToTemplates[sw] = {}
state.keySources[sw] = {}
end
return state
end
local function ensure_switch_key(state, sw, key)
local byKey = state.keyToTemplates[sw]
if not byKey[key] then
byKey[key] = {}
state.keyOrder[sw][#state.keyOrder[sw] + 1] = key
end
return byKey[key]
end
local function add_switch_entry(state, sw, key, entry)
local bucket = ensure_switch_key(state, sw, key)
entry.idx = #bucket + 1
bucket[#bucket + 1] = entry
end
local function collect_tpl_calls(entries)
local tplCalls = {}
local sources = {}
if #entries > 0 then
sort_entries_by_priority(entries)
for _, e in ipairs(entries) do
tplCalls[#tplCalls + 1] = e.tpl
sources[#sources + 1] = e.source
end
end
return tplCalls, sources
end
local function makeSourceLink(s)
local className =
(s.name:sub(1, 1):upper() .. s.name:sub(2)) ..
(s.kind and (s.kind:sub(1, 1):upper() .. s.kind:sub(2)) or "")
local tplLabel = "Template:" .. s.tplPath
return "[[" .. tplLabel .. "|" .. className .. "]]"
end
local function renderTitleBlock(key, tplCalls, sources, includeHeader, frame, showSource)
local parts = {}
if tplCalls and #tplCalls > 0 then
for i, tpl in ipairs(tplCalls) do
local add = true
if frame then
local expanded = frame:preprocess(tpl)
add = expanded and trim(expanded) ~= ""
end
if add then
local src = sources and sources[i]
local line = '<div>' .. tpl .. '</div>'
if showSource and src then
line = line .. '<div class="ts-Сущность-field">' .. makeSourceLink(src) .. '</div>'
end
parts[#parts + 1] = '<div class="ts-Сущность">' .. line .. '</div>'
end
end
if #parts == 0 then
return ""
end
if includeHeader then
table.insert(parts, 1, "<h2>" .. mw.text.encode(key) .. "</h2>")
end
end
return table.concat(parts, "\n")
end
local function split_title_key(key)
local main, sub = (key or ""):match("^([^_]+)_(.+)$")
if main and sub then
return main, sub
end
return key, nil
end
local function renderGroupedTitleBlocks(frame, keyOrder, keyToTemplates, noHeaders, showSource)
local groups = {}
local groupOrder = {}
for _, key in ipairs(keyOrder or {}) do
local mainTitle, subTitle = split_title_key(key)
if mainTitle and mainTitle ~= "" then
local group = groups[mainTitle]
if not group then
group = { blocks = {} }
groups[mainTitle] = group
groupOrder[#groupOrder + 1] = mainTitle
end
group.blocks[#group.blocks + 1] = {
subTitle = subTitle,
entries = keyToTemplates[key] or {}
}
end
end
local out = {}
for _, mainTitle in ipairs(groupOrder) do
local group = groups[mainTitle]
local parts = {}
if not noHeaders then
parts[#parts + 1] = "<h2>" .. mw.text.encode(mainTitle) .. "</h2>"
end
for _, block in ipairs(group.blocks or {}) do
local tplCalls, sources = collect_tpl_calls(block.entries or {})
local blockText = renderTitleBlock(block.subTitle or mainTitle, tplCalls, sources, false, frame, showSource)
if blockText ~= "" then
if block.subTitle and not noHeaders then
parts[#parts + 1] = "<h3>" .. mw.text.encode(block.subTitle) .. "</h3>"
end
parts[#parts + 1] = blockText
end
end
if #parts > 0 then
out[#out + 1] = table.concat(parts, "\n")
end
end
return table.concat(out, "\n")
end
local function normalizeFilterKey(s)
s = trim(s or "")
s = s:gsub("%s*_%s*", "_")
return s
end
local function matches_card_list(list, callKey, compositeKey)
if not list then
return false
end
callKey = normalizeFilterKey(callKey)
compositeKey = normalizeFilterKey(compositeKey)
return list[callKey] or list[compositeKey] or false
end
local function buildCardCall(merged, entityId)
local parts = {}
if entityId and entityId ~= "" then
parts[#parts + 1] = "id=" .. mw.text.encode(entityId)
end
if merged.tags and #merged.tags > 0 then
table.sort(merged.tags)
parts[#parts + 1] = "тип=" .. table.concat(merged.tags, ", ")
end
if merged.sections and #merged.sections > 0 then
table.sort(merged.sections, function(a, b)
if a == "Сущность" then return true end
if b == "Сущность" then return false end
return a < b
end)
parts[#parts + 1] = "sections=" .. table.concat(merged.sections, ", ")
for _, section in ipairs(merged.sections) do
local labels = merged.labelLists[section]
if labels and #labels > 0 then
local enc = {}
for i = 1, #labels do
enc[i] = mw.text.encode(labels[i])
end
parts[#parts + 1] = mw.text.encode(section) .. "=" .. table.concat(enc, ", ")
end
end
end
for compositeKey, displayLabel in pairs(merged.labelOverrides or {}) do
if displayLabel and displayLabel ~= "" then
parts[#parts + 1] = mw.text.encode(compositeKey .. "_label") .. "=" .. displayLabel
end
end
for compositeKey, content in pairs(merged.contentByKey or {}) do
if content and content ~= "" then
parts[#parts + 1] = mw.text.encode(compositeKey) .. "=" .. content
end
end
if #parts == 0 then
return ""
end
return "{{карточка/сущность|" .. table.concat(parts, "|") .. "}}"
end
local function cardWrapper(frame, keyOrder, keyToTemplates, keySources, entityId, noHeaders, cardFilter)
local merged = {
sections = {},
sectionsMap = {},
labelLists = {},
labelSets = {},
labelOverrides = {},
contentByKey = {},
tags = {},
tagSet = {}
}
local rawContentParts = {}
for _, callKey in ipairs(keyOrder or {}) do
local entries = keyToTemplates[callKey] or {}
if #entries > 0 then
sort_entries_by_priority(entries)
for _, e in ipairs(entries) do
local displayLabel = trim(frame:preprocess(e.tplLabel or "") or "")
local content = trim(frame:preprocess(e.tplContent or "") or "")
local tagText = ""
if e.tplTag then
tagText = trim(frame:preprocess(e.tplTag or "") or "")
end
local compositeKey = (callKey:find("_", 1, true)) and callKey or ("Сущность_" .. callKey)
local section = (callKey:find("_", 1, true)) and callKey:match("^([^_]+)") or "Сущность"
local isWhitelisted = cardFilter and matches_card_list(cardFilter.whitelist, callKey, compositeKey) or
false
local isBlacklisted = cardFilter and matches_card_list(cardFilter.blacklist, callKey, compositeKey) or
false
if isWhitelisted and content ~= "" then
rawContentParts[#rawContentParts + 1] = content
end
local allowCardEntry = not isWhitelisted and not isBlacklisted and
((not cardFilter) or (not cardFilter.hasWhitelist) or
matches_card_list(cardFilter.cardWhitelist, callKey, compositeKey))
if allowCardEntry and (displayLabel ~= "" or content ~= "") then
if not merged.sectionsMap[section] then
merged.sectionsMap[section] = true
merged.sections[#merged.sections + 1] = section
end
if displayLabel ~= "" and (not merged.labelOverrides[compositeKey] or merged.labelOverrides[compositeKey] == "") then
merged.labelOverrides[compositeKey] = displayLabel
end
if content ~= "" then
local prev = merged.contentByKey[compositeKey]
if prev and prev ~= "" then
merged.contentByKey[compositeKey] = prev .. "\n" .. content
else
merged.contentByKey[compositeKey] = content
end
end
merged.labelSets[section] = merged.labelSets[section] or {}
if not merged.labelSets[section][compositeKey] then
merged.labelSets[section][compositeKey] = true
local cur = merged.labelLists[section] or {}
cur[#cur + 1] = compositeKey
merged.labelLists[section] = cur
end
end
if allowCardEntry and tagText ~= "" then
if not merged.tagSet[tagText] then
merged.tagSet[tagText] = true
merged.tags[#merged.tags + 1] = tagText
end
end
end
end
end
each_csv_value(frame.args.cardTag or "", function(extraTag)
if not merged.tagSet[extraTag] then
merged.tagSet[extraTag] = true
merged.tags[#merged.tags + 1] = extraTag
end
end)
local out = {}
if #rawContentParts > 0 then
out[#out + 1] = table.concat(rawContentParts, "\n")
end
local cardCall = buildCardCall(merged, entityId)
if noHeaders then
local hasLabel = false
for _, v in pairs(merged.labelOverrides or {}) do
if v and v ~= "" then
hasLabel = true
break
end
end
if not hasLabel then
for _, lst in pairs(merged.labelLists or {}) do
if #lst > 0 then
hasLabel = true
break
end
end
end
local hasContent = false
for _, v in pairs(merged.contentByKey or {}) do
if v and v ~= "" then
hasContent = true
break
end
end
if not hasLabel and not hasContent then
return table.concat(out, "\n")
end
end
if cardCall ~= "" then
out[#out + 1] = cardCall
end
return table.concat(out, "\n")
end
register_switch_mode("card", {
full = true,
build_entry = function(ctx, key)
return {
tplLabel = makeTplCall(ctx.tplPath, "cardLabel", key, ctx.id, ctx.extra),
tplContent = makeTplCall(ctx.tplPath, "cardContent", key, ctx.id, ctx.extra),
tplTag = makeTplCall(ctx.tplPath, "cardTag", key, ctx.id, ctx.extra),
source = ctx.source,
priority = ctx.priority
}
end,
build_preview_entry = function(ctx, key)
return {
tplLabel = makeTplCall(ctx.tplPath, "cardLabel", key, ""),
tplContent = makeTplCall(ctx.tplPath, "cardContent", key, ""),
source = ctx.source,
priority = ctx.priority
}
end,
add_entity_extras = function(state, parsed, ctx)
if type(parsed) == "table" and parsed.cardTag and parsed.cardTag ~= "" then
add_switch_entry(state, "card", "cardTag", {
tplLabel = "",
tplContent = "",
tplTag = makeTplCall(ctx.tplPath, "cardTag", "cardTag", ctx.id, ctx.extra),
source = ctx.source,
priority = ctx.priority
})
end
end,
render_full = function(frame, keyOrder, keyToTemplates, keySources, entityId, noHeaders, showSource, cardFilter)
return cardWrapper(frame, keyOrder, keyToTemplates, keySources, entityId, noHeaders, cardFilter)
end
})
register_switch_mode("title", {
full = true,
build_entry = function(ctx, key)
return {
tpl = makeTplCall(ctx.tplPath, "title", key, ctx.id, ctx.extra),
source = ctx.source,
priority = ctx.priority
}
end,
build_preview_entry = function(ctx, key)
return {
tpl = makeTplCall(ctx.tplPath, "title", key, ""),
source = ctx.source,
priority = ctx.priority
}
end,
render_full = function(frame, keyOrder, keyToTemplates, keySources, entityId, noHeaders, showSource)
return renderGroupedTitleBlocks(frame, keyOrder, keyToTemplates, noHeaders, showSource)
end
})
local function getTemplateMeta(frame, tplPath)
local expanded = frame:expandTemplate {
title = tplPath,
args = { "json" }
}
local ok, data = pcall(mw.text.jsonDecode, expanded)
if not ok or type(data) ~= "table" then
return ""
end
if data.card == nil then
local cardKeys = {}
local seen = {}
for base, labels in pairs(data) do
if type(base) == "string" and base ~= "card" and base:sub(1, 4) == "card" and type(labels) == "table" then
for _, lab in ipairs(labels) do
if not seen[lab] then
seen[lab] = true
cardKeys[#cardKeys + 1] = lab
end
end
end
end
data.card = cardKeys
end
return data
end
local function parseListArg(str)
local res = {}
if not str or str == "" then return res end
for item in string.gmatch(str, "[^,]+") do
local s = normalizeFilterKey(item)
if s ~= "" then
local a, b = s:match("^([^_]+)_(.+)$")
if a and b then
res[a] = res[a] or {}
res[a][b] = true
end
end
end
return res
end
local function build_key_filter(args)
local filter = {}
filter.blacklist = parseListArg(args.blacklist or "")
filter.whitelist = parseListArg(args.whitelist or "")
filter.hasWhitelist = false
for _, sw in ipairs(switchModeOrder) do
if filter.whitelist[sw] and next(filter.whitelist[sw]) ~= nil then
filter.hasWhitelist = true
break
end
end
if not filter.hasWhitelist and filter.whitelist.cardContent and next(filter.whitelist.cardContent) ~= nil then
filter.hasWhitelist = true
end
return filter
end
local function build_render_options(filter)
return {
noHeaders = false,
cardFilter = {
hasWhitelist = filter.whitelist.cardContent and next(filter.whitelist.cardContent) ~= nil or false,
cardWhitelist = filter.whitelist.card or {},
blacklist = filter.blacklist.cardContent or {},
whitelist = filter.whitelist.cardContent or {}
}
}
end
local function should_include_key(filter, sw, key)
if filter.hasWhitelist then
if filter.whitelist[sw] and filter.whitelist[sw][key] then
return true
end
if sw == "card" and filter.whitelist.cardContent and filter.whitelist.cardContent[key] then
return true
end
return false
end
return not (filter.blacklist[sw] and filter.blacklist[sw][key])
end
local function parse_csv_set(str)
local res = {}
each_csv_value(str, function(name)
res[name] = true
end)
return res
end
local function apply_entity_set_filters(foundSet, whitelistSet, blacklistSet)
local hasWhitelist = next(whitelistSet or {}) ~= nil
if hasWhitelist then
for name in pairs(foundSet) do
if not whitelistSet[name] then
foundSet[name] = nil
end
end
end
for name in pairs(blacklistSet or {}) do
foundSet[name] = nil
end
end
local function collect_entity_sets(id, componentDefs, prototypeStoreDefs,
componentWhitelist, componentBlacklist, prototypeWhitelist, prototypeBlacklist)
local foundComponents, foundPrototypes = {}, {}
local compList = componentDefs[id]
if type(compList) == "table" then
for _, v in ipairs(compList) do
if type(v) == "string" then
foundComponents[v] = true
end
end
end
each_csv_value(id, function(name)
if name ~= id then
if componentDefs[name] ~= nil then
foundComponents[name] = true
elseif prototypeStoreDefs[name] ~= nil then
foundPrototypes[name] = true
end
end
end)
apply_entity_set_filters(foundComponents, parse_csv_set(componentWhitelist), parse_csv_set(componentBlacklist))
apply_entity_set_filters(foundPrototypes, parse_csv_set(prototypeWhitelist), parse_csv_set(prototypeBlacklist))
return foundComponents, foundPrototypes
end
local function resolve_priority(parsed)
local basePriority = 1
if type(parsed) == "table" and parsed.priority ~= nil then
if type(parsed.priority) == "number" then
basePriority = parsed.priority
else
local pnum = tonumber(parsed.priority)
if pnum then
basePriority = pnum
end
end
end
return basePriority
end
local function get_selective_extra(id, dataPage, paramNames)
if not dp or type(dp.flattenFieldSelectiveDirect) ~= "function" then
return ""
end
if type(paramNames) ~= "table" or #paramNames == 0 then
return ""
end
return dp.flattenFieldSelectiveDirect(id, dataPage, paramNames) or ""
end
local function add_card_tag_value(tags, seen, value)
value = trim(value or "")
if value == "" or seen[value] then
return
end
seen[value] = true
tags[#tags + 1] = value
end
local function merge_card_tag_text(...)
local tags = {}
local seen = {}
for i = 1, select("#", ...) do
each_csv_value(select(i, ...), function(value)
add_card_tag_value(tags, seen, value)
end)
end
return table.concat(tags, ", ")
end
local function add_entries_from_meta(state, parsed, ctx, filter, isPreview)
for _, sw in ipairs(switchModeOrder) do
local mode = switchModeRegistry[sw] or {}
local keys
if type(mode.get_keys) == "function" then
keys = mode.get_keys(parsed)
else
keys = (type(parsed) == "table" and parsed[sw]) or {}
end
if type(keys) == "table" then
for _, key in ipairs(keys) do
if (not filter) or should_include_key(filter, sw, key) then
local buildFn = isPreview and (mode.build_preview_entry or mode.build_entry) or mode.build_entry
if type(buildFn) == "function" then
local entry = buildFn(ctx, key)
if entry then
add_switch_entry(state, sw, key, entry)
end
end
end
end
end
if (not isPreview) and type(mode.add_entity_extras) == "function" then
mode.add_entity_extras(state, parsed, ctx)
end
end
end
local function extract_whitelist_search_strings(keyFilter)
if not keyFilter or not keyFilter.hasWhitelist then
return nil
end
local strings = {}
for sw, keys in pairs(keyFilter.whitelist) do
if type(keys) == "table" then
for key in pairs(keys) do
strings[#strings + 1] = key
end
end
end
if #strings == 0 then
return nil
end
return strings
end
local function content_matches_whitelist(content, searchStrings)
if not searchStrings then
return true
end
if not content then
return false
end
for _, s in ipairs(searchStrings) do
if string.find(content, s, 1, true) then
return true
end
end
return false
end
local function each_entity_data(frame, id, onEntity, onMissing, keyFilter)
local componentWhitelist = frame.args.componentWhitelist or frame.args.componentwhitelist or ""
local componentBlacklist = frame.args.componentBlacklist or frame.args.componentblacklist or ""
local prototypeWhitelist = frame.args.prototypeWhitelist or frame.args.prototypewhitelist or ""
local prototypeBlacklist = frame.args.prototypeBlacklist or frame.args.prototypeblacklist or ""
local componentDefs = load_module_data("component.json")
local prototypeStoreDefs = load_module_data("prototype_store.json")
if not componentDefs or not prototypeStoreDefs then
return false
end
local foundComponents, foundPrototypes = collect_entity_sets(id, componentDefs, prototypeStoreDefs,
componentWhitelist, componentBlacklist, prototypeWhitelist, prototypeBlacklist)
local compWhitelistSet = parse_csv_set(componentWhitelist)
local compBlacklistSet = parse_csv_set(componentBlacklist)
local protoWhitelistSet = parse_csv_set(prototypeWhitelist)
local protoBlacklistSet = parse_csv_set(prototypeBlacklist)
local compHasWhitelist = next(compWhitelistSet) ~= nil
local protoHasWhitelist = next(protoWhitelistSet) ~= nil
local anyEntityWhitelist = compHasWhitelist or protoHasWhitelist
local whitelistSearchStrings = extract_whitelist_search_strings(keyFilter)
local function processEntity(kind, name, isStore)
local pathName = lcfirst(name)
local tplPath = kind .. "/" .. pathName
if isStore then
tplPath = tplPath .. "/store"
end
local content = load_template_content(tplPath)
if not content then
if onMissing then
onMissing(kind, name, isStore, tplPath)
end
return
end
if not content_matches_whitelist(content, whitelistSearchStrings) then
return
end
local parsed = getTemplateMeta(frame, tplPath)
if type(parsed) ~= "table" then
parsed = {}
end
local extra = ""
local paramNames = get_template_params(tplPath, content)
if dp then
local dataPage = tplPath .. ".json"
extra = get_selective_extra(id, dataPage, paramNames)
end
onEntity(parsed, {
tplPath = tplPath,
id = id,
extra = extra,
source = make_source(kind, name, pathName, tplPath),
priority = resolve_priority(parsed)
})
end
for compName in pairs(foundComponents) do
if not anyEntityWhitelist or compHasWhitelist then
processEntity("component", compName, false)
end
end
for protoName in pairs(foundPrototypes) do
if not anyEntityWhitelist or protoHasWhitelist then
processEntity("prototype", protoName, false)
end
end
local componentStoreDefs = load_module_data("component_store.json")
if type(componentStoreDefs) == "table" and (not anyEntityWhitelist or compHasWhitelist) then
local compStore = componentStoreDefs[id]
if type(compStore) == "table" then
for compName in pairs(compStore) do
local allowed = true
if compBlacklistSet[compName] then
allowed = false
elseif compHasWhitelist and not compWhitelistSet[compName] then
allowed = false
end
if allowed then
processEntity("component", compName, true)
end
end
end
end
if type(prototypeStoreDefs) == "table" and (not anyEntityWhitelist or protoHasWhitelist) then
local protoStore = prototypeStoreDefs[id]
if type(protoStore) == "table" then
for protoName in pairs(protoStore) do
local allowed = true
if protoBlacklistSet[protoName] then
allowed = false
elseif protoHasWhitelist and not protoWhitelistSet[protoName] then
allowed = false
end
if allowed then
processEntity("prototype", protoName, true)
end
end
end
end
return true
end
local function collect_card_tag_text(frame, args, id)
local filter = build_key_filter(args)
local cardFilter = build_render_options(filter).cardFilter
local entries = {}
local ok = each_entity_data(frame, id, function(parsed, ctx)
local keys = parsed.card or {}
if type(keys) == "table" then
for _, key in ipairs(keys) do
local compositeKey = (key:find("_", 1, true)) and key or ("Сущность_" .. key)
local isWhitelisted = matches_card_list(cardFilter.whitelist, key, compositeKey)
local isBlacklisted = matches_card_list(cardFilter.blacklist, key, compositeKey)
local allowCardEntry = not isWhitelisted and not isBlacklisted and
((not cardFilter.hasWhitelist) or matches_card_list(cardFilter.cardWhitelist, key, compositeKey))
if allowCardEntry then
entries[#entries + 1] = {
tplTag = makeTplCall(ctx.tplPath, "cardTag", key, ctx.id, ctx.extra),
priority = ctx.priority,
idx = #entries + 1
}
end
end
end
if parsed.cardTag and parsed.cardTag ~= "" then
entries[#entries + 1] = {
tplTag = makeTplCall(ctx.tplPath, "cardTag", "cardTag", ctx.id, ctx.extra),
priority = ctx.priority,
idx = #entries + 1
}
end
end)
if not ok then
return trim(args.tag or "")
end
sort_entries_by_priority(entries)
local tags = {}
local seen = {}
for _, entry in ipairs(entries) do
local tagText = trim(frame:preprocess(entry.tplTag or "") or "")
add_card_tag_value(tags, seen, tagText)
end
return merge_card_tag_text(table.concat(tags, ", "), args.tag)
end
p.mergeCardTagText = merge_card_tag_text
p.collectCardTagText = collect_card_tag_text
local function build_missing_template_error(kind, name, isStore, tplPath)
local baseType = (kind and (kind:sub(1, 1):upper() .. kind:sub(2)) or "")
local classType = baseType
if isStore then
classType = classType .. "Store"
end
local className = name .. baseType
local tplLabel = "Template:" .. tplPath
return "{{сущность/infobox/base|тип=" .. classType .. "|название=" .. className .. "|ссылка=" .. tplLabel .. "}}"
end
local function renderBlocks(frame, state, renderOptions, entityId, showSource)
local outLocal = {}
local noHeaders = renderOptions and renderOptions.noHeaders
local cardFilter = renderOptions and renderOptions.cardFilter
for _, sw in ipairs(switchModeOrder) do
local mode = switchModeRegistry[sw] or {}
if mode.full then
local outStr = ""
if type(mode.render_full) == "function" then
outStr = mode.render_full(frame, state.keyOrder[sw], state.keyToTemplates[sw], state.keySources[sw],
entityId, noHeaders, showSource, cardFilter)
end
if outStr and outStr ~= "" then outLocal[#outLocal + 1] = outStr end
else
for _, key in ipairs(state.keyOrder[sw] or {}) do
local entries = state.keyToTemplates[sw][key] or {}
if type(mode.render_key) == "function" then
local outStr = mode.render_key(frame, key, entries, noHeaders, showSource)
if outStr and outStr ~= "" then outLocal[#outLocal + 1] = outStr end
end
end
end
end
return outLocal
end
function p.get(frame)
local args = getArgs(frame, { removeBlanks = false })
local id = args[1] or ""
if id == "" then return "" end
local showSource = trim(args.showSource or "") == ""
local filter = build_key_filter(args)
local renderOptions = build_render_options(filter)
local state = new_switch_state()
local errors = {}
local ok = each_entity_data(frame, id, function(parsed, ctx)
add_entries_from_meta(state, parsed, ctx, filter, false)
end, function(kind, name, isStore, tplPath)
if not filter.hasWhitelist then
errors[#errors + 1] = build_missing_template_error(kind, name, isStore, tplPath)
end
end, filter)
if not ok then return "" end
local out = {}
if #errors > 0 then
out[#out + 1] = '{{сущность/infobox|' .. table.concat(errors, "\n") .. '}}'
end
renderOptions.noHeaders = filter.hasWhitelist
local blocks = renderBlocks(frame, state, renderOptions, id, showSource)
for _, b in ipairs(blocks) do
out[#out + 1] = b
end
return frame:preprocess(table.concat(out, "\n"))
end
function p.preview(frame)
local args = getArgs(frame, { removeBlanks = false })
local tplPath = args[1] or ""
if tplPath == "" then return "" end
local showSource = trim(args.nosource or "") == ""
local previewFilter = build_key_filter(args)
local renderOptions = build_render_options(previewFilter)
local content = load_template_content(tplPath)
if not content then
return ""
end
local parsed = getTemplateMeta(frame, tplPath) or {}
if type(parsed) ~= "table" then
parsed = {}
end
local state = new_switch_state()
add_entries_from_meta(state, parsed, {
tplPath = tplPath,
id = "",
extra = "",
source = make_source("", tplPath, tplPath, tplPath),
priority = 1
}, nil, true)
local hasWhitelist = previewFilter.hasWhitelist
renderOptions.noHeaders = hasWhitelist
local out = {}
local blocks = renderBlocks(frame, state, renderOptions, "", showSource)
for _, b in ipairs(blocks) do
out[#out + 1] = b
end
return frame:preprocess(table.concat(out, "\n"))
end
return p