模組:沙盒/PexEric/1
外观
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