模組:Category tree/topic cat
- 下列說明文檔位於Module:Category tree/topic cat/doc。[編輯]
- 相關連結:根頁面 • 根頁面的子頁面 • 本頁面的子頁面 • 链入 • 嵌入包含 • 測試用例 • 沙盒 (差異)
此模塊由{{topic cat}}
調用。有關如何使用模板或修改其類別的信息,請參見其文檔。有關完整的層次結構,請參見Module:category tree/topic cat/hierarchy。
local export = {}
local label_data = require("Module:category tree/topic cat/data")
local topic_cat_utilities_module = "Module:category tree/topic cat/utilities"
local labels_utilities_module = "Module:labels/utilities"
local string_utilities_module = "Module:string utilities"
local rsplit = mw.text.split
-- Category object
local Category = {}
Category.__index = Category
-- L10N
local en_to_zh_category_types = {
["related-to"] = "相關",
["set"] = "集合",
["type"] = "類型",
["name"] = "名稱",
["grouping"] = "分組"
}
local type_data = {
["相關"] = {
desc = "terms related to",
additional = "'''注意''':這是一個「相關於」的分類。它應該包含與{{{topic}}}" ..
"直接相關的術語。請不要包括那些僅與{{{topic}}}有間接關聯的術語。" ..
"請注意,通常類型或實例的術語會放在另一個單獨的分類中。",
},
["集合"] = {
desc = "terms for types or instances of",
additional = "'''注意''':這是一個集合分類。它應該包含{{{topic}}}的術語," ..
"而不僅僅是與{{{topic}}}相關的術語它可能包含更一般的術語(例如{{{topic}}}的類型)" ..
"或更具體的術語(例如特定{{{topic}}}的名稱),儘管可能有"..
"專門針對這些類型術語的相關分類。",
},
["名稱"] = {
desc = "names of specific",
additional = "'''注意''':這是一個名稱分類。它應該包含特定{{{topic}}}的名稱," ..
"而不僅僅是與{{{topic}}}相關的術語,也不應包含{{{topic}}}類型的通用術語。",
},
["類型"] = {
desc = "terms for types of",
additional = "'''注意''':這是一個類型分類。它應該包含{{{topic}}}類型的術語," ..
"而不僅僅是與{{{topic}}}相關的術語,也不應包含特定{{{topic}}}的名稱。",
},
["分組"] = {
desc = "categories concerning more specific variants of",
additional = "'''注意''':注意:這是一個分組分類。它不應直接包含任何術語,而" ..
"應僅包含子分類。如果這個分類中有任何術語,請將它們移動到相應的子分類中。",
},
toplevel = {
desc = "UNUSED", -- all categories of this type hardcode their description
additional = "'''注意''':這是頂級列表分類。它不應直接包含任何詞語," ..
"而只能包含{{{topic}}}分類。",
},
}
local function invalid_type(types)
local valid_types = {}
for typ, _ in pairs(type_data) do
table.insert(valid_types, ("'%s'"):format(typ))
end
error(("Invalid type '%s', should be one or more of %s, comma-separated")
:format(types, require("Module:table").serialCommaJoin(valid_types, {dontTag = true})))
end
local function split_types(types)
types = types or "相關"
local splitvals = rsplit(types, "%s*,%s*")
for i, typ in ipairs(splitvals) do
-- FIXME: Temporary
if typ == "topic" then
typ = "相關"
end
-- L10N
typ = en_to_zh_category_types[typ] or typ
if not type_data[typ] then
invalid_type(types)
end
splitvals[i] = typ
end
return splitvals
end
local function gsub_escaping_replacement(str, from, to)
return (str:gsub(from, require(string_utilities_module).replacement_escape(to)))
end
function Category.new_main(frame)
local self = setmetatable({}, Category)
local params = {
[1] = {},
[2] = {required = true},
["sc"] = {},
}
args = require("Module:parameters").process(frame:getParent().args, params, nil, "category tree/topic cat", "new_main")
self._info = {code = args[1], label = args[2]}
self:initCommon()
if not self._data then
return nil
end
return self
end
function Category.new(info)
for key, val in pairs(info) do
if not (key == "code" or key == "label") then
error("The parameter “" .. key .. "” was not recognized.")
end
end
local self = setmetatable({}, Category)
self._info = info
if not self._info.label then
error("No label was specified.")
end
self:initCommon()
if not self._data then
error("The label “" .. self._info.label .. "” does not exist.")
end
return self
end
export.new = Category.new
export.new_main = Category.new_main
function Category:initCommon()
if self._info.code then
self._lang = require("Module:languages").getByCode(self._info.code, true)
end
-- Convert label to lowercase if possible
local lowercase_label = mw.getContentLanguage():lcfirst(self._info.label)
-- Check if the label exists
local labels = label_data["LABELS"]
if labels[lowercase_label] then
self._info.label = lowercase_label
end
self._data = labels[self._info.label]
-- Go through handlers
if not self._data then
for _, handler in ipairs(label_data["HANDLERS"]) do
self._data = handler.handler(self._info.label)
if self._data then
self._data.module = handler.module
break
end
end
end
end
function Category:getInfo()
return self._info
end
function ucfirst(txt)
local italics, raw_txt = txt:match("^('*)(.-)$")
return italics .. mw.getContentLanguage():ucfirst(raw_txt)
end
function Category:uclabel()
return ucfirst(self._info.label)
end
function Category:format_displaytitle(include_lang_prefix, upcase)
local displaytitle = self._data.displaytitle
if not displaytitle then
return nil
end
if type(displaytitle) == "function" then
displaytitle = displaytitle(self._info.label, lang)
end
if upcase then
displaytitle = ucfirst(displaytitle)
end
if include_lang_prefix and self._lang then
displaytitle = ("%s:%s"):format(self._lang:getCode(), displaytitle)
end
return displaytitle
end
function Category:getBreadcrumbName()
local ret
if self._lang then
ret = self._data.breadcrumb or self:format_displaytitle(false, "upcase")
else
ret = self._data.umbrella and self._data.umbrella.breadcrumb or
self._data.breadcrumb or self:format_displaytitle(false, "upcase")
end
if not ret then
ret = self._info.label
end
if type(ret) == "string" or type(ret) == "number" then
ret = {name = ret}
end
local name = self:substitute_template_specs(ret.name)
local nocap = ret.nocap
return name, nocap
end
function Category:getDataModule()
return self._data.module
end
function Category:canBeEmpty()
if self._lang then
return false
else
return true
end
end
function Category:isHidden()
return false
end
function Category:getCategoryName()
if self._lang then
return self._lang:getCanonicalName() .. " " .. self:uclabel() -- L10N
else
return self:uclabel()
end
end
function Category:process_default(desc)
local stripped_desc = desc
local no_singularize, wikify, add_the
while true do
local new_stripped_desc = stripped_desc:match("^(.+) no singularize$")
if new_stripped_desc then
no_singularize = true
end
if not new_stripped_desc then
new_stripped_desc = stripped_desc:match("^(.+) wikify$")
if new_stripped_desc then
wikify = true
end
end
if not new_stripped_desc then
new_stripped_desc = stripped_desc:match("^(.+) with the$")
if new_stripped_desc then
add_the = true
end
end
if new_stripped_desc then
stripped_desc = new_stripped_desc
else
break
end
end
if stripped_desc == "default" then
return true, no_singularize, wikify, add_the
else
return false
end
end
function Category:replace_special_descriptions(desc)
if not desc then
return desc
end
local function format_desc(desc)
local desc_parts = {}
local types = split_types(self._data.type)
for _, typ in ipairs(types) do
table.insert(desc_parts, type_data[typ].desc .. " " .. desc)
end
return "{{{langname}}} " .. require("Module:table").serialCommaJoin(desc_parts) .. "."
end
if desc:find("^=") then
desc = desc:gsub("^=", "")
return format_desc(desc)
end
local is_default, no_singularize, wikify, add_the = self:process_default(desc)
if is_default then
local linked_label = require(topic_cat_utilities_module).link_label(self._info.label, no_singularize, wikify)
if add_the then
linked_label = "the " .. linked_label
end
return format_desc(linked_label)
else
return desc
end
end
function Category:substitute_template_specs(desc)
if not desc then
return desc
end
if type(desc) == "number" then
desc = tostring(desc)
end
-- FIXME, when does this occur? It doesn't occur in the corresponding place in [[Module:category tree/poscatboiler]].
if type(desc) ~= "string" then
return desc
end
desc = gsub_escaping_replacement(desc, "{{PAGENAME}}", mw.title.getCurrentTitle().text)
if desc:find("{{{umbrella_msg}}}") then
local eninfo = mw.clone(self._info)
eninfo.code = "zh"
local zh = Category.new(eninfo)
desc = desc:gsub("{{{umbrella_msg}}}", "此分類不包含詞典條目,只包含其他分類。子分類有兩種類型:\n\n" ..
"* 像「阿法爾語 " .. self:uclabel() .. "」這樣命名的子分類" ..
"(帶有語言名稱和空格)是特定語言的詞語分類。" ..
"你可能對漢語詞語分類[[:Category:" .. zh:getCategoryName() .. "]]感興趣。\n" ..
"* 此分類中沒有語言名稱和空格的子分類是進一步的分類,它們與這個分類類似,但專注於更細的主題。"
)
end
if self._lang then
desc = gsub_escaping_replacement(desc, "{{{langname}}}", self._lang:getCanonicalName())
desc = gsub_escaping_replacement(desc, "{{{langcode}}}", self._lang:getCode())
desc = gsub_escaping_replacement(desc, "{{{langcat}}}", self._lang:getCategoryName())
desc = gsub_escaping_replacement(desc, "{{{langlink}}}", self._lang:makeCategoryLink())
end
if desc:find("{{{topic}}}") then
local function get_displaytitle_or_label()
return self:format_displaytitle(false) or self._info.label
end
local function process_default_add_the(topic)
local is_default, no_singularize, wikify, add_the = self:process_default(topic)
if is_default then
topic = get_displaytitle_or_label()
if add_the then
topic = "the " .. topic
end
end
return topic, is_default
end
-- Compute the value for {{{topic}}}. If the user specified `topic`, use it. (If we're an umbrella category,
-- allow a separate value for `umbrella.topic`, falling back to `topic`.) Otherwise, see if the description
-- was specified as 'default' or a variant; if so, parse it to determine whether to add "the" to the label.
-- Otherwise, just use the label directly.
local topic = not self._lang and self._data.umbrella and self._data.umbrella.topic or self._data.topic
if topic then
topic, _ = process_default_add_the(topic)
else
local desc
if not self._lang then
desc = self._data.umbrella and self._data.umbrella.description or self._data.umbrella_description
end
desc = desc or self._data.description
local defaulted_desc, is_default = process_default_add_the(desc)
if is_default then
topic = defaulted_desc
else
topic = get_displaytitle_or_label()
end
end
desc = gsub_escaping_replacement(desc, "{{{topic}}}", topic)
end
if desc:find("{") then
desc = mw.getCurrentFrame():preprocess(desc)
end
return desc
end
function Category:substitute_template_specs_in_args(args)
if not args then
return args
end
local pinfo = {}
for k, v in pairs(args) do
k = self:substitute_template_specs(k)
v = self:substitute_template_specs(v)
pinfo[k] = v
end
return pinfo
end
function Category:getTopright()
local def_topright_parts = {}
local function process_box(val, pattern)
if not val then
return
end
local defval = self:uclabel()
if type(val) ~= "table" then
val = {val}
end
for _, v in ipairs(val) do
if v == true then
table.insert(def_topright_parts, pattern:format(defval))
else
table.insert(def_topright_parts, pattern:format(v))
end
end
end
process_box(self._data.wp, "{{wikipedia|%s}}")
process_box(self._data.wpcat, "{{wikipedia|category=%s}}")
process_box(self._data.commonscat, "{{commonscat|%s}}")
local def_topright
if #def_topright_parts > 0 then
def_topright = table.concat(def_topright_parts, "\n")
end
if self._lang then
return self:substitute_template_specs(self._data.topright or def_topright)
else
return self._data.umbrella and self:substitute_template_specs(self._data.umbrella.topright) or
self:substitute_template_specs(def_topright)
end
end
local function remove_lang_params(desc)
desc = desc:gsub("{{{langname}}}", "")
desc = desc:gsub("{{{langcode}}}:", "")
desc = desc:gsub("{{{langcode}}}", "")
desc = desc:gsub("{{{langcat}}}", "")
return desc
end
function Category:getDescription(isChild)
-- Allows different text in the list of a category's children
local isChild = isChild == "child"
local function display_title()
local displaytitle = self:format_displaytitle("include lang prefix", "upcase")
if displaytitle then
displaytitle = self:substitute_template_specs(displaytitle)
mw.getCurrentFrame():callParserFunction("DISPLAYTITLE", "Category:" .. displaytitle)
end
end
if not isChild and self._data.displaytitle then
display_title()
end
local function get_labels_categorizing()
local m_labels_utilities = require(labels_utilities_module)
return m_labels_utilities.format_labels_categorizing(
m_labels_utilities.find_labels_for_category(self._info.label, "topic", self._lang), nil, self._lang)
end
local function get_additional_msg()
local types = split_types(self._data.type)
if #types > 1 then
local parts = {}
local function ins(txt)
table.insert(parts, txt)
end
ins("'''注意''':這是一個混合分類。它可能包含以下任何類型的分類術語:")
for i, typ in ipairs(types) do
ins(("* %s {{{topic}}}%s"):format(type_data[typ].desc, i == #types and "。" or ";"))
end
ins("'''警告''':強烈不推薦此類類別,應將其拆分成按類型分開的分類。")
return table.concat(parts, "\n")
elseif self._info.label == "主題" then
return "'''注意''':這是{{{langname}}}的最高層主題分類。" ..
"它不應直接包含任何術語,而應僅包含按類型組織的主題分類列表。"
else
return type_data[types[1]].additional
end
end
if self._lang then
local desc = self._data.description
desc = self:replace_special_descriptions(desc)
if not isChild and desc then
if self._data.preceding then
desc = self._data.preceding .. "\n\n" .. desc
end
if self._data.additional then
desc = desc .. "\n\n" .. self._data.additional
end
desc = desc .. "\n\n" .. get_additional_msg()
local labels_msg = get_labels_categorizing()
if labels_msg then
desc = desc .. "\n\n" .. labels_msg
end
end
return self:substitute_template_specs(desc)
else
if self._info.label == "主題" then
return "此類別適用於有關 Wiki 的內容,不適用於元資料。"
end
local desc = self._data.umbrella and self._data.umbrella.description or self._data.umbrella_description
local has_umbrella_desc = not not desc
if not desc then
desc = self._data.description
if desc then
desc = self:replace_special_descriptions(desc)
desc = remove_lang_params(desc)
desc = desc:gsub("%.$", "")
desc = "此分類涉及的主題是:" .. desc .. "。"
end
end
if not desc then
desc = "各種語言中有關" .. self._info.label .. "的分類。"
end
if not isChild then
local preceding = self._data.umbrella and self._data.umbrella.preceding or
not has_umbrella_desc and self._data.preceding
local additional = self._data.umbrella and self._data.umbrella.additional or
not has_umbrella_desc and self._data.additional
if preceding then
desc = remove_lang_params(preceding) .. "\n\n" .. desc
end
if additional then
desc = desc .. "\n\n" .. remove_lang_params(additional)
end
desc = desc .. "\n\n{{{umbrella_msg}}}"
desc = desc .. "\n\n" .. get_additional_msg()
local labels_msg = get_labels_categorizing()
if labels_msg then
desc = desc .. "\n\n" .. labels_msg
end
end
desc = self:substitute_template_specs(desc)
return desc
end
end
function Category:getParents()
local parents = self._data["parents"]
local label = self._info.label
if not self._lang and label == "主題" then
return {{ name = "Category:分類", sort = "topics" }}
end
if not parents or #parents == 0 then
return nil
end
local ret = {}
for key, parent in ipairs(parents) do
parent = mw.clone(parent)
if type(parent) ~= "table" then
parent = {name = parent}
end
if not parent.sort then
-- When defaulting sort key to label, strip 'The ' (e.g. in 'The Matrix', 'The Hunger Games')
-- and 'A ' (e.g. in 'A Song of Ice and Fire', 'A Christmas Carol') from label.
local stripped_sort = label:match("^[Tt]he (.*)$")
if stripped_sort then
parent.sort = stripped_sort
end
if not stripped_sort then
stripped_sort = label:match("^[Aa] (.*)$")
if stripped_sort then
parent.sort = stripped_sort
end
end
if not stripped_sort then
parent.sort = label
end
end
if self._lang then
parent.sort = self:substitute_template_specs(parent.sort)
elseif parent.sort:find("{{{langname}}}") or parent.sort:find("{{{langcat}}}") or parent.module then
return nil
end
if not self._lang then
parent.sort = " " .. parent.sort
end
if parent.name and parent.name:find("^Category:") then
if self._lang then
parent.name = self:substitute_template_specs(parent.name)
elseif parent.name:find("{{{langname}}}") or parent.name:find("{{{langcat}}}") or parent.module then
return nil
end
else
local pinfo = mw.clone(self._info)
pinfo.label = parent.name
if parent.module then
-- A reference to a category using another category tree module.
if not parent.args then
error("Missing .args in parent table with module=\"" .. parent.module .. "\" for '" ..
label .. "' topic entry in module '" .. (self._data.module or "unknown") .. "'")
end
parent.name = require("Module:category tree/" .. parent.module).new(self:substitute_template_specs_in_args(parent.args))
else
parent.name = Category.new(pinfo)
end
end
table.insert(ret, parent)
end
if self._data.type ~= "toplevel" then
local types = split_types(self._data.type)
for _, typ in ipairs(types) do
local pinfo = mw.clone(self._info)
pinfo.label = ("%s分類列表"):format(typ)
table.insert(ret, {name = Category.new(pinfo), sort = (not self._lang and " " or "") .. label})
end
if #types > 1 then
local pinfo = mw.clone(self._info)
pinfo.label = ("混合分類列表"):format(typ)
table.insert(ret, {name = Category.new(pinfo), sort = (not self._lang and " " or "") .. label})
end
end
return ret
end
function Category:getChildren()
return nil
end
function Category:getUmbrella()
if not self._lang then
return nil
end
-- We take advantage of the fact that this function (getUmbrella) is fully overridden in
-- [[Module:category tree/ws topic cat]]. That code never calls this function. Moreover,
-- this function is only called when attempting to display the category boilerplate, not
-- simply when a category object is instantiated. This makes it a safe place to throw an
-- error when a user tries to create a thesaurus-only category under a regular mainspace
-- title.
if self._data and self._data.thesaurusonly then
error('This is a thesaurus-only category, you cannot do this')
end
local uinfo = mw.clone(self._info)
uinfo.code = nil
return Category.new(uinfo)
end
function Category:getTOCTemplateName()
local lang = self._lang
local code = lang and lang:getCode() or "zh"
return "Template:" .. code .. "-categoryTOC"
end
return export