跳转到内容

模組:沙盒/PexEric/1

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

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

local p = {}

-- Lazy load necessary modules
local Arguments -- Will be loaded by require
local TPV -- Module:Template parameter value

-- Helper function to initialize modules if not already done
local function init_modules()
    if not Arguments then
        -- Adjust the path if your Arguments module is elsewhere
        Arguments = require('Module:Arguments')
    end
    if not TPV then
        -- Adjust the path if your Template parameter value module is elsewhere
        -- For simplicity, assuming it's at "Module:Template parameter value"
        local success_tpv, tpv_module = pcall(require, "Module:Template parameter value")
        if success_tpv then
            TPV = tpv_module
        else
            -- Fallback or error handling if TPV module cannot be loaded
            mw.log("Error loading Module:Template parameter value: " .. tostring(tpv_module))
            TPV = nil -- Ensure it's nil so checks below will fail gracefully
        end
    end
end

-- Copied from previous version, for extracting templates from a string
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

-- List of Lua string patterns for WikiProject banner shell and its redirects.
-- These patterns are designed to be matched against a lowercase, space-normalized template name.
-- IMPORTANT: These are now used as the 'templates' argument for TPV.getTemplate
local WPBS_TEMPLATE_NAMES_FOR_TPV = {
    "WikiProject banner shell",
    "WPbannershell", "WPBS", "Wpbs",
    "Pjbannershell", "Pjbs",
    "WikiProject banners",
    "Multiple Wikiprojects", "Multiple WikiProjects",
    "WikiProject shell",
    "维基专题横幅", "維基專題橫幅",
    "多个维基专题横幅", "多個維基專題橫幅", "多個維基专题横幅", "多个維基專題橫幅",
    "维基专题", "維基專題", -- These were ambiguous before, but as TPV template targets, they are fine.
    "多个维基专题", "多個維基專題",
    "专题横幅", "專題橫幅",
    "通用评级", "通用評級"
    -- Ensure this list contains the *canonical names* or common redirects TPV should look for.
    -- TPV itself handles case-insensitivity for the first letter.
    -- We can also pass treat_as_regex = true to TPV if needed, but for now, direct names are better.
}


-- Internal function to get candidate article titles
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

-- Internal function to get WikiProject banners for a given article
-- MODIFIED to use Module:Template parameter value
local function get_article_wikiprojects(article_title_str)
    init_modules() -- Ensure TPV is loaded
    if not article_title_str or article_title_str == "" or not TPV then
        if not TPV then mw.log("get_article_wikiprojects: TPV module not available for " .. article_title_str) end
        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

    -- TPV options
    local tpv_options = {
        template_index = 1, -- We want the first WPBS found
        -- treat_as_regex = false, -- WPBS_TEMPLATE_NAMES_FOR_TPV are direct names
        ignore_blank = true, -- For parameter value
    }

    local success, wpbs_param1_value
    local tpv_page_target = talk_page_title_str

    -- Check if talk page exists. If not, and base is a project page, try base.
    local talk_title_obj = mw.title.new(talk_page_title_str)
    if not talk_title_obj or not talk_title_obj.exists then
        if base_title_obj and base_title_obj.exists and base_title_obj.namespace ~= 0 then
            tpv_page_target = base_title_obj.fullText
        else
            -- mw.log("get_article_wikiprojects: No talk page or suitable base page for " .. article_title_str)
            return {} -- No suitable page to check
        end
    end
    
    -- Try to get parameter "1" from any of the WPBS templates
    success, wpbs_param1_value = TPV.getParameter(
        tpv_page_target,
        WPBS_TEMPLATE_NAMES_FOR_TPV, -- list of WPBS template names
        "1", -- The first unnamed parameter
        tpv_options
    )

    local projects_found = {}
    local projects_map = {}

    if success and wpbs_param1_value and wpbs_param1_value ~= "" then
        -- wpbs_param1_value now contains the wikitext of all project banners inside the shell
        -- We need to extract template names from this wikitext
        local templates_in_param1 = _matchAllTemplates(wpbs_param1_value)
        for _, tpl_code in ipairs(templates_in_param1) do
            local tpl_name_match = mw.ustring.match(tpl_code, "{{%s*([^}|<\n#]+)") -- Avoid matching parameters starting with #
            if tpl_name_match then
                local raw_tpl_name = mw.text.trim(tpl_name_match)
                if raw_tpl_name ~= "" then
                    -- Normalize the template name (resolve redirects, canonical case)
                    local template_title_obj = mw.title.new(raw_tpl_name, "Template")
                    local canonical_tpl_name = raw_tpl_name -- fallback
                    if template_title_obj then
                        local p_success, p_result = pcall(function() return template_title_obj:getText() end)
                        if p_success and type(p_result) == "string" then
                            canonical_tpl_name = p_result
                        end
                    end
                    
                    if not projects_map[canonical_tpl_name] then
                        table.insert(projects_found, canonical_tpl_name)
                        projects_map[canonical_tpl_name] = true
                    end
                end
            end
        end
    else
        -- If WPBS not found or param 1 is empty, try to find project templates directly on the page
        -- This handles cases where projects are not inside a WPBS
        local talk_content_obj = mw.title.new(tpv_page_target)
        if talk_content_obj and talk_content_obj.exists then
            local talk_content = talk_content_obj:getContent()
            if talk_content then
                local all_page_templates = _matchAllTemplates(talk_content)
                for _, tpl_code in ipairs(all_page_templates) do
                    local tpl_name_match = mw.ustring.match(tpl_code, "{{%s*([^}|<\n#]+)")
                    if tpl_name_match then
                        local raw_tpl_name = mw.text.trim(tpl_name_match)
                        if raw_tpl_name ~= "" then
                            local template_title_obj = mw.title.new(raw_tpl_name, "Template")
                            local canonical_tpl_name = raw_tpl_name
                            if template_title_obj then
                                local p_success, p_result = pcall(function() return template_title_obj:getText() end)
                                if p_success and type(p_result) == "string" then
                                    canonical_tpl_name = p_result
                                end
                            end

                            -- Rough check if it's a banner shell itself to avoid re-adding it
                            local is_shell = false
                            for _, shell_name_pattern_base in ipairs(WPBS_TEMPLATE_NAMES_FOR_TPV) do
                                -- This simple check might need refinement if shell names are complex patterns
                                if mw.ustring.lower(canonical_tpl_name):find(mw.ustring.lower(shell_name_pattern_base):gsub("%%s%*", " *")) then
                                    is_shell = true
                                    break
                                end
                            end

                            if not is_shell and not projects_map[canonical_tpl_name] then
                                table.insert(projects_found, canonical_tpl_name)
                                projects_map[canonical_tpl_name] = true
                            end
                        end
                    end
                end
            end
        end
    end
    return projects_found
end

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

function p.listProjects(frame)
    init_modules()
    if not Arguments then return '<span class="error">错误:Module:Arguments 未能加载。</span>' end
    local args = Arguments.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 = #projects_str_parts > 0 and table.concat(projects_str_parts, "、") or "(暂无专题信息)"
        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

-- Normalization for project name (used as text to match regex against)
local function normalize_project_for_matching(name)
    if not name or name == "" then return "" end
    -- For regex matching, usually the canonical name is best
    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 -- Returns "Foo bar" for "Template:Foo bar"
        end
    end
    -- Fallback: simple normalization
    local temp_name = mw.text.trim(name)
    temp_name = mw.ustring.gsub(temp_name, "_", " ")
    if #temp_name > 0 then 
        temp_name = mw.ustring.upper(mw.ustring.sub(temp_name, 1, 1)) .. mw.ustring.sub(temp_name, 2)
    end
    return temp_name
end


-- Main function 2: Filter candidates by one or more project regexes
function p.filterByProjects(frame)
    init_modules()
    if not Arguments then return '<span class="error">错误:Module:Arguments 未能加载。</span>' end
    local args = Arguments.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 project_regexes = {}
    -- Collect project regexes, e.g., project1_regex, project2_regex, ... or project_regex_pattern for project1, project2
    -- Option 1: Numbered regex parameters like project1_regex, project2_regex
    for i = 1, 50 do -- Arbitrary limit for numbered parameters
        local regex_param_name = "project" .. i .. "_regex"
        if args[regex_param_name] then
            table.insert(project_regexes, args[regex_param_name])
        else
            -- If using numbered params, break on first missing one for simplicity
            -- Or, if projectN parameters are also used for names, we need a different logic
            break 
        end
    end

    -- Option 2: A single project_regex_pattern to apply to project names from project1, project2, ...
    -- For this request, the requirement is "each are independent regexes". So Option 1 is primary.
    -- If you meant `project1="regexA"`, `project2="regexB"`, then:
    if #project_regexes == 0 then -- If no projectN_regex params were found, try projectN as regex
        for i = 1, 50 do
            local project_arg_name = "project" .. i
            if args[project_arg_name] then
                table.insert(project_regexes, args[project_arg_name])
            else
                 -- For "projectN" args, if one is missing, assume no more.
                if i == 1 and not args.projects then -- if project1 is missing, and no legacy 'projects'
                   -- no regexes defined by projectN
                elseif i > 1 then -- if project2+ is missing, stop looking
                    break
                end
            end
        end
    end
    
    -- Fallback to legacy 'projects' parameter if no projectN or projectN_regex found, treat as plain strings (not regex)
    -- Or, if 'projects' should also be regex, it needs to be handled explicitly.
    -- The request was "multiple project parameters ... each are independent regular expressions"
    -- So, if 'projects' is present and project_regexes is empty, we should clarify if 'projects' should be regex.
    -- For now, assuming 'projects' (if used and no projectN regexes found) is NOT regex.
    -- To make 'projects' a list of regexes, split it and add to project_regexes.

    if #project_regexes == 0 and args.projects then
        -- If user only provided `|projects=regex1,regex2`, split and use them.
         mw.log("Using 'projects' parameter for regex list as no projectN or projectN_regex found.")
         for regex_str in mw.text.gsplit(args.projects, "[,;]", true) do
             if regex_str ~= "" then
                table.insert(project_regexes, mw.text.trim(regex_str))
             end
         end
    end

    if #project_regexes == 0 then
        return '<span class="error">错误:必须通过 "project1_regex", "project1", 或 "projects" (作为regex列表) 等参数提供至少一个专题匹配正则表达式。</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, 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_names = get_article_wikiprojects(candidate_raw_name)
        local match_found_for_article = false
        for _, proj_name_on_article in ipairs(article_projects_names) do
            local normalized_proj_name = normalize_project_for_matching(proj_name_on_article)
            if normalized_proj_name ~= "" then
                for _, regex_pattern in ipairs(project_regexes) do
                    local success, err = pcall(function() return mw.ustring.match(normalized_proj_name, regex_pattern) end)
                    if success and err then -- err here is the match result if successful
                        match_found_for_article = true
                        break -- Found a matching regex for this project name
                    elseif not success then
                        mw.log(string.format("Regex error for pattern '%s' on project '%s' (article '%s'): %s",
                                             regex_pattern, normalized_proj_name, candidate_raw_name, tostring(err)))
                    end
                end
            end
            if match_found_for_article then break end -- Found a match for one of the article's projects
        end

        if match_found_for_article 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