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

Отмена версии 213664, сделанной Pok (обсуждение)
Метки: отмена отменено
Отмена версии 315901, сделанной Pok (обсуждение)
Метка: отмена
 
(не показано 8 промежуточных версий этого же участника)
Строка 3: Строка 3:
local JsonPaths = require('Module:JsonPaths')
local JsonPaths = require('Module:JsonPaths')


local moduleDataCache = {}
local dpOk, dpModule = pcall(require, "Module:GetField")
local templateContentCache = {}
local dp = dpOk and dpModule or nil
local templateMetaCache = {}
 
local templateArgCache = {}
local flattenExtraCache = {}
local switchModeRegistry = {}
local switchModeRegistry = {}
local switchModeOrder = {}
local switchModeOrder = {}
Строка 14: Строка 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 = JsonPaths.get(page)
     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


local function load_template_content(path)
local function load_template_content(path)
    if templateContentCache[path] ~= nil then
        return templateContentCache[path] or nil
    end
     local title = mw.title.new("Template:" .. path)
     local title = mw.title.new("Template:" .. path)
     if not title then
     if not title then
        templateContentCache[path] = false
         return nil
         return nil
     end
     end
     local ok, content = pcall(function() return title:getContent() end)
     local ok, content = pcall(title.getContent, title)
     if not ok then
     if not ok then
        templateContentCache[path] = false
         return nil
         return nil
     end
     end
    templateContentCache[path] = content or false
     return content
     return content
end
end
Строка 96: Строка 90:


local function get_template_params(tplPath, content)
local function get_template_params(tplPath, content)
     local cached = templateArgCache[tplPath]
     return collect_template_params(content)
    if cached ~= nil then
        return cached
    end
 
    local params = collect_template_params(content)
    templateArgCache[tplPath] = params
    return params
end
end


Строка 136: Строка 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]
Строка 184: Строка 171:
                     line = line .. '<div class="ts-Сущность-field">' .. makeSourceLink(src) .. '</div>'
                     line = line .. '<div class="ts-Сущность-field">' .. makeSourceLink(src) .. '</div>'
                 end
                 end
                 table.insert(parts, '<div class="ts-Сущность">' .. line .. '</div>')
                 parts[#parts + 1] = '<div class="ts-Сущность">' .. line .. '</div>'
             end
             end
         end
         end
Строка 252: Строка 239:


     return table.concat(out, "\n")
     return table.concat(out, "\n")
end
local function normalizeFilterKey(s)
    s = trim(s or "")
    s = s:gsub("%s*_%s*", "_")
    return s
end
end


Строка 258: Строка 251:
         return false
         return false
     end
     end
    callKey = normalizeFilterKey(callKey)
    compositeKey = normalizeFilterKey(compositeKey)
     return list[callKey] or list[compositeKey] or false
     return list[callKey] or list[compositeKey] or false
end
end
Строка 337: Строка 332:


                 local isWhitelisted = cardFilter and matches_card_list(cardFilter.whitelist, callKey, compositeKey) or
                 local isWhitelisted = cardFilter and matches_card_list(cardFilter.whitelist, callKey, compositeKey) or
                false
                    false
                 local isBlacklisted = cardFilter and matches_card_list(cardFilter.blacklist, callKey, compositeKey) or
                 local isBlacklisted = cardFilter and matches_card_list(cardFilter.blacklist, callKey, compositeKey) or
                false
                    false
                 if isWhitelisted and content ~= "" then
                 if isWhitelisted and content ~= "" then
                     rawContentParts[#rawContentParts + 1] = content
                     rawContentParts[#rawContentParts + 1] = content
Строка 351: Строка 346:
                     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
Строка 369: Строка 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
Строка 377: Строка 372:
                     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
Строка 383: Строка 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 = {}
     local out = {}
Строка 485: Строка 487:


local function getTemplateMeta(frame, tplPath)
local function getTemplateMeta(frame, tplPath)
    if templateMetaCache[tplPath] ~= nil then
        return templateMetaCache[tplPath] or ""
    end
     local expanded = frame:expandTemplate {
     local expanded = frame:expandTemplate {
         title = tplPath,
         title = tplPath,
Строка 496: Строка 494:
     local ok, data = pcall(mw.text.jsonDecode, expanded)
     local ok, data = pcall(mw.text.jsonDecode, expanded)
     if not ok or type(data) ~= "table" then
     if not ok or type(data) ~= "table" then
        templateMetaCache[tplPath] = false
         return ""
         return ""
     end
     end
Строка 508: Строка 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
Строка 516: Строка 513:
     end
     end


    templateMetaCache[tplPath] = data
     return data
     return data
end
end
Строка 524: Строка 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("^([^_]+)_(.+)$")
Строка 576: Строка 572:
     end
     end
     return not (filter.blacklist[sw] and filter.blacklist[sw][key])
     return not (filter.blacklist[sw] and filter.blacklist[sw][key])
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


Строка 658: Строка 642:
end
end


local function get_selective_extra(dp, id, dataPage, paramNames)
local function get_selective_extra(id, dataPage, paramNames)
     if not dp or type(dp.flattenFieldSelective) ~= "function" then
     if not dp or type(dp.flattenFieldSelectiveDirect) ~= "function" then
         return ""
         return ""
     end
     end
Строка 666: Строка 650:
     end
     end


     local okJson, keysJson = pcall(mw.text.jsonEncode, paramNames)
     return dp.flattenFieldSelectiveDirect(id, dataPage, paramNames) or ""
     if not okJson or not keysJson or keysJson == "" then
end
         return ""
 
local function add_card_tag_value(tags, seen, value)
    value = trim(value or "")
     if value == "" or seen[value] then
         return
     end
     end
    seen[value] = true
    tags[#tags + 1] = value
end


     local cacheKey = dataPage .. "\31" .. id .. "\31" .. keysJson
local function merge_card_tag_text(...)
    if flattenExtraCache[cacheKey] ~= nil then
     local tags = {}
         return flattenExtraCache[cacheKey]
    local seen = {}
 
    for i = 1, select("#", ...) do
        each_csv_value(select(i, ...), function(value)
            add_card_tag_value(tags, seen, value)
         end)
     end
     end


     local extra = dp.flattenFieldSelective({ args = { id, dataPage, keysJson } }) or ""
     return table.concat(tags, ", ")
    flattenExtraCache[cacheKey] = extra
    return extra
end
end


Строка 711: Строка 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, renderOptions, entityId, showSource)
     local strings = {}
     local outLocal = {}
     for sw, keys in pairs(keyFilter.whitelist) do
    local noHeaders = renderOptions and renderOptions.noHeaders
         if type(keys) == "table" then
    local cardFilter = renderOptions and renderOptions.cardFilter
             for key in pairs(keys) do
     for _, sw in ipairs(switchModeOrder) do
                 strings[#strings + 1] = key
         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 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, showSource)
                    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


     local showSource = trim(args.showSource or "") == ""
     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
    local renderOptions = build_render_options(filter)
end


     local componentWhitelist = args.componentWhitelist or args.componentwhitelist or ""
local function each_entity_data(frame, id, onEntity, onMissing, keyFilter)
     local componentBlacklist = args.componentBlacklist or args.componentblacklist or ""
     local componentWhitelist = frame.args.componentWhitelist or frame.args.componentwhitelist or ""
     local prototypeWhitelist = args.prototypeWhitelist or args.prototypewhitelist or ""
     local componentBlacklist = frame.args.componentBlacklist or frame.args.componentblacklist or ""
     local prototypeBlacklist = args.prototypeBlacklist or args.prototypeblacklist 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")
     if not componentDefs or not prototypeStoreDefs then return "" end
     if not componentDefs or not prototypeStoreDefs then
        return false
    end


     local foundComponents, foundPrototypes = collect_entity_sets(id, componentDefs, prototypeStoreDefs,
     local foundComponents, foundPrototypes = collect_entity_sets(id, componentDefs, prototypeStoreDefs,
         componentWhitelist, componentBlacklist, prototypeWhitelist, prototypeBlacklist)
         componentWhitelist, componentBlacklist, prototypeWhitelist, prototypeBlacklist)
    local state = new_switch_state()


     local ok, dp = pcall(require, "Module:GetField")
     local compWhitelistSet = parse_csv_set(componentWhitelist)
     local errors = {}
     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 function processEntity(kind, name, isStore)
Строка 783: Строка 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
Строка 797: Строка 795:
         local extra = ""
         local extra = ""
         local paramNames = get_template_params(tplPath, content)
         local paramNames = get_template_params(tplPath, content)
         if ok and dp then
         if dp then
             local dataPage = tplPath .. ".json"
             local dataPage = tplPath .. ".json"
             extra = get_selective_extra(dp, id, dataPage, paramNames)
             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,
Строка 808: Строка 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


     local componentStoreDefs = load_module_data("component_store.json")
     local componentStoreDefs = load_module_data("component_store.json")
     if type(componentStoreDefs) == "table" then
     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
 
    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
         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


Строка 847: Строка 980:
     local blocks = renderBlocks(frame, state, renderOptions, id, showSource)
     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


Строка 881: Строка 1014:
     }, nil, true)
     }, nil, true)


    local whitelist = previewFilter.whitelist
     local hasWhitelist = previewFilter.hasWhitelist
     local hasWhitelist = previewFilter.hasWhitelist
     renderOptions.noHeaders = hasWhitelist
     renderOptions.noHeaders = hasWhitelist
Строка 888: Строка 1020:
     local blocks = renderBlocks(frame, state, renderOptions, "", showSource)
     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