Модуль:Сущность/data: различия между версиями

Новая страница: «local p = {} local getArgs = require('Module:Arguments').getArgs local BASE_USER = "IanComradeBot/" local moduleDataCache = {} local switchModeRegistry = {} local switchModeOrder = {} local function trim(s) if not s then return s end return (s:gsub("^%s*(.-)%s*$", "%1")) end local function load_module_data(page) local moduleName = "Module:" .. BASE_USER .. page .. "/data" if moduleDataCache[moduleName] ~= nil then return moduleDataC...»
 
Отмена версии 315901, сделанной Pok (обсуждение)
Метка: отмена
 
(не показано 19 промежуточных версий этого же участника)
Строка 1: Строка 1:
local p = {}
local p = {}
local getArgs = require('Module:Arguments').getArgs
local getArgs = require('Module:Arguments').getArgs
local BASE_USER = "IanComradeBot/"
local JsonPaths = require('Module:JsonPaths')
local moduleDataCache = {}
 
local dpOk, dpModule = pcall(require, "Module:GetField")
local dp = dpOk and dpModule or nil
 
local switchModeRegistry = {}
local switchModeRegistry = {}
local switchModeOrder = {}
local switchModeOrder = {}
Строка 9: Строка 12:
     if not s then return s end
     if not s then return s end
     return (s:gsub("^%s*(.-)%s*$", "%1"))
     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
end


local function load_module_data(page)
local function load_module_data(page)
     local moduleName = "Module:" .. BASE_USER .. page .. "/data"
     local moduleName = JsonPaths.get(page)
    if moduleDataCache[moduleName] ~= nil then
        return moduleDataCache[moduleName]
    end
     local ok, data = pcall(mw.loadData, moduleName)
     local ok, data = pcall(mw.loadData, moduleName)
     if not ok then
     if not ok then
        moduleDataCache[moduleName] = nil
         return nil
         return nil
     end
     end
    moduleDataCache[moduleName] = data
     return data
     return data
end
end
Строка 27: Строка 37:
local function load_template_content(path)
local function load_template_content(path)
     local title = mw.title.new("Template:" .. path)
     local title = mw.title.new("Template:" .. path)
     if not title then return nil end
     if not title then
     local ok, content = pcall(function() return title:getContent() end)
        return nil
     if not ok then return nil end
    end
     local ok, content = pcall(title.getContent, title)
     if not ok then
        return nil
    end
     return content
     return content
end
end
Строка 44: Строка 58:
     tplStr = tplStr .. "}}"
     tplStr = tplStr .. "}}"
     return 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
end


Строка 76: Строка 123:
     if not byKey[key] then
     if not byKey[key] then
         byKey[key] = {}
         byKey[key] = {}
         table.insert(state.keyOrder[sw], key)
         state.keyOrder[sw][#state.keyOrder[sw] + 1] = key
     end
     end
     return byKey[key]
     return byKey[key]
Строка 102: Строка 149:
local function makeSourceLink(s)
local function makeSourceLink(s)
     local className =
     local className =
         (s.name:sub(1,1):upper() .. s.name:sub(2)) ..
         (s.name:sub(1, 1):upper() .. s.name:sub(2)) ..
         (s.kind and (s.kind:sub(1,1):upper() .. s.kind:sub(2)) or "")
         (s.kind and (s.kind:sub(1, 1):upper() .. s.kind:sub(2)) or "")


     local tplLabel = "Template:" .. s.tplPath
     local tplLabel = "Template:" .. s.tplPath
Строка 109: Строка 156:
end
end


local function renderTitleBlock(key, tplCalls, sources, includeHeader, frame)
local function renderTitleBlock(key, tplCalls, sources, includeHeader, frame, showSource)
     local parts = {}
     local parts = {}
     if tplCalls and #tplCalls > 0 then
     if tplCalls and #tplCalls > 0 then
Строка 120: Строка 167:
             if add then
             if add then
                 local src = sources and sources[i]
                 local src = sources and sources[i]
                 local line = '<div>' .. tpl .. '</div><div class="ts-Сущность-field">' .. makeSourceLink(src) .. '</div>'
                 local line = '<div>' .. tpl .. '</div>'
                 table.insert(parts, '<div class="ts-Сущность">' .. line .. '</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
         end
         end
Строка 132: Строка 182:
     end
     end
     return table.concat(parts, "\n")
     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
end


Строка 137: Строка 259:
     local parts = {}
     local parts = {}


    -- id сущности
     if entityId and entityId ~= "" then
     if entityId and entityId ~= "" then
         parts[#parts + 1] = "id=" .. mw.text.encode(entityId)
         parts[#parts + 1] = "id=" .. mw.text.encode(entityId)
     end
     end


    -- типы
     if merged.tags and #merged.tags > 0 then
     if merged.tags and #merged.tags > 0 then
         table.sort(merged.tags)
         table.sort(merged.tags)
Строка 148: Строка 268:
     end
     end


    -- секции карточки
     if merged.sections and #merged.sections > 0 then
     if merged.sections and #merged.sections > 0 then
         table.sort(merged.sections, function(a, b)
         table.sort(merged.sections, function(a, b)
Строка 168: Строка 287:
     end
     end


    -- содержимое полей
     for compositeKey, displayLabel in pairs(merged.labelOverrides or {}) do
     for compositeKey, displayLabel in pairs(merged.labelOverrides or {}) do
         if displayLabel and displayLabel ~= "" then
         if displayLabel and displayLabel ~= "" then
Строка 187: Строка 305:
end
end


local function cardWrapper(frame, keyOrder, keyToTemplates, keySources, entityId, noHeaders)
local function cardWrapper(frame, keyOrder, keyToTemplates, keySources, entityId, noHeaders, cardFilter)
     local merged = {
     local merged = {
         sections = {},
         sections = {},
Строка 198: Строка 316:
         tagSet = {}
         tagSet = {}
     }
     }
    local rawContentParts = {}
     for _, callKey in ipairs(keyOrder or {}) do
     for _, callKey in ipairs(keyOrder or {}) do
         local entries = keyToTemplates[callKey] or {}
         local entries = keyToTemplates[callKey] or {}
Строка 212: Строка 331:
                 local section = (callKey:find("_", 1, true)) and callKey:match("^([^_]+)") or "Сущность"
                 local section = (callKey:find("_", 1, true)) and callKey:match("^([^_]+)") or "Сущность"


                 if displayLabel ~= "" or content ~= "" then
                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
                     if not merged.sectionsMap[section] then
                         merged.sectionsMap[section] = true
                         merged.sectionsMap[section] = true
                         table.insert(merged.sections, section)
                         merged.sections[#merged.sections + 1] = section
                     end
                     end
                     if displayLabel ~= "" and (not merged.labelOverrides[compositeKey] or merged.labelOverrides[compositeKey] == "") then
                     if displayLabel ~= "" and (not merged.labelOverrides[compositeKey] or merged.labelOverrides[compositeKey] == "") then
Строка 233: Строка 364:
                         merged.labelSets[section][compositeKey] = true
                         merged.labelSets[section][compositeKey] = true
                         local cur = merged.labelLists[section] or {}
                         local cur = merged.labelLists[section] or {}
                         table.insert(cur, compositeKey)
                         cur[#cur + 1] = compositeKey
                         merged.labelLists[section] = cur
                         merged.labelLists[section] = cur
                     end
                     end
                 end
                 end


                 if tagText ~= "" then
                 if allowCardEntry and tagText ~= "" then
                     if not merged.tagSet[tagText] then
                     if not merged.tagSet[tagText] then
                         merged.tagSet[tagText] = true
                         merged.tagSet[tagText] = true
                         table.insert(merged.tags, tagText)
                         merged.tags[#merged.tags + 1] = tagText
                     end
                     end
                 end
                 end
Строка 247: Строка 378:
         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
     if noHeaders then
         local hasLabel = false
         local hasLabel = false
         for _, v in pairs(merged.labelOverrides or {}) do
         for _, v in pairs(merged.labelOverrides or {}) do
             if v and v ~= "" then hasLabel = true break end
             if v and v ~= "" then
                hasLabel = true
                break
            end
         end
         end
         if not hasLabel then
         if not hasLabel then
             for _, lst in pairs(merged.labelLists or {}) do
             for _, lst in pairs(merged.labelLists or {}) do
                 if #lst > 0 then hasLabel = true break end
                 if #lst > 0 then
                    hasLabel = true
                    break
                end
             end
             end
         end
         end
Строка 261: Строка 412:
         local hasContent = false
         local hasContent = false
         for _, v in pairs(merged.contentByKey or {}) do
         for _, v in pairs(merged.contentByKey or {}) do
             if v and v ~= "" then hasContent = true break end
             if v and v ~= "" then
                hasContent = true
                break
            end
         end
         end


         if not hasLabel and not hasContent then
         if not hasLabel and not hasContent then
             return ""
             return table.concat(out, "\n")
         end
         end
     end
     end


     return buildCardCall(merged, entityId)
    if cardCall ~= "" then
        out[#out + 1] = cardCall
    end
 
     return table.concat(out, "\n")
end
end


Строка 302: Строка 460:
         end
         end
     end,
     end,
     render_full = function(frame, keyOrder, keyToTemplates, keySources, entityId, noHeaders)
     render_full = function(frame, keyOrder, keyToTemplates, keySources, entityId, noHeaders, showSource, cardFilter)
         return cardWrapper(frame, keyOrder, keyToTemplates, keySources, entityId, noHeaders)
         return cardWrapper(frame, keyOrder, keyToTemplates, keySources, entityId, noHeaders, cardFilter)
     end
     end
})
})


register_switch_mode("title", {
register_switch_mode("title", {
    full = true,
     build_entry = function(ctx, key)
     build_entry = function(ctx, key)
         return {
         return {
Строка 322: Строка 481:
         }
         }
     end,
     end,
     render_key = function(frame, key, entries, noHeaders)
     render_full = function(frame, keyOrder, keyToTemplates, keySources, entityId, noHeaders, showSource)
        local tplCalls, sources = collect_tpl_calls(entries)
         return renderGroupedTitleBlocks(frame, keyOrder, keyToTemplates, noHeaders, showSource)
         return renderTitleBlock(key, tplCalls, sources, not noHeaders, frame)
     end
     end
})
})
Строка 347: Строка 505:
                     if not seen[lab] then
                     if not seen[lab] then
                         seen[lab] = true
                         seen[lab] = true
                         table.insert(cardKeys, lab)
                         cardKeys[#cardKeys + 1] = lab
                     end
                     end
                 end
                 end
Строка 362: Строка 520:
     if not str or str == "" then return res end
     if not str or str == "" then return res end
     for item in string.gmatch(str, "[^,]+") do
     for item in string.gmatch(str, "[^,]+") do
         local s = trim(item)
         local s = normalizeFilterKey(item)
         if s ~= "" then
         if s ~= "" then
             local a, b = s:match("^([^_]+)_(.+)$")
             local a, b = s:match("^([^_]+)_(.+)$")
Строка 378: Строка 536:
     filter.blacklist = parseListArg(args.blacklist or "")
     filter.blacklist = parseListArg(args.blacklist or "")
     filter.whitelist = parseListArg(args.whitelist or "")
     filter.whitelist = parseListArg(args.whitelist or "")
     filter.hasWhitelist = next(filter.whitelist) ~= nil
     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
     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
end


local function should_include_key(filter, sw, key)
local function should_include_key(filter, sw, key)
     if filter.hasWhitelist then
     if filter.hasWhitelist then
         return filter.whitelist[sw] and filter.whitelist[sw][key]
         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
     end
     return not (filter.blacklist[sw] and filter.blacklist[sw][key])
     return not (filter.blacklist[sw] and filter.blacklist[sw][key])
end
end


local function each_csv_value(str, fn)
local function parse_csv_set(str)
     if not str or str == "" then
    local res = {}
         return
    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
     end
     for item in string.gmatch(str, "[^,]+") do
 
         local value = trim(item)
     for name in pairs(blacklistSet or {}) do
        if value ~= "" then
         foundSet[name] = nil
            fn(value)
        end
     end
     end
end
end


local function collect_entity_sets(id, componentDefs, prototypeStoreDefs, ignoreComponents, ignorePrototypes)
local function collect_entity_sets(id, componentDefs, prototypeStoreDefs,
                                  componentWhitelist, componentBlacklist, prototypeWhitelist, prototypeBlacklist)
     local foundComponents, foundPrototypes = {}, {}
     local foundComponents, foundPrototypes = {}, {}


Строка 423: Строка 621:
     end)
     end)


     each_csv_value(ignoreComponents, function(name)
     apply_entity_set_filters(foundComponents, parse_csv_set(componentWhitelist), parse_csv_set(componentBlacklist))
        foundComponents[name] = nil
     apply_entity_set_filters(foundPrototypes, parse_csv_set(prototypeWhitelist), parse_csv_set(prototypeBlacklist))
    end)
 
     each_csv_value(ignorePrototypes, function(name)
        foundPrototypes[name] = nil
    end)


     return foundComponents, foundPrototypes
     return foundComponents, foundPrototypes
Строка 447: Строка 640:
     end
     end
     return basePriority
     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
end


Строка 479: Строка 705:
end
end


local function build_missing_template_error(kind, name, isStore, tplPath)
local function extract_whitelist_search_strings(keyFilter)
     local baseType = (kind and (kind:sub(1, 1):upper() .. kind:sub(2)) or "")
     if not keyFilter or not keyFilter.hasWhitelist then
    local classType = baseType
         return nil
    if isStore then
         classType = classType .. "Store"
     end
     end
    local className = name .. baseType
    local tplLabel = "Template:" .. tplPath
    return "{{сущность/infobox/base|тип=" .. classType .. "|название=" .. className .. "|ссылка=" .. tplLabel .. "}}"
end


local function renderBlocks(frame, state, noHeaders, entityId)
     local strings = {}
     local outLocal = {}
     for sw, keys in pairs(keyFilter.whitelist) do
     for _, sw in ipairs(switchModeOrder) do
         if type(keys) == "table" then
        local mode = switchModeRegistry[sw] or {}
             for key in pairs(keys) do
         if mode.full then
                 strings[#strings + 1] = key
            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)
            end
            if outStr and outStr ~= "" then table.insert(outLocal, 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)
                    if outStr and outStr ~= "" then table.insert(outLocal, outStr) end
                end
             end
             end
         end
         end
     end
     end
     return outLocal
 
     if #strings == 0 then
        return nil
    end
 
    return strings
end
end


function p.get(frame)
local function content_matches_whitelist(content, searchStrings)
     local args = getArgs(frame, { removeBlanks = false })
     if not searchStrings then
     local id = args[1] or ""
        return true
     if id == "" then return "" end
    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


     local filter = build_key_filter(args)
     return false
end


     local ignoreComponents = args.ignoreComponents or args.ignoreComponent or ""
local function each_entity_data(frame, id, onEntity, onMissing, keyFilter)
     local ignorePrototypes = args.ignorePrototypes or args.ignorePrototype or ""
    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 componentDefs = load_module_data("component.json")
     local prototypeStoreDefs = load_module_data("prototype_store.json")
     local prototypeStoreDefs = load_module_data("prototype_store.json")
    local componentStoreDefs = load_module_data("component_store.json")
     if not componentDefs or not prototypeStoreDefs then
     if not componentDefs or not prototypeStoreDefs or not componentStoreDefs then return "" end
        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 foundComponents, foundPrototypes = collect_entity_sets(id, componentDefs, prototypeStoreDefs, ignoreComponents,
     local compHasWhitelist = next(compWhitelistSet) ~= nil
        ignorePrototypes)
     local protoHasWhitelist = next(protoWhitelistSet) ~= nil
     local state = new_switch_state()
    local anyEntityWhitelist = compHasWhitelist or protoHasWhitelist


     local ok, dp = pcall(require, "Module:GetField")
     local whitelistSearchStrings = extract_whitelist_search_strings(keyFilter)
    local errors = {}


     local function processEntity(kind, name, isStore)
     local function processEntity(kind, name, isStore)
Строка 545: Строка 778:
         local content = load_template_content(tplPath)
         local content = load_template_content(tplPath)
         if not content then
         if not content then
             if filter.hasWhitelist then
             if onMissing then
                 return
                 onMissing(kind, name, isStore, tplPath)
             end
             end
             errors[#errors + 1] = build_missing_template_error(kind, name, isStore, tplPath)
             return
        end
 
        if not content_matches_whitelist(content, whitelistSearchStrings) then
             return
             return
         end
         end
Строка 558: Строка 794:


         local extra = ""
         local extra = ""
         if ok and dp and dp.flattenField then
        local paramNames = get_template_params(tplPath, content)
         if dp then
             local dataPage = tplPath .. ".json"
             local dataPage = tplPath .. ".json"
             extra = dp.flattenField({ args = { id, dataPage } })
             extra = get_selective_extra(id, dataPage, paramNames)
         end
         end


         add_entries_from_meta(state, parsed, {
         onEntity(parsed, {
             tplPath = tplPath,
             tplPath = tplPath,
             id = id,
             id = id,
Строка 569: Строка 806:
             source = make_source(kind, name, pathName, tplPath),
             source = make_source(kind, name, pathName, tplPath),
             priority = resolve_priority(parsed)
             priority = resolve_priority(parsed)
         }, filter, false)
         })
     end
     end


     for compName in pairs(foundComponents) do
     for compName in pairs(foundComponents) do
         processEntity("component", compName, false)
         if not anyEntityWhitelist or compHasWhitelist then
            processEntity("component", compName, false)
        end
     end
     end
     for protoName in pairs(foundPrototypes) do
     for protoName in pairs(foundPrototypes) do
         processEntity("prototype", protoName, false)
         if not anyEntityWhitelist or protoHasWhitelist then
            processEntity("prototype", protoName, false)
        end
     end
     end


     if type(componentStoreDefs) == "table" then
    local componentStoreDefs = load_module_data("component_store.json")
     if type(componentStoreDefs) == "table" and (not anyEntityWhitelist or compHasWhitelist) then
         local compStore = componentStoreDefs[id]
         local compStore = componentStoreDefs[id]
         if type(compStore) == "table" then
         if type(compStore) == "table" then
             for compName in pairs(compStore) do
             for compName in pairs(compStore) do
                 processEntity("component", compName, true)
                 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
         end
     end
     end


     if type(prototypeStoreDefs) == "table" then
     if type(prototypeStoreDefs) == "table" and (not anyEntityWhitelist or protoHasWhitelist) then
         local protoStore = prototypeStoreDefs[id]
         local protoStore = prototypeStoreDefs[id]
         if type(protoStore) == "table" then
         if type(protoStore) == "table" then
             for protoName in pairs(protoStore) do
             for protoName in pairs(protoStore) do
                 processEntity("prototype", protoName, true)
                 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
         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 = {}
     local out = {}


     if #errors > 0 then
     if #errors > 0 then
         table.insert(out, '{{сущность/infobox|' .. table.concat(errors, "\n") .. '}}')
         out[#out + 1] = '{{сущность/infobox|' .. table.concat(errors, "\n") .. '}}'
     end
     end


     local blocks = renderBlocks(frame, state, filter.hasWhitelist, id)
    renderOptions.noHeaders = filter.hasWhitelist
 
     local blocks = renderBlocks(frame, state, renderOptions, id, showSource)
     for _, b in ipairs(blocks) do
     for _, b in ipairs(blocks) do
         table.insert(out, b)
         out[#out + 1] = b
     end
     end


Строка 615: Строка 990:
     local tplPath = args[1] or ""
     local tplPath = args[1] or ""
     if tplPath == "" then return "" end
     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)
     local content = load_template_content(tplPath)
Строка 635: Строка 1014:
     }, nil, true)
     }, nil, true)


     local whitelist = parseListArg(args.whitelist or "")
     local hasWhitelist = previewFilter.hasWhitelist
     local hasWhitelist = next(whitelist) ~= nil
     renderOptions.noHeaders = hasWhitelist


     local out = {}
     local out = {}
     local blocks = renderBlocks(frame, state, hasWhitelist, "")
     local blocks = renderBlocks(frame, state, renderOptions, "", showSource)
     for _, b in ipairs(blocks) do
     for _, b in ipairs(blocks) do
         table.insert(out, b)
         out[#out + 1] = b
     end
     end