跳转到内容

模組:沙盒/PexEric/1

维基百科,自由的百科全书

这是本页的一个历史版本,由PexEric留言 | 贡献2025年5月31日 (六) 17:57编辑。这可能和当前版本存在着巨大的差异。

local p = {}

-- Lazy load necessary modules
local getArgs -- for frame arguments, will be loaded when first needed

-- =================================================================================
-- Internal helper: _matchAllTemplates (Recursive template finder)
-- Adapted from Module:Template parameter value
-- =================================================================================
local function _matchAllTemplates(str)
    if not str or str == "" then
        return {}
    end
    local matches = {}
    str = tostring(str)
    for template in mw.ustring.gmatch(str, "{%b{}}") do
        table.insert(matches, template)
        local innerContent = mw.ustring.sub(template, 3, -3)
        if innerContent and #innerContent > 0 then
            local subtemplates = _matchAllTemplates(innerContent)
            for _, subtemplate in ipairs(subtemplates) do
                table.insert(matches, subtemplate)
            end
        end
    end
    return matches
end
p._matchAllTemplates = _matchAllTemplates -- Export for potential internal/test use

-- =================================================================================
-- Internal helper: getParameters (Simplified parameter extractor)
-- Adapted from Module:Template parameter value
-- This version is simplified for extracting unnamed parameters.
-- =================================================================================
local function getParametersFromCode(template_code)
    local parameters, parameterOrder = {}, {}
    local params_str = mw.ustring.match(template_code, '{{[^|}]-|(.+)}}') -- Capture content after first pipe

    if params_str then
        local count = 0
        local temp_params_str = params_str
        local placeholders = {}
        local placeholder_idx = 0

        temp_params_str = mw.ustring.gsub(temp_params_str, "{%b{}}", function(subtemplate)
            placeholder_idx = placeholder_idx + 1
            local ph_key = string.format("__SUBTPL_PLACEHOLDER_%d__", placeholder_idx)
            placeholders[ph_key] = subtemplate
            return ph_key
        end)

        temp_params_str = mw.ustring.gsub(temp_params_str, "%[%b[]%]", function(wikilink)
            placeholder_idx = placeholder_idx + 1
            local ph_key = string.format("__WIKILINK_PLACEHOLDER_%d__", placeholder_idx)
            placeholders[ph_key] = wikilink
            return ph_key
        end)
        
        for parameter_part in mw.text.gsplit(temp_params_str, '|') do
            local restored_parameter_part = parameter_part
            for ph_key, original_text in pairs(placeholders) do
                restored_parameter_part = mw.ustring.gsub(restored_parameter_part, mw.ustring.gsub(ph_key, "([%(%)%.%%%+%-%*%?%[%]%^%$])", "%%%1"), original_text)
            end
            
            -- MODIFIED LINE: Added plain=true as the 4th argument
            local parts = mw.text.split(restored_parameter_part, '=', 2, true) 
            local key_part = mw.text.trim(parts[1])
            local value_part

            if #parts == 1 then
                value_part = key_part
                count = count + 1
                key_part = tostring(count)
            else
                value_part = mw.text.trim(parts[2] or "")
            end
            
            for ph_key, original_text in pairs(placeholders) do
                value_part = mw.ustring.gsub(value_part, mw.ustring.gsub(ph_key, "([%(%)%.%%%+%-%*%?%[%]%^%$])", "%%%1"), original_text)
                key_part = mw.ustring.gsub(key_part, mw.ustring.gsub(ph_key, "([%(%)%.%%%+%-%*%?%[%]%^%$])", "%%%1"), original_text)
            end

            table.insert(parameterOrder, key_part)
            parameters[key_part] = value_part
        end
    end
    return parameters, parameterOrder
end

-- =================================================================================
-- WikiProject Banner Shell (WPBS) related definitions
-- =================================================================================
local WPBS_PATTERNS = {
    "wiki%s*project%s*banner%s*shell",
    "w?pj?%s*banner%s*shell",
    "wiki%s*project%s*banners",
    "multiple%s*wikiprojects?",
    "wiki%s*project%s*shell",
    "pjbs",
    "[維维]基[专專][题題][橫横]幅",
    "多?[個个]?[維维]?[基基]?[专專][题題][橫横]幅",
    "[专專][题題][橫横]幅",
    "通用[評评][級级]"
}

local function is_wikiproject_banner_shell(template_name_str)
    if not template_name_str or template_name_str == "" then
        return false
    end
    local normalized_name = mw.ustring.lower(template_name_str)
    normalized_name = normalized_name:gsub("^template:", "")
    normalized_name = normalized_name:gsub("_", " ")
    normalized_name = mw.text.trim(normalized_name)

    for _, pattern in ipairs(WPBS_PATTERNS) do
        if mw.ustring.match(normalized_name, "^" .. pattern .. "$") then
            return true
        end
    end
    return false
end

-- =================================================================================
-- Candidate retrieval logic (similar to PatternedCandidateUtils)
-- =================================================================================
local function get_candidates_internal(page_title_str, pattern_str, black_list_str, black_regex_str)
    if not page_title_str or not pattern_str then
        return {}
    end

    local page_title_obj = mw.title.new(page_title_str)
    if not page_title_obj or not page_title_obj.exists then
        return {}
    end

    local content = page_title_obj:getContent()
    if not content then
        return {}
    end

    local matches = {}
    local black_map = {}
    if black_list_str and black_list_str ~= "" then
        for b in mw.text.gsplit(black_list_str, '|', true) do
            if b ~= "" then black_map[mw.text.trim(b)] = true end
        end
    end

    for m in mw.ustring.gmatch(content, pattern_str) do
        local trimmed_m = mw.text.trim(m)
        if not black_map[trimmed_m] and
           not (black_regex_str and black_regex_str ~= "" and mw.ustring.match(trimmed_m, black_regex_str)) then
            table.insert(matches, trimmed_m)
        end
    end
    return matches
end

-- =================================================================================
-- Core logic: Get WikiProjects for an article
-- =================================================================================
local function get_article_wikiprojects(article_title_str)
    if not article_title_str or article_title_str == "" then
        return {}
    end

    local talk_page_title_str = "Talk:" .. article_title_str
    local base_title_obj = mw.title.new(article_title_str)
    if base_title_obj and base_title_obj.namespace ~= 0 and base_title_obj.namespace ~= 10 and base_title_obj.namespace ~= 118 then
        if base_title_obj.isTalkPage then
             talk_page_title_str = article_title_str
        elseif base_title_obj.canHaveTalkPage then
             talk_page_title_str = base_title_obj.talkPageTitle.fullText
        else
            return {}
        end
    end

    local talk_page_title = mw.title.new(talk_page_title_str)
    if not talk_page_title or not talk_page_title.exists then
        if base_title_obj and base_title_obj.exists and base_title_obj.namespace ~= 0 then
             talk_page_title = base_title_obj
        else
            return {}
        end
    end

    local content = talk_page_title:getContent()
    if not content then
        return {}
    end

    local projects_found = {}
    local projects_map = {} 

    local all_templates_on_page = _matchAllTemplates(content)

    for _, tpl_code in ipairs(all_templates_on_page) do
        local tpl_name_match = mw.ustring.match(tpl_code, "{{%s*([^}|<\n]+)")
        if tpl_name_match then
            local tpl_name_raw = mw.text.trim(tpl_name_match)
            
            if tpl_name_raw ~= "" then
                local template_title_obj_for_check = mw.title.new(tpl_name_raw, "Template")
                local canonical_tpl_name_for_check = tpl_name_raw 

                if template_title_obj_for_check then
                    local success, result = pcall(function() return template_title_obj_for_check:getText() end)
                    if success and type(result) == "string" then
                        canonical_tpl_name_for_check = result
                    elseif not success then
                        mw.log(string.format("Module:CandidateProjectLister Info: getText() failed for '%s' on '%s' (WPBS check). Error: %s. Using raw name.", 
                                             tpl_name_raw, article_title_str, tostring(result)))
                    end
                end
                
                if type(canonical_tpl_name_for_check) == "string" and canonical_tpl_name_for_check ~= "" then
                    if is_wikiproject_banner_shell(canonical_tpl_name_for_check) then
                        local params, _ = getParametersFromCode(tpl_code) -- Ignore paramOrder here
                        local first_unnamed_param_content = params["1"]
                        
                        if first_unnamed_param_content and type(first_unnamed_param_content) == "string" then
                            local projects_in_wpbs_param = _matchAllTemplates(first_unnamed_param_content)
                            for _, proj_in_param_code in ipairs(projects_in_wpbs_param) do
                                local proj_in_param_name_match = mw.ustring.match(proj_in_param_code, "{{%s*([^}|<\n]+)")
                                if proj_in_param_name_match then
                                    local proj_in_param_name_raw = mw.text.trim(proj_in_param_name_match)
                                    if proj_in_param_name_raw ~= "" then
                                        local proj_title_obj = mw.title.new(proj_in_param_name_raw, "Template")
                                        local canonical_proj_name = proj_in_param_name_raw 
                                        if proj_title_obj then
                                            local s, r = pcall(function() return proj_title_obj:getText() end)
                                            if s and type(r) == "string" then canonical_proj_name = r
                                            elseif not s then 
                                                 mw.log(string.format("Module:CandidateProjectLister Info: getText() failed for project '%s' (in WPBS) on '%s'. Error: %s. Using raw name.", 
                                                                      proj_in_param_name_raw, article_title_str, tostring(r)))
                                            end
                                        end
                                        if type(canonical_proj_name) == "string" and canonical_proj_name ~= "" and not projects_map[canonical_proj_name] then
                                            table.insert(projects_found, canonical_proj_name)
                                            projects_map[canonical_proj_name] = true
                                        end
                                    end
                                end
                            end
                        end
                    else
                        if not projects_map[canonical_tpl_name_for_check] then
                            table.insert(projects_found, canonical_tpl_name_for_check)
                            projects_map[canonical_tpl_name_for_check] = true
                        end
                    end
                else
                     mw.log(string.format("Module:CandidateProjectLister Warning: canonical_tpl_name_for_check for template (original: '%s') on page '%s' is not a valid string for project checking. Value: '%s', Type: %s",
                                          tpl_name_raw, article_title_str, tostring(canonical_tpl_name_for_check), type(canonical_tpl_name_for_check)))
                end
            end
        end
    end
    return projects_found
end

-- =================================================================================
-- Formatting and Normalization helpers
-- =================================================================================
local function format_candidate_item(raw_candidate_name, item_link_prefix, item_name_prefix, item_name_suffix)
    local display_name = raw_candidate_name:gsub("_", " ")
    local link_target

    if item_link_prefix and item_link_prefix ~= "" then
        if not mw.ustring.find(item_link_prefix, "#$") and not mw.ustring.find(item_link_prefix, "^%[%[") then
            item_link_prefix = item_link_prefix .. "#"
        end
        link_target = item_link_prefix .. raw_candidate_name
    else
        link_target = raw_candidate_name
    end

    local item_markup = string.format("[[:%s|%s]]", link_target, display_name)
    return (item_name_prefix or "") .. item_markup .. (item_name_suffix or "")
end

local function normalize_project_for_comparison(name)
    if not name or name == "" then return "" end
    local titleObj = mw.title.new(name, "Template")
    if titleObj then
        local success, text = pcall(function() return titleObj:getText() end)
        if success and type(text) == "string" then
             return text 
        end
    end
    local temp_name = mw.text.trim(name)
    temp_name = mw.ustring.gsub(temp_name, "_", " ")
    if #temp_name > 0 then
        local first_char = mw.ustring.sub(temp_name, 1, 1)
        local rest_of_name = mw.ustring.sub(temp_name, 2)
        temp_name = mw.ustring.upper(first_char) .. rest_of_name
    end
    return temp_name
end

-- =================================================================================
-- Public Function: listProjects
-- =================================================================================
function p.listProjects(frame)
    if not getArgs then getArgs = require('Module:Arguments').getArgs end
    local args = getArgs(frame) 

    local review_page_title = args.title
    local pattern = args.pattern
    if not review_page_title or not pattern then
        return '<span class="error">错误:必须提供 "title" 和 "pattern" 参数。</span>'
    end

    local black_list = args.black
    local black_regex = args.blackregex

    local item_link_prefix = args.item_link_prefix or ""
    local item_name_prefix = args.item_name_prefix or ""
    local item_name_suffix = args.item_name_suffix or ""

    local tpl_name_prefix = args.tpl_prefix or "{{tl|"
    local tpl_name_suffix = args.tpl_suffix or "}}"

    local candidates = get_candidates_internal(review_page_title, pattern, black_list, black_regex)

    if #candidates == 0 then
        return args.ifnone or "暂无候选项目"
    end

    local output_table = {'{| class="wikitable sortable plainlinks"', '! 候选项 !! 所属专题'}
    for _, candidate_raw_name in ipairs(candidates) do
        local formatted_candidate_item = format_candidate_item(candidate_raw_name, item_link_prefix, item_name_prefix, item_name_suffix)
        local projects = get_article_wikiprojects(candidate_raw_name)
        local projects_str_parts = {}
        if #projects > 0 then
            for _, proj_name in ipairs(projects) do
                table.insert(projects_str_parts, tpl_name_prefix .. proj_name .. tpl_name_suffix)
            end
        end

        local projects_display_str
        if #projects_str_parts > 0 then
            projects_display_str = table.concat(projects_str_parts, "、")
        else
            projects_display_str = "(暂无专题信息)"
        end

        table.insert(output_table, string.format("|-\n| %s \n| %s", formatted_candidate_item, projects_display_str))
    end
    table.insert(output_table, "|}")
    return table.concat(output_table, "\n")
end

-- =================================================================================
-- Public Function: filterByProjects
-- =================================================================================
function p.filterByProjects(frame)
    if not getArgs then getArgs = require('Module:Arguments').getArgs end
    local args = getArgs(frame) 

    local review_page_title = args.title
    local pattern_str = args.pattern 
    if not review_page_title or not pattern_str then
        return '<span class="error">错误:必须提供 "title" 和 "pattern" 参数。</span>'
    end

    local project_regex_patterns = {}
    local i = 1
    while true do
        local proj_arg = args['project' .. i]
        if proj_arg then
            if proj_arg ~= "" then table.insert(project_regex_patterns, proj_arg) end
            i = i + 1
        else
            break
        end
    end
    
    local use_literal_project_names_from_legacy = false
    local literal_project_names_map = {}
    if #project_regex_patterns == 0 and args.projects then
        mw.log("Module:CandidateProjectLister Warning: Using deprecated 'projects' parameter for literal matching. Consider 'projectN' for regex.")
        use_literal_project_names_from_legacy = true
        for proj_name in mw.text.gsplit(args.projects, "[,;]", true) do
             local trimmed_proj = mw.text.trim(proj_name)
             if trimmed_proj ~= "" then
                 literal_project_names_map[normalize_project_for_comparison(trimmed_proj)] = true
             end
        end
        if next(literal_project_names_map) == nil then
             return '<span class="error">错误:"projects" 参数(旧版)为空或仅包含分隔符,且无 "projectN" 参数。</span>'
        end
    elseif #project_regex_patterns == 0 then 
        return '<span class="error">错误:必须提供 "project1", "project2", ... (作为正则表达式) 或旧版的 "projects" 参数。</span>'
    end


    local black_list = args.black
    local black_regex = args.blackregex

    local item_link_prefix = args.item_link_prefix or ""
    local item_name_prefix = args.item_name_prefix or ""
    local item_name_suffix = args.item_name_suffix or ""

    local candidates = get_candidates_internal(review_page_title, pattern_str, black_list, black_regex)
    if #candidates == 0 then
        return args.ifnone or "暂无候选项目"
    end

    local filtered_candidate_items = {}
    for _, candidate_raw_name in ipairs(candidates) do
        local article_projects = get_article_wikiprojects(candidate_raw_name)
        local match_found = false
        
        for _, proj_name_on_article_raw in ipairs(article_projects) do
            local normalized_proj_name = normalize_project_for_comparison(proj_name_on_article_raw)
            
            if normalized_proj_name ~= "" then
                if use_literal_project_names_from_legacy then
                    if literal_project_names_map[normalized_proj_name] then
                        match_found = true
                        break 
                    end
                else 
                    for _, regex_pattern in ipairs(project_regex_patterns) do
                        local success_match, match_result = pcall(mw.ustring.match, normalized_proj_name, regex_pattern)
                        if success_match and match_result then 
                            match_found = true
                            break 
                        elseif not success_match then
                            mw.log(string.format("Module:CandidateProjectLister Regex Error: Pattern '%s' failed on '%s'. Error: %s", 
                                                 regex_pattern, normalized_proj_name, tostring(match_result)))
                        end
                    end
                    if match_found then break end 
                end
            end 
        end 

        if match_found then
            local formatted_candidate_item = format_candidate_item(candidate_raw_name, item_link_prefix, item_name_prefix, item_name_suffix)
            table.insert(filtered_candidate_items, formatted_candidate_item)
        end
    end 

    if #filtered_candidate_items == 0 then
        return args.ifnone_filter or "指定专题下暂无候选项目"
    end

    return "# " .. table.concat(filtered_candidate_items, "\n# ")
end

return p