跳转到内容

模組:沙盒/PexEric/1

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

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

local p = {}

-- Lazy load necessary modules
local getArgs -- for frame arguments
local yesno -- for boolean frame arguments

-- Copied from Module:Template parameter value (matchAllTemplates)
-- string.gmatch will check the largest block it can without re-scanning whats inside, but we need whats inside
local function _matchAllTemplates(str)
    if not str or str == "" then
        return {}
    end
    local matches = {}
    -- Ensure str is a string, as mw.title:getContent() can return nil
    str = tostring(str)
    for template in mw.ustring.gmatch(str, "{%b{}}") do
        table.insert(matches, template)
        local innerContent = mw.ustring.sub(template, 3, -3)
        -- Check if innerContent is not empty before recursive call
        if innerContent and #innerContent > 0 then
            local subtemplates = _matchAllTemplates(innerContent) -- Recursive call
            for _, subtemplate in ipairs(subtemplates) do
                table.insert(matches, subtemplate) -- Adds subtemplates too
            end
        end
    end
    return matches
end
p._matchAllTemplates = _matchAllTemplates -- Export for testing or other internal uses if needed

-- 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.
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",
    "[維维]基[专專][题題][橫横]幅",
    "多?[個个]?[維维]?[基基]?[专專][题題][橫横]幅",
    "[維维]基[专專][题題]",
    "多?[個个]?[維维]?[基基]?[专專][题題]",
    "[专專][题題][橫横]幅",
    "通用[評评][級级]"
}

-- Internal helper function to check if a template name is a WikiProject banner shell
local function is_wikiproject_banner_shell(template_name_str)
    if not template_name_str or template_name_str == "" then
        return false
    end
    -- Normalize the template name: lowercase, replace underscores with spaces, remove "Template:" prefix
    local normalized_name = mw.ustring.lower(template_name_str)
    normalized_name = normalized_name:gsub("^template:", "") -- Remove "Template:" prefix
    normalized_name = normalized_name:gsub("_", " ")         -- Underscores to spaces
    normalized_name = mw.text.trim(normalized_name)

    for _, pattern in ipairs(WPBS_PATTERNS) do
        -- Anchor pattern to match the whole normalized name
        if mw.ustring.match(normalized_name, "^" .. pattern .. "$") then
            return true
        end
    end
    return false
end

-- Internal function to get candidate article titles from a review page
-- Similar logic to Module:PatternedCandidateUtils's getCandidates
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 {} -- Page doesn't exist
    end

    local content = page_title_obj:getContent()
    if not content then
        return {} -- No content
    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
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
    -- For non-main namespace pages like Wikipedia:Foo, talk page is Wikipedia talk:Foo
    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 -- Not Main, Template, Draft
         -- Try to get subject talk page for other namespaces
        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 -- Cannot have talk page (e.g. Special pages)
            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
        -- Try article's own page if it's a project page itself (e.g. a WikiProject page being evaluated)
        if base_title_obj and base_title_obj.exists and base_title_obj.namespace ~= 0 then
             talk_page_title = base_title_obj
        else
            return {} -- No talk page and not a suitable self-page
        end
    end

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

    local projects_found = {}
    local projects_map = {} -- To ensure uniqueness of project names

    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]+)") -- Added < and \n to avoid issues with nowiki, etc.
        if tpl_name_match then
            local tpl_name = mw.text.trim(tpl_name_match)
            -- Normalize template name by getting its base text (resolves redirects, canonical case)
            local template_title_obj = mw.title.new(tpl_name, "Template")
            local canonical_tpl_name = tpl_name -- fallback
            if template_title_obj then
                canonical_tpl_name = template_title_obj:getText() -- Returns just "Foobar" from "Template:Foobar"
            end

            if not is_wikiproject_banner_shell(canonical_tpl_name) then
                -- If it's not a banner shell, consider it a project template.
                -- Further filtering (e.g. by "WikiProject" prefix) could be added here if needed,
                -- but for now, we assume non-shell templates found this way are projects.
                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
    return projects_found
end

-- Helper to format candidate item link
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
        -- Ensure item_link_prefix ends with # if it's meant to be a section link base
        if not mw.ustring.find(item_link_prefix, "#$") and not mw.ustring.find(item_link_prefix, "^%[%[") then
             -- Heuristic: if it doesn't look like a full link already and doesn't end with #, assume it's a page name for section linking
            item_link_prefix = item_link_prefix .. "#"
        end
        link_target = item_link_prefix .. raw_candidate_name
    else
        link_target = raw_candidate_name -- Link directly to the article/page
    end
    
    local item_markup = string.format("[[:%s|%s]]", link_target, display_name)
    
    return (item_name_prefix or "") .. item_markup .. (item_name_suffix or "")
end

-- Main function 1: List all candidates and their projects in a table
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 "" -- e.g., "Wikipedia:New_articles_candidates#"
    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

-- Normalization for project name comparison (used in filterByProjects)
local function normalize_project_for_comparison(name)
    if not name or name == "" then return "" end
    local titleObj = mw.title.new(name, "Template") -- Assuming project names are template names
    if titleObj then
        -- Use getText to get the canonical name (e.g., "Foo bar" for "Template:Foo bar", resolves redirects)
        return titleObj:getText() 
    end
    -- Fallback: simple normalization if mw.title.new fails (e.g., invalid title characters)
    local temp_name = mw.text.trim(name)
    temp_name = mw.ustring.gsub(temp_name, "_", " ")
    if #temp_name > 0 then -- Capitalize first letter
        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 projects
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 = args.pattern
    local target_projects_str = args.projects
    if not review_page_title or not pattern or not target_projects_str then
        return '<span class="error">错误:必须提供 "title", "pattern", 和 "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 target_projects_map = {}
    -- Split by comma or semicolon, then normalize
    for proj_name in mw.text.gsplit(target_projects_str, "[,;]", true) do
        if proj_name ~= "" then
            target_projects_map[normalize_project_for_comparison(mw.text.trim(proj_name))] = true
        end
    end
    
    if next(target_projects_map) == nil then -- Check if map is empty
         return '<span class="error">错误:"projects" 参数不能为空或仅包含分隔符。</span>'
    end

    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 = get_article_wikiprojects(candidate_raw_name)
        local match_found = false
        for _, proj_name_on_article in ipairs(article_projects) do
            if target_projects_map[normalize_project_for_comparison(proj_name_on_article)] then
                match_found = true
                break
            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