模块:Catnav

求闻百科,共笔求闻

本模块用于模板链接:{{catnav}}模板。

上述文档内容嵌入自Module:Catnav/doc编辑 | 历史
编者可以在本模块的沙盒创建 | 镜像和测试样例创建页面进行实验。
请将模块自身所属的分类添加在文档中。本模块的子页面
local yesno = require 'Module:Yesno'
local getArgs = require 'Module:Arguments'.getArgs
local tools = require 'Module:TableTools'

-- 根分类的名称。若为 nil,则即使 noroot 不为 false,也不会补充根分类。
local rootCatName = '总览'

-- 在自动补充分类树模式下,每一条分类树的最大项数(超出数量则不再往前面补充分类名称,而是添加省略号)。
local max_catList_size = 8

-- 在自动补充分类树模式下,最多多少个分类树(超出数量会停止产生分类树,并报出警告)。
local max_catLists_size = 20

-- 用于避免一个分类树的一个部分产生过多的分支,数字越大允许的数量也越多。当分支数量过多时,即使还没有到达上述两个限制,分类树最左侧也会使用“(x个分类)”的表述代替
local branch_detection_level = 12

local p = {}

-- 可用作分类命名空间名称的集合。
local categoryNamespaces = tools.listToSet(mw.site.namespaces.category.aliases)
categoryNamespaces.Category = true
p.categoryNamespaces = categoryNamespaces
function p._main(args)
	local auto = yesno(args.auto)  -- boolean
	if auto then
		--[[
			注意:一般args[1]参数为该分类的上一级分类(自己手动设置),如果有多个,
			则多次调用此模板。当然你也可以留空,使其直接获取*当前*页面的分类,但
			需要注意的是,在分类页面预览页面时,捕获到的*当前*页面的分类时编辑
			之前的,这种情况只有提交后才能看到更改,所以不建议这么做。
		]]
		return p._auto(args)
	end
	if rootCatName and not yesno(args.noroot) then -- noroot=1时,不自动补充根分类。
		if args[1] ~= rootCatName  then
			table.insert(args, 1, rootCatName)
		end
	end
	-- args:
	-- rootCatName; aaa; bbb; ccc
	local currentTitle = mw.title.getCurrentTitle()
	local currentIsCategory = currentTitle.namespace == 14
	if currentIsCategory and not yesno(args.nohere) then -- nohere=1时,不自动补充本页链接
		if currentTitle.text ~= args[#args] then
			args[#args+1] = currentTitle.text
		end
	end
	-- args:
	-- rootCatName; aaa; bbb; ccc; 页面名
	local cat = nil
	if (not auto) and currentIsCategory and not yesno(args.nocat) then
		cat = args[#args]
		if cat == currentTitle.text then
			cat = args[#args-1]
		end
	end
	local list = {} -- 承载链接字符串的表
	for k, v in ipairs(args) do
		if v == '' then
			v = nil
		elseif type(v) == 'table' then
			v = v.custom
		elseif v == '...' or v == '…' or v == '……' then
		else
			v = "[[:Category:" .. v .. "|" .. v .. "]]"
		end
		list[#list+1] = v
	end
	return tostring(mw.html.create("div")
		:addClass("catnav")
		:wikitext(table.concat(list," > "))
		:wikitext(cat and '[[Category:' .. cat .. ']]' or ''))
end

function p.main(frame)
	return p._main(tools.shallowClone(getArgs(frame)))
end

local unpack = unpack or table.unpack
--[[
	根据页面名称判断其子分类,如果没有子分类了则说明当前的catList完成了,将其
	加入catLists,否则继续递归,直到不再有子分类,或者已经超限了。
	@string name 当前页面的名称。
	@list <string> catList 当前的分类树。它有可能直接被修改,也有可能被复制。
	@list <list <string>> catLists 当前的分类树的列表。检测到catList完成之后,就
	会将其加入catLists。任何时候在执行此函数之前,catLists都是不包含catList的。
	最终通过递归,catLists自身可能会被添加元素。
]]
local function appendCategoryTrees(catList, catLists)
	local name = catList[1]
	
	local categories = p.getCategories(name)
	
	if #categories == 0 then
		-- 说明当前页面没有分类了,是相对的根分类,将保存的catList追加至catLists。
		catLists[#catLists + 1] = catList
		return
	elseif #catLists >= max_catLists_size then
		mw.addWarning '[[Module:Catnav]]检测到一个分类具有过多的分类树,可能是因为存在分类循环。'
		catLists.exceeded = true
		return
	elseif #catList >= max_catList_size then
		table.insert(catList, 1, '...')
		catLists[#catLists + 1] = catList
		return
	else
		local index = -1
		for i, catName in ipairs(catList) do
			if i > 1 and catName == name then
				local category = catList[i + 1]
				local msg = {}
				for j = i, 1, -1 do
					msg[#msg + 1] = '[[:Category:' .. catList[j] .. '|' .. catList[j] .. ']]'
				end
				mw.addWarning ('[[Module:Catnav]]检测到分类循环:'
					.. table.concat(msg, ' → ')
					)
				table.insert(catList, 1, '...')
				catLists[#catLists + 1] = catList
				return
			end
		end
	end
	if #categories == 1 then
		-- 当前分类只有一个上级分类,故直接修改catList,然后进行下一轮。
		local category = categories[1]
		table.insert(catList, 1, category)
		appendCategoryTrees(catList, catLists)
	else
		if 2*#categories + #catList >= branch_detection_level then
			-- 说明该分类的上级分类过多,不再逐个展示。
			table.insert(catList, 1, {custom = '...(' .. #categories .. '个分类)'})
			catLists[#catLists + 1] = catList
			return
		else
			for i, category in ipairs(categories) do
				local clonedCatList = {unpack(catList)}
				table.insert(clonedCatList, 1, category)
				appendCategoryTrees(clonedCatList, catLists)
			end
		end
	end
end
p.appendCategoryTrees = appendCategoryTrees

function p.getCategoryTrees(initialCatList)
	local catLists = {}
	appendCategoryTrees(initialCatList, catLists)
	return catLists
end

function p._auto(args)
	if not args[1] then
		args[1] = mw.title.getCurrentTitle().text
	end
	local catLists = p.getCategoryTrees(args)
	local result = {}  -- list of string
	for i, catList in ipairs(catLists) do
		catList.noroot = true
		catList.nocat = true
		catList.nohere = yesno(args.nohere)
		catList.auto = false
		if #catList < 2 then return end
		result[#result + 1] = p._main(catList)
	end
	if catLists.exceeded then
		result[#result + 1] = '<div>(仅列举出部分可能的分类关系)</div>'
	end
	
	return table.concat(result, '\n')
end

--[=[
	获取指定分类页面(或任意页面)所属的分类。将根据源代码进行判断。
	这意味着只有在分类页面中直接使用 [[Category:xxx]] 这样的链接才会被识别。
	通过模板等方式间接添加的分类不会被识别。
	@string name 页面名。
	@return list of string
]=]
function p.getCategories(name)
	local title = mw.title.new(name, 'category')
	local content = title:getContent()
	if not content then return {} end
	return p.findCategoriesFromContent(content)
end

--[=[
	根据内容进行正则解析,从而判断其拥有的分类。
	不会尝试将其展开。
	@string content
	@return list of string
]=]
function p.findCategoriesFromContent(content)
	local result = {}
	for namespace, text in content:gmatch '%[%[%s*([^:%|%[%]]+)%s*:%s*([^%[%]]+)%s*]]' do
		if categoryNamespaces[namespace] then
			text = text:match '(.-)%|' or text
			result[#result + 1] = text
		end
	end
	return result
end

return p