模組:Category tree
This module is used for generating category boilerplate templates. It is not meant to be used directly. Rather, each template will have its own submodule, which handles the specifics of that template.
This documentation only covers the generics of the category tree system. If you are looking for documentation on a specific template, or on how to add or modify category information, see the documentation of that template.
參數
The category tree module is invoked as:
{{#invoke:category tree|show|template=name of the template|...other parameters...}}
Every template that uses this module should have a submodule of this module with the name given in the |template= parameter. (For example, {{poscatboiler}} uses Module:category tree/poscatboiler.) This submodule should export a function named new which takes a single parameter: a table named info that contains the various parameters that were passed to the template initially. This function should return a new Category object representing those parameters, or nil if the combination of parameters was not valid (i.e. no such category exists).
Most templates accept and pass this common set of parameters. The parameters passed to the module by a template are defined by that template individually, so not every template will necessarily use all of these. {{family cat}} for example only passes the |code= parameter to the module.
|code=- The code that specifies what 'owns' the category's contents. This is usually a language code such as
en, but it can also be a script code likeLatnor the code of a language family, depending on how the specific template treats it. |label=- A name for the thing that is being categorised. The submodule determines how the label is interpreted, so it depends on the template being used. Many templates use it to look up data in a table, while others may interpret it as a language code of some kind.
|sc=- The script code of the items to be categorised. This is usually empty, but many categories such as those used by Mandarin Chinese can split into subcategories based on script.
一般工作
The module is based on the principle of two main kinds of category:
Basic categories are those for which the |code= parameter is not empty. These therefore belong to a specific language (or similar) and are the "regular" categories. Examples are: Category:English nouns, Category:French templates, Category:nl:Linguistics, Category:English terms derived from Japanese, Category:Latin script characters.
Umbrella categories do not have a code, but contain all basic categories of their label, one for each code. These are the "by language" type categories. Examples are: Category:Nouns by language, Category:Templates by language, Category:Linguistics, Category:Terms derived from Japanese, Category:Characters by script.
Some templates also distinguish a third type of category, the fundamental category. This category is used as the parent category for umbrella categories.
Category objects
Template:Documentation outdated
Category objects are returned by each submodule's new function. They represent a single category in the tree. A category object has a variety of methods which may be called on it to ask for information about the category.
getBreadcrumbName
getBreadcrumbName()
Returns the name that is used for the category in the "breadcrumbs" at the top of the category page.
getDataModule
getDataModule()
Returns the name of the module which contains the data for this category. This is used to create an "edit" link on the category, which allows users to find and edit the information more easily.
canBeEmpty
canBeEmpty()
Returns true either if the category contains pages but might be empty or if the category only contains categories, otherwise returns false.
getCategoryName
getCategoryName()
Returns the name of the category that this category object represents.
getDescription
getDescription()
Returns the description text that is shown at the top of the category page. If the category has no description, this returns nil.
getParents
getParents()
Returns a table of the parent categories of this category. Each element in the table is a table itself, with two elements:
.name- One of two possibilities: An category object representing the parent category, or a string that directly specifies the name of the parent category.
.sort- The sorting key that should be used when categorizing the current category in the parent.
If the category has no parents, this returns nil.
If there are two or more parent categories, the first will be used to generate the breadcrumbs that are displayed at the top of the category page. For example, Category:English language is in numerous categories (All languages, West Germanic languages, Latin script languages, Braille script languages, and so on), but the first category, All languages, is the one displayed in the breadcrumbs: Fundamental » All languages » English language.
getChildren
getChildren()
Returns a table of the child categories of this category. Each element in the table is a category object representing the child category. If the category has no children, this returns nil.
getUmbrella
getUmbrella()
Returns a category object for the current category's corresponding umbrella category. If the current category is already an umbrella category, this returns nil. It also returns nil if the category has no umbrella category.
getAppendix
getAppendix()
Returns an appendix link (such as Appendix:French verbs) if the page exists, else returns nil.
-- Prevent substitution.
if mw.isSubsting() then
return require("Module:unsubst")
end
local export = {}
local category_tree_submodule_prefix = "Module:category tree/"
local category_tree_styles_css = "Module:category tree/styles.css"
local m_str_utils = require("Module:string utilities")
local m_template_parser = require("Module:template parser")
local m_utilities = require("Module:utilities")
local m_zh = require("Module:Zh") -- 引入中文處理模組
local ceil = math.ceil
local class_else_type = m_template_parser.class_else_type
local concat = table.concat
local deep_copy = require("Module:table").deepCopy
local full_url = mw.uri.fullUrl
local insert = table.insert
local is_callable = require("Module:fun").is_callable
local log10 = math.log10 or require("Module:math").log10
local new_title = mw.title.new
local pages_in_category = mw.site.stats.pagesInCategory
local parse = m_template_parser.parse
local remove_comments = require("Module:string/removeComments")
local sort = table.sort
local split = m_str_utils.split
local string_compare = require("Module:string/compare")
local trim = m_str_utils.trim
local uupper = m_str_utils.upper
local yesno = require("Module:yesno")
local current_frame = mw.getCurrentFrame()
local current_title = mw.title.getCurrentTitle()
local namespace = current_title.namespace
local poscatboiler_subsystem = "poscatboiler"
local extra_args_error = "此分類不允許傳遞額外參數給 {{((}}auto cat{{))}}。"
-- 簡轉繁輔助函數
local function st(text)
return m_zh.st(text)
end
-- Generates a sortkey for a numeral `n`.
function export.numeral_sortkey(n, max_n)
max_n = max_n or require("Module:list of languages").count()
return ("#%%0%dd"):format(ceil(log10(max_n + 1))):format(n)
end
function export.split_lang_label(title_text)
local getByCanonicalName = require("Module:languages").getByCanonicalName
local words = split(title_text, " ", true)
if #words > 1 then
for i = #words - 1, 1, -1 do
local lang = getByCanonicalName(concat(words, " ", 1, i))
if lang then
return lang, concat(words, " ", i + 1)
end
end
end
-- L10N: 波羅的
local preLabel, posLabel = mw.ustring.match(title_text, "^(.+[^羅]的)(.+)$")
if preLabel then
-- 如果匹配到「XX的」,嘗試去掉「的」字看是否為語言
local langName = preLabel:sub(1, -4) -- 去掉最後的 "的" (UTF-8 3 bytes) ? 或是直接匹配
-- 舊版正則捕獲了「的」,所以這裡我們拿 preLabel 去測
-- 讓我們簡化邏輯:直接逐步縮減字串長度來匹配語言
end
-- 3. 漸進式匹配:從頭開始匹配語言名稱
for i = mw.ustring.len(title_text), 1, -1 do
local potential_lang_name = mw.ustring.sub(title_text, 1, i)
local lang = getByCanonicalName(potential_lang_name)
if not lang then
local trad_name = st(potential_lang_name)
if trad_name ~= potential_lang_name then
lang = getByCanonicalName(trad_name)
end
end
if lang then
local label = mw.ustring.sub(title_text, i + 1)
if mw.ustring.sub(label, 1, 1) == "的" then
label = mw.ustring.sub(label, 2)
end
if label ~= "" then
return lang, label
end
end
end
return nil, title_text
end
local function show_error(text)
return require("Module:message box").maintenance(
"red",
"[[File:Codex icon Alert red.svg|40px|alt=alert]]",
"本分類自動生成的內容有錯誤。", -- This category is not defined in Wiktionary's category tree.
text
)
end
-- Show the text that goes at the very top right of the page.
local function show_topright(current)
return current.getTopright and current:getTopright() or nil
end
local function link_box(content)
return ("<div class=\"noprint plainlinks\" style=\"float: right; clear: both; margin: 0 0 .5em 1em; background: var(--wikt-palette-paleblue, #f9f9f9); border: 1px var(--border-color-base, #aaaaaa) solid; margin-top: -1px; padding: 5px; font-weight: bold;\">%s</div>"):format(content)
end
local function show_editlink(current)
return link_box(("[%s 編輯分類-{zh-hant:資料;zh-hans:数据}-]"):format(tostring(full_url(current:getDataModule(), "action=edit"))))
end
function show_related_changes()
local title = current_title.fullText
return link_box(("[%s <span title=\"對%s內頁面最近做出的編輯和其他更改\">最近更改</span>]"):format(
tostring(full_url("Special:RecentChangesLinked", {
target = title,
showlinkedto = 0,
})),
title
))
end
local function show_pagelist(current)
local namespace = "namespace="
local info = current:getInfo()
local lang_code = info.code
if info.label == "citations" or info.label == "citations of undefined terms" then
namespace = namespace .. "Citations"
elseif lang_code then
local lang = require("Module:languages").getByCode(lang_code, true)
if lang then
if (lang_code:find("%-pro$") and lang_code ~= "gmq-pro") or lang:hasType("reconstructed") then
namespace = namespace .. "Reconstruction"
elseif lang:hasType("appendix-constructed") then
namespace = namespace .. "Appendix"
end
end
elseif info.label:match("templates") then
namespace = namespace .. "Template"
elseif info.label:match("modules") then
namespace = namespace .. "Module"
elseif info.label:match("^Wiktionary") or info.label:match("^Pages") then
namespace = ""
end
-- return ([=[
-- {| id="newest-and-oldest-pages" class="wikitable mw-collapsible" style="float: right; clear: both; margin: 0 0 .5em 1em;"
-- ! 最新和最舊頁面
-- |-
-- | id="recent-additions" style="font-size:0.9em;" | '''最新頁面(按[[mw:Manual:Categorylinks table#cl_timestamp|加入分類時間]]排序):'''
-- %s
-- |-
-- | id="oldest-pages" style="font-size:0.9em;" | '''最舊頁面(按最後編輯時間排序):'''
-- %s
-- |}]=]):format(
-- current_frame:extensionTag(
-- "DynamicPageList",
-- ([=[
-- category=%s
-- %s
-- count=10
-- mode=ordered
-- ordermethod=categoryadd
-- order=descending]=]
-- ):format(current_title.text, namespace)
-- ),
-- current_frame:extensionTag(
-- "DynamicPageList",
-- ([=[
-- category=%s
-- %s
-- count=10
-- mode=ordered
-- ordermethod=lastedit
-- order=ascending]=]
-- ):format(current_title.text, namespace)
-- )
-- )
return ""
end
-- Show navigational "breadcrumbs" at the top of the page.
local function show_breadcrumbs(current)
local steps = {}
-- Start at the current label and move our way up the "chain" from child to parent, until we can't go further.
while current do
local category, display_name, nocap
if type(current) == "string" then
category = current
display_name = current:gsub("^Category:", ""):gsub("^分類:", "")
else
if not current.getCategoryName then
error("Internal error: Bad format in breadcrumb chain structure, probably a misformatted value for `parents`: " ..
mw.dumpObject(current))
end
category = "Category:" .. current:getCategoryName()
display_name, nocap = current:getBreadcrumbName()
end
if not nocap then
-- 中文通常不需要 ucfirst,但也保留原邏輯
display_name = mw.getContentLanguage():ucfirst(display_name)
end
insert(steps, 1, ("[[:%s|%s]]"):format(category, display_name))
-- Move up the "chain" by one level.
if type(current) == "string" then
current = nil
else
current = current:getParents()
end
if current then
current = current[1].name
end
end
local templateStyles = require("Module:TemplateStyles")(category_tree_styles_css)
local ol = mw.html.create("ol")
for i, step in ipairs(steps) do
local li = mw.html.create("li")
if i ~= 1 then
local span = mw.html.create("span")
:attr("aria-hidden", "true")
:addClass("ts-categoryBreadcrumbs-separator")
:wikitext(" » ")
li:node(span)
end
li:wikitext(step)
ol:node(li)
end
return templateStyles .. tostring(mw.html.create("div")
:attr("role", "navigation")
:attr("aria-label", "Breadcrumb")
:addClass("ts-categoryBreadcrumbs")
:node(ol))
end
local function show_also(current)
local also = current._info.also
if also and #also > 0 then
return ('<div style="margin-top:-1em;margin-bottom:1.5em">%s</div>'):format(require("Module:also").main(also))
end
return nil
end
-- Show a short description text for the category.
local function show_description(current)
return current.getDescription and current:getDescription() or nil
end
local function show_appendix(current)
local appendix = current.getAppendix and current:getAppendix()
return appendix and ("更多信息請見[[%s]]。"):format(appendix) or nil
end
local function sort_children(child1, child2)
return string_compare(uupper(child1.sort), uupper(child2.sort))
end
-- Show a list of child categories.
local function show_children(current)
local children = current.getChildren and current:getChildren() or nil
if not children then
return nil
end
sort(children, sort_children)
local children_list = {}
for _, child in ipairs(children) do
local child_name, child_pagetitle = child.name
if type(child_name) == "string" then
child_pagetitle = child_name
else
child_pagetitle = "Category:" .. child_name:getCategoryName()
end
-- 檢查繁簡標題是否存在
local child_page_exists = new_title(child_pagetitle).exists
if not child_page_exists then
-- 嘗試繁體化檢查
local st_title = st(child_pagetitle)
if st_title ~= child_pagetitle and new_title(st_title).exists then
child_page_exists = true
child_pagetitle = st_title
end
end
if child_page_exists then
insert(children_list, ("* [[:%s]]:%s"):format(
child_pagetitle,
child.description or
type(child_name) == "string" and child_name:gsub("^Category:", ""):gsub("^分類:", "") .. "." or
child_name:getDescription("child")
))
end
end
return concat(children_list, "\n")
end
-- Show a table of contents with links to each letter in the language's script.
local function show_TOC(current)
local titleText = current_title.text
local inCategoryPages = pages_in_category(titleText, "pages")
local inCategorySubcats = pages_in_category(titleText, "subcats")
local TOC_type
-- Compute type of table of contents required.
if inCategoryPages > 2500 or inCategorySubcats > 2500 then
TOC_type = "full"
elseif inCategoryPages > 200 or inCategorySubcats > 200 then
TOC_type = "normal"
else
-- No (usual) need for a TOC if all pages or subcategories can fit on one page;
-- but allow this to be overridden by a custom TOC handler.
TOC_type = "none"
end
if current.getTOC then
local TOC_text = current:getTOC(TOC_type)
if TOC_text ~= true then
return TOC_text or nil
end
end
if TOC_type ~= "none" then
local templatename = current:getTOCTemplateName()
local TOC_template
if TOC_type == "full" then
-- This category is very large, see if there is a "full" version of the TOC.
local TOC_template_full = new_title(templatename .. "/full")
if TOC_template_full.exists then
TOC_template = TOC_template_full
end
end
if not TOC_template then
local TOC_template_normal = new_title(templatename)
if TOC_template_normal.exists then
TOC_template = TOC_template_normal
end
end
if TOC_template then
return current_frame:expandTemplate{title = TOC_template.text, args = {}}
end
end
return nil
end
-- Show the "catfix" that adds language attributes and script classes to the page.
local function show_catfix(current)
local lang, sc = current:getCatfixInfo()
return lang and m_utilities.catfix(lang, sc) or nil
end
-- Show the parent categories that the current category should be placed in.
local function show_categories(current, categories)
local parents = current.getParents and current:getParents() or nil
if not parents then
return nil
end
for _, parent in ipairs(parents) do
local parent_name = parent.name
local sortkey = type(parent.sort) == "table" and parent.sort:makeSortKey() or parent.sort
if type(parent_name) == "string" then
insert(categories, ("[[%s|%s]]"):format(parent_name, sortkey))
else
insert(categories, ("[[Category:%s|%s]]"):format(parent_name:getCategoryName(), sortkey))
end
end
-- Also put the category in its corresponding "umbrella" or "by language" category.
local umbrella = current:getUmbrella()
if umbrella then
-- FIXME: use a language-neutral sorting function like the Unicode Collation Algorithm.
local sortkey = current._lang and current._lang:getCanonicalName() or current:getCategoryName()
sortkey = require("Module:languages").getByCode("en", true):makeSortKey(sortkey)
if type(umbrella) == "string" then
insert(categories, ("[[%s|%s]]"):format(umbrella, sortkey))
else
insert(categories, ("[[Category:%s|%s]]"):format(umbrella:getCategoryName(), sortkey))
end
end
-- Check for various unwanted parser functions, which should be integrated into the category tree data instead.
-- Note: HTML comments shouldn't be removed from `content` until after this step, as they can affect the result.
local content = current_title:getContent()
if not content then
-- This happens when using [[Special:ExpandTemplates]] to call {{auto cat}} on a nonexistent category page,
-- which is needed by Benwing's create_wanted_categories.py script.
return
end
local defaultsort, displaytitle, page_has_param
for node in parse(content):iterate_nodes() do
local node_class = class_else_type(node)
if node_class == "template" then
local name = node:get_name()
if name == "DEFAULTSORT:" and not defaultsort then
insert(categories, "[[Category:頁面存在DEFAULTSORT衝突]]")
defaultsort = true
elseif name == "DISPLAYTITLE:" and not displaytitle then
insert(categories,"[[Category:頁面存在DISPLAYTITLE衝突]]")
displaytitle = true
end
elseif node_class == "parameter" and not page_has_param then
insert(categories,"[[Category:頁面含有原始三括號模板參數]]")
page_has_param = true
end
end
-- Check for raw category markup, which should also be integrated into the category tree data.
content = remove_comments(content, "BOTH")
local head = content:find("[[", 1, true)
while head do
local close = content:find("]]", head + 2, true)
if not close then
break
end
-- Make sure there are no intervening "[[" between head and close.
local open = content:find("[[", head + 2, true)
while open and open < close do
head = open
open = content:find("[[", head + 2, true)
end
local cat = content:sub(head + 2, close - 1)
local colon = cat:match("^[ _\128-\244]*[Cc][Aa][Tt][EeGgOoRrYy _\128-\244]*():") or cat:match("^[ _\128-\244]*[分][類类] _\128-\244]*():")
if colon then
local pipe = cat:find("|", colon + 1, true)
if pipe ~= #cat then
local title = new_title(pipe and cat:sub(1, pipe - 1) or cat)
if title and title.namespace == 14 then
insert(categories,"[[Category:分類頁面含有原始分類標記]]")
break
end
end
end
head = open
end
end
local function generate_output(current)
if current then
for _, functionName in pairs{
"getBreadcrumbName",
"getDataModule",
"canBeEmpty",
"getDescription",
"getParents",
"getChildren",
"getUmbrella",
"getAppendix",
"getTOCTemplateName",
} do
if not is_callable(current[functionName]) then
require("Module:debug").track{"category tree/missing function", "category tree/missing function/" .. functionName}
end
end
end
local boxes, display, categories = {}, {}, {}
-- Categories should never show files as a gallery.
insert(categories, "__NOGALLERY__")
if current_frame:getParent():getTitle() == "Template:auto cat" then
insert(categories, "[[Category:調用auto cat模板的分類]]")
end
-- Check if the category is empty
local totalPages = pages_in_category(current_title.text, "all")
local hugeCategory = totalPages > 1000000 -- 1 million
-- Categorize huge categories, as they cause DynamicPageList to time out and make the category inaccessible.
if hugeCategory then
insert(categories, "[[Category:龐大分類]]")
end
-- Are the parameters valid?
if not current then
insert(categories, "[[Category:未在分類樹中定義的分類]]")
insert(categories, totalPages == 0 and "[[Category:空分類]]" or nil)
insert(display, show_error(
"請再次檢查分類名稱是否有錯別字。<br>" ..
"[[Special:Search/Category: " .. current_title.text:gsub("^.+:", ""):gsub(" ", "~2 ") .. '~2|搜索現有分類]] 以檢查此分類是否應以不同名稱建立(例如用“Fruits”而不是“Fruit”)。<br>' ..
"若要在維基詞典分類樹中請求定義新分類,請在 [[Wiktionary:啤酒館]] 發起討論。"))
-- Exit here, as all code beyond here relies on current not being nil
return concat(categories, "") .. concat(display, "\n\n"), true
end
-- Does the category have the correct name?
local currentName = current:getCategoryName()
local correctName = current_title.text == currentName
if not correctName then
-- 如果繁簡轉換後一致,則不算錯誤(中維特性)
if st(current_title.text) == currentName then
-- 隱形重定向或相容,不做報錯
else
insert(categories, "[[Category:名稱不正確的分類]]")
insert(display, show_error(("根據分類樹中的資料,此分類名稱應為 '''[[:Category:%s]]'''。"):format(currentName)))
end
end
-- Add cleanup category for empty categories.
local canBeEmpty = current:canBeEmpty()
if canBeEmpty and correctName then
insert(categories, " __EXPECTUNUSEDCATEGORY__")
elseif totalPages == 0 then
insert(categories, "[[Category:空分類]]")
end
if current:isHidden() then
insert(categories, "__HIDDENCAT__")
end
-- Put all the float-right stuff into a <div> that does not clear, so that float-left stuff like the breadcrumbs and
-- description can go opposite the float-right stuff without vertical space.
insert(boxes, "<div style=\"float: right;\">")
insert(boxes, show_topright(current))
insert(boxes, show_editlink(current))
insert(boxes, show_related_changes())
-- Show pagelist, unless it's a huge category (since they can't use DynamicPageList - see above).
if not hugeCategory then
insert(boxes, show_pagelist(current))
end
insert(boxes, "</div>")
-- Generate the displayed information
insert(display, show_breadcrumbs(current))
insert(display, show_also(current))
insert(display, show_description(current))
insert(display, show_appendix(current))
insert(display, show_children(current))
insert(display, show_TOC(current))
insert(display, show_catfix(current))
insert(display, '<br class="clear-both-in-vector-2022-only">')
show_categories(current, categories)
return concat(boxes, "\n") .. "\n" .. concat(display, "\n\n") .. concat(categories, "")
end
--[==[
List of handler functions that try to match the page name. A handler should return the name of a submodule to
[[Module:category tree]] and an info table which is passed as an argument to the submodule. If a handler does not
recognize the page name, it should return nil. Note that the order of handlers matters!
]==]
local handlers = {}
-- Thesaurus per-language category
insert(handlers, function(title)
local code, label = title:match("^Thesaurus:(%l[%a-]*%a):(.+)")
if code then
return poscatboiler_subsystem, {label = title, raw = true}
end
end)
-- Topic per-language category
insert(handlers, function(title)
local code, label = title:match("^(%l[%a-]*%a):(.+)")
if code then
return poscatboiler_subsystem, {label = title, raw = true}
end
end)
-- Lect category e.g. for [[:Category:New Zealand English]] or [[:Category:Issime Walser]]
insert(handlers, function(title, args)
local lect = args.lect or args.dialect
if lect ~= "" and yesno(lect, true) then -- Same as boolean in [[Module:parameters]].
return poscatboiler_subsystem, {label = title, args = args, raw = true}
end
end)
-- poscatboiler per-language label, e.g. [[Category:英語不規則名詞]]
insert(handlers, function(title, args)
local lang, label = export.split_lang_label(title)
if not lang then
local prefix, rest = mw.ustring.match(title, "^(.+[^羅]的)(.+)$")
if prefix and rest then
local getByCanonicalName = require("Module:languages").getByCanonicalName
for i = mw.ustring.len(rest), 1, -1 do
local potential_lang_name = mw.ustring.sub(rest, 1, i)
local potential_lang = getByCanonicalName(potential_lang_name)
if not potential_lang then
local trad_potential = st(potential_lang_name)
if trad_potential ~= potential_lang_name then
potential_lang = getByCanonicalName(trad_potential)
end
end
if potential_lang then
lang = potential_lang
local suffix = mw.ustring.sub(rest, i + 1)
label = prefix .. suffix
break
end
end
end
end
if not lang then
return
end
local script, baseLabel = mw.ustring.match(label, "^以(.-)書寫的(.+)$")
if script and baseLabel ~= "詞" then
local scriptObj = require("Module:scripts").getByCanonicalName(script)
if scriptObj then
return poscatboiler_subsystem, {label = baseLabel, code = lang:getCode(), sc = scriptObj:getCode(), args = args}
end
end
return poscatboiler_subsystem, {label = label, code = lang:getCode(), args = args}
end)
-- poscatboiler label umbrella category
insert(handlers, function(title, args)
-- English: (.+) by language$
-- Chinese: (.+)按語言分類 ? 或是 按語言分類的(.+) ?
-- 參考 Fundamental categories: "Fundamental categories by language" -> "按語言分類的基本分類"
local label = title:match("(.+)按語言分類$") or title:match("^按語言分類的(.+)")
if label then
-- The poscatboiler code will appropriately lowercase if needed.
return poscatboiler_subsystem, {label = label, args = args}
end
end)
-- poscatboiler raw handlers
insert(handlers, function(title, args)
return poscatboiler_subsystem, {label = title, args = args, raw = true}
end)
-- poscatboiler umbrella handlers without 'by language'
insert(handlers, function(title, args)
return poscatboiler_subsystem, {label = title, args = args}
end)
function export.show(frame)
local args, other_args = require("Module:parameters").process(frame:getParent().args, {
["also"] = {type = "title", sublist = "comma without whitespace", namespace = 14}
}, true)
if args.also then
for k, arg in next, args.also do
args.also[k] = arg.prefixedText
end
end
for k, arg in next, other_args do
other_args[k] = trim(arg)
end
if namespace == 10 then -- Template
return "(本模板應使用於 [[Help:名字空間#Category|Category:]] 名字空間。)"
elseif namespace ~= 14 then -- Category
error("本模板/模組只能用於 [[mw:Help:Namespaces#Category|Category:]] 名字空間。")
end
local first_fail_args_handled, first_fail_cattext
local process_title_text = current_title.text
-- Go through each handler in turn. If a handler doesn't recognize the format of the category, it will return nil,
-- and we will consider the next handler. Otherwise, it returns a template name and arguments to call it with, but
-- even then, that template might return an error, and we need to consider the next handler.
for _, handler in ipairs(handlers) do
-- Use a new title object and args table for each handler, to keep them isolated.
local submodule, info = handler(process_title_text, deep_copy(other_args))
if submodule then
info.also = deep_copy(args.also)
require("Module:debug").track("auto cat/" .. submodule)
-- `failed` is true if no match was found.
submodule = require(category_tree_submodule_prefix .. submodule)
local cattext, failed = generate_output(submodule.main(info))
if failed then
if not first_fail_cattext then
first_fail_cattext = cattext
first_fail_args_handled = info.args and true or false
end
elseif not info.args and next(other_args) then
error(extra_args_error)
else
return cattext
end
end
end
-- If there were no matches, throw an error if any arguments were given, or otherwise return the cattext
-- from the first fail encountered. The final handlers call the boilers unconditionally, so there should
-- always be something to return.
if not first_fail_args_handled and next(other_args) then
error(extra_args_error)
end
return first_fail_cattext
end
-- TODO: new test entrypoint.
return export