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