Kontent qismiga oʻtish

Modul:category tree

Vikilug‘atdan olingan

Ushbu modul turkumda stereotiplarni andozalari ishlab chiqarish uchun ishlatiladi. Bu toʻgʻri foydalanish uchun moʻljallangan emas. Aksincha, har bir Andoza deb andozalarini oʻziga qoʻllari oʻz submodule ega boʻladi. Ushbu hujjatlar faqat turkumda daraxt tizimini Generics oʻz ichiga oladi. Agar maʼlum bir andozani yoki qoʻshish yoki turkumda ma'lumotlarni oʻzgartirish uchun qanday hujjatlar uchun izlayotgan boʻlsangiz, bu andozalarini hujjatiga qarang.

Parametrlar

[tahrirlash]

Turkum daraxt moduli sifatida taklif etiladi:

{{#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. 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. {{famcatboiler}} 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 like Latn or 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.

General workings

[tahrirlash]

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.

Turkum obʼektlari

[tahrirlash]

Andoza:documentation outdated Turkum obʼektlar har bir submoduli yangi funktsiyasi tomonidan qaytarilur. Ular daraxtda bir kategoriya vakili. A turkum obʼekt turkumidagi haqida maʼlumot soʻrash uchun u chaqirdi mumkin, turli usullarini bor.

getBreadcrumbName

[tahrirlash]

getBreadcrumbName()

Turkum sahifaning tepasida "ket haqli sinib" kategoriyadagi uchun ishlatiladi nomini qaytaradi.

getDataModule

[tahrirlash]

getDataModule()

Ushbu turkumda uchun maʼlumotlarni oʻz ichiga olgan modul nomini qaytaradi. Bu foydalanuvchilar topish va yanada oson maʼlumotlarni tahrir qilish imkonini beradi toifali kuni, "tartibga solish" bogʻ yaratish uchun ishlatiladi.

getCategoryName

[tahrirlash]

getCategoryName()

Ushbu turkumda obʼekt ifodalaydi toifali nomini qaytaradi.

getDescription

[tahrirlash]

getDescription()

Turkum sahifaning yuqorisida koʻrsatilgan bayoni matnni qaytaradi. Turkum yoʻq tavsifi ega boʻlsa, bu nol qaytaradi.

getParents

[tahrirlash]

getParents()

Ushbu turkumda ota toifalarida bir jadval qaytaradi. Jadvalda har bir element ikki elementlar bilan bir stol oʻzi emas:

.nom
Ota-kategoriya vakili An turkumda obʼekt, yoki toʻg'ridan-toʻgʻri, ota-toifali nomini bildiradi, bir string: ikki imkoniyatlari biri.
.sort
Ota-ona joriy kategoriya ishlatilishi kerak saralash tugmasini bosing.

Turkum yoʻq ota-onasiga bor boʻlsa, bu nol qaytaradi.

getChildren

[tahrirlash]

getChildren()

Ushbu toifadagi bolalar toifalari jadvali qaytaradi. Jadvalda har bir element bola kategoriya vakili boʻlgan turkumda obʼekt hisoblanadi. Turkum farzandi yoʻq, bu qaytadi ega boʻlsa nil.

getUmbrella

[tahrirlash]

getUmbrella()

Hozirgi kategoriyalari ning mos soyabon turkumidagi uchun bir kategoriya obyekti qaytaradi. Joriy Turkum soyabon turkumda allaqachon boʻlsa, bu nol qaytaradi. Bundan tashqari, kategoriya yoʻq soyabon kategoriya ega boʻlsa nol qaytaradi.


local m_str_utils = require("Module:string utilities")
local m_template_parser = require("Module:template parser")
local m_utilities = require("Module:utilities")

local class_else_type = m_template_parser.class_else_type
local concat = table.concat
local insert = table.insert
local new_title = mw.title.new
local pages_in_category = mw.site.stats.pagesInCategory
local parse = m_template_parser.parse
local remove_comments = m_str_utils.remove_comments
local sort = table.sort
local split = m_str_utils.split
local uupper = m_str_utils.upper

local current_frame = mw.getCurrentFrame()
local current_title = mw.title.getCurrentTitle()
local inFundamental = mw.loadData("Module:category tree/data")

local function show_error(text)
	return require("Module:message box").maintenance(
		"red",
		"[[File:Ambox warning pn.svg|50px]]",
		"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 "")
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;\">"
		.. content .. "</div>"
end

local function show_editlink(current)
	return link_box(
		"[" .. tostring(mw.uri.fullUrl(current:getDataModule(), "action=edit"))
		.. " Edit category data]")
end

function show_related_changes()
	local title = current_title.fullText
	return link_box(
		"["
		.. tostring(mw.uri.fullUrl("Special:RecentChangesLinked", {
			target = title,
			showlinkedto = 0,
		}))
		.. ' <span title="Recent edits and other changes to pages in ' .. title .. '">Recent changes</span>]')
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:tili").getByCode(lang_code, true, nil, nil, true)
		if lang then
			-- Proto-Norse (gmq-pro) is the probably language with a code ending in -pro
			-- that's intended to have mostly non-reconstructed entries.
			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;"
! Newest and oldest pages&nbsp;
|-
| id="recent-additions" style="font-size:0.9em;" | '''Newest pages ordered by last [[mw:Manual:Categorylinks table#cl_timestamp|category link update]]:'''
%s
|-
| id="oldest-pages" style="font-size:0.9em;" | '''Oldest pages ordered by last edit:'''
%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)
	)
)
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 = nil
		local display_name = nil
		local nocap = nil
		
		if type(current) == "string" then
			category = current
			display_name = current:gsub("^Category:", "")
		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
			display_name = mw.getContentLanguage():ucfirst(display_name)
		end
		insert(steps, 1, "[[:" .. 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
		elseif inFundamental[category] then
			current = "Category:Fundamental"
		end	
	end
	
	local templateStyles = require("Module:TemplateStyles")("Module: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
	local div = mw.html.create("div")
		:attr("role", "navigation")
		:attr("aria-label", "Breadcrumb")
		:addClass("ts-categoryBreadcrumbs")
		:node(ol)
	
	return templateStyles .. tostring(div)
end

-- Show a short description text for the category.
local function show_description(current)
	return (current:getDescription() or "")
end

local function show_appendix(current)
	local appendix
	
	if current.getAppendix then
		appendix = current:getAppendix()
	end
	
	if appendix then
		return "For more information, see [[" .. appendix .. "]]."
	else
		return nil
	end
end

-- Show a list of child categories.
local function show_children(current)
	local children = current:getChildren()
	
	if not children then
		return nil
	end
	
	sort(children, function(first, second) return uupper(first.sort) < uupper(second.sort) end)
	
	local children_list = {}
	
	for _, child in ipairs(children) do
		local child_pagetitle
		if type(child.name) == "string" then
			child_pagetitle = child.name
		else
			child_pagetitle = "Category:" .. child.name:getCategoryName()
		end
		local child_page = new_title(child_pagetitle)
		
		if child_page.exists then
			local child_description =
				child.description or
				type(child.name) == "string" and child.name:gsub("^Category:", "") .. "." or
				child.name:getDescription("child")
			insert(children_list, "* [[:" .. child_pagetitle .. "]]: " .. child_description)
		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
		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
	if current.getCatfixInfo then
		lang, sc = current:getCatfixInfo()
	elseif not (current._info and current._info.no_catfix) then
		-- FIXME: This is hacky and should be removed.
		lang = current._lang
		sc = current._info and require("Module:scripts").getByCode(current._info.sc, true, nil, true) or nil
	end
	if lang then
		return m_utilities.catfix(lang, sc)
	else
		return nil
	end
end

-- Show the parent categories that the current category should be placed in.
local function show_categories(current, categories)
	local parents = current:getParents()
	
	if not parents then
		return
	end
	
	for _, parent in ipairs(parents) do
		local sortkey = type(parent.sort) == "table" and parent.sort:makeSortKey() or parent.sort
		if type(parent.name) == "string" then
			insert(categories, "[[" .. parent.name .. "|" .. sortkey .. "]]")
		else
			insert(categories, "[[Category:" .. 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:tili").getByCode("en", true, nil, nil, true):makeSortKey(sortkey)
		if type(umbrella) == "string" then
			insert(categories, "[[" .. umbrella .. "|" .. sortkey .. "]]")
		else
			insert(categories, "[[Category:" .. 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 my (Benwing's) create_wanted_categories.py script.
		return
	end
	local defaultsort, displaytitle, page_has_param
	for node in parse(content):__pairs("next_node") 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:Pages with DEFAULTSORT conflicts]]")
				defaultsort = true
			elseif name == "DISPLAYTITLE:" and not displaytitle then
				insert(categories,"[[Category:Pages with DISPLAYTITLE conflicts]]")
				displaytitle = true
			end
		elseif node_class == "parameter" and not page_has_param then
			insert(categories,"[[Category:Pages with raw triple-brace template parameters]]")
			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]*():")
		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:Categories with categories using raw markup]]")
					break
				end
			end
		end
		head = open
	end
end

local function generate_output(current)
	local functions = {
		"getBreadcrumbName",
		"getDataModule",
		"canBeEmpty",
		"getDescription",
		"getParents",
		"getChildren",
		"getUmbrella",
		"getAppendix",
		"getTOCTemplateName",
	}
	
	if current then
		for _, functionName in pairs(functions) do
			if type(current[functionName]) ~= "function" then
				require("Module:debug").track{ "category tree/missing function", "category tree/missing function/" .. functionName }
			end
		end
	end

	local boxes = {}
	local display = {}
	local categories = {}
	
	-- Categories should never show files as a gallery.
	insert(categories, "__NOGALLERY__")
	
	if current_frame:getParent():getTitle() == "Template:auto cat" then
		insert(categories, "[[Category:Categories calling Template:auto cat]]")
	end
	
	-- Check if the category is empty
	local totalPages = pages_in_category(current_title.text, "all")
	local hugeCategory = totalPages > 1250000
	
	-- Categorize huge categories, as they can't use DynamicPageList (see below).
	if hugeCategory then
		insert(categories, "[[Category:Huge categories]]")
	end
	
	-- Are the parameters valid?
	if not current then
		insert(categories, "[[Category:Categories that are not defined in the category tree]]")
		insert(categories, totalPages == 0 and "[[Category:Empty categories]]" or nil)
		insert(display, show_error(
			"Double-check the category name for typos. <br>" ..
			"[[Special:Search/Category: " .. current_title.text:gsub("^.+:", ""):gsub(" ", "~2 ") .. '~2|Search existing categories]] to check if this category should be created under a different name (for example, "Fruits" instead of "Fruit"). <br>' ..
			"To add a new category to Wiktionary's category tree, please consult " .. current_frame:expandTemplate{title = "section link", args = {
				"Help:Category#How_to_create_a_category",
			}} .. "."))
		
		-- 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
		insert(categories, "[[Category:Categories with incorrect names]]")
		insert(display, show_error(
			"Based on the data in the category tree, this category should be called '''[[:Category:" .. currentName .. "]]'''."))
	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:Empty categories]]")
	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 the category has more than 1.25 million members, as DynamicPageList times out with very large
	-- categories.
	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_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

local export = {}

function export.split_lang_label(titleObject)
	local getByCanonicalName = require("Module:tili").getByCanonicalName
	local text = titleObject.text
	
	-- Progressively remove a word from the potential canonical name until it
	-- matches an actual canonical name.
	local words = split(text, " ", true)
	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
	
	return nil, text
end

-- The main entry point from [[Module:auto cat]].
-- TODO: merge [[Module:auto cat]] into this module.
function export.main(submodule, info)
	submodule = require("Module:category tree/" .. submodule)
	return generate_output(submodule.main(info))
end

-- TODO: new test entrypoint.

return export