Module:Namespace pagename

来自Kyoshihira Encyclopedia
Lhscogs​(留言 | 贡献)2026年10月20日 (二) 01:25的版本 (导入1个版本)
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)
跳转到导航 跳转到搜索

此模块的文档可以在Module:Namespace pagename/doc创建

-- =====================================================================
-- 根据页面命名空间和实际内容综合判断该页面类型
--
-- 原作者:SunAfterRain
--
-- 维护说明:
--
-- 测试用例见 [[Template:Namespace pagename/testcases]]
--
-- 1. 命名空间(含伪空间)ID、英文名与简繁中文名在[[Module:Namespace/data]]维护。如增加新命名空间注意同时修改[[Template:NS1]]
-- 2. 页面具体内容(例如重定向、沙盒等)名称、判定规则在[[Module:Namespace pagename/data]]维护。
-- 3. 判断逻辑在本页维护。
-- =====================================================================

require('strict')

local yesno = require('Module:Yesno')
local mNS = require('Module:Namespace')
local mError = require('Module:Error')
local data = mw.loadData('Module:Namespace pagename/data')
local lan = mNS._lan
local NS = data.NS

local p = {}

-- ---------------------------------------------------------------------
-- 辅助函数
-- ---------------------------------------------------------------------
local function msg(group, key, lang)
	-- 包裹 lan(data[group][key], lang)
	return lan(data[tostring(group)][tostring(key)] or {}, lang)
end

local function inArray(item, arr)
	for i, v in ipairs(arr) do
		if item == v then
			return v
		end
	end
	return nil
end

local function getDefaultSuffix(nsid, lang)
	local pageConv = data.NamespaceSuffixConfig.DefaultSuffixList[nsid]
	if pageConv == false then
		return ''
	end
	return lan(pageConv, lang)
end

local function lowerFirst(value)
	-- 將字串第一個文字換成小寫
	return mw.ustring.lower(mw.ustring.sub(value, 1, 1)) .. mw.ustring.sub(value, 2)
end

local function makeError(errorMessage)
	return mError.error{'[[Module:Namespace pagename]]錯誤:' .. errorMessage}
end
-- ---------------------------------------------------------------------
-- 页面类型匹配规则
-- ---------------------------------------------------------------------

-- 是否为沙盒
local function isSandbox(title, nsid)
	local nsSandbox = data.SandboxPageNames[nsid]
	if nsSandbox and nsSandbox[title.text] then
		return true
	end

	-- 用户页沙盒
	if nsid == NS.USER and mw.ustring.match(title.text, '^[^/]+/沙盒$') then
		return true
	end

	return false
end

-- 根据data中配置的子页面名称(SubpageRules)匹配页面类型
local function matchSubpageType(title, nsid)
	local subpageName = lowerFirst(title.subpageText)
	local isSubpage = title.text ~= title.rootText

	for pageType, rule in pairs(data.SubpageRules) do
		-- 先判斷這條規則在本命名空間、本頁面是否適用
		local ok
		if rule.matcher then
			-- 自定义 matcher 取代默认的命名空间白名单与子页面判断
			ok = true
			if rule.matcher.allowNs then
				ok = inArray(nsid, rule.matcher.allowNs) ~= nil
			end
			if rule.matcher.subpageOnly then
				ok = ok and isSubpage
			end
		else
			-- 默认子页面匹配:必须是子页面,且符合命名空间白名单
			local allowList = (pageType == 'temp')
				and data.NamespaceSuffixConfig.AllowTempType
				or data.NamespaceSuffixConfig.AllowTemplateType
			ok = isSubpage and inArray(nsid, allowList) ~= nil
		end

		-- 不論有沒有 matcher,rule.names 存在時都必須命中,
		-- 否則設了 matcher 的規則會變成該命名空間下所有子頁面的萬用規則
		if ok and (not rule.names or inArray(subpageName, rule.names)) then
			return pageType
		end
	end

	return nil
end

-- 根据data中配置的页面内容特征(ContentRules)匹配类型
local function matchContentType(content, nsid)
	local isAllowTemplateType = inArray(nsid, data.NamespaceSuffixConfig.AllowTemplateType)

	for pageType, patterns in pairs(data.ContentRules) do
		-- doc 与 testcases 仅在 AllowTemplateType 指定的命名空间下检测
		local allowed = (pageType == 'sandbox') or isAllowTemplateType
		if allowed then
			for _, pat in ipairs(patterns) do
				if type(pat) == 'function' then
					if pat(nsid, content) then return pageType end
				elseif type(pat) == 'string' then
					if mw.ustring.match(content, pat) then return pageType end
				end
			end
		end
	end

	return nil
end

-- 检查提示类特殊页面(Editnotice / Emailnotice 等)
local function matchNotice(title, nsid)
	for pageType, rule in pairs(data.NoticeRules) do
		if rule.matcher then
			local nsRule = rule.matcher[nsid]
			if nsRule then
				local ok = true
				if nsRule.rootText then
					ok = ok and title.rootText == nsRule.rootText
				end
				if nsRule.subpageText then
					ok = ok and title.subpageText == nsRule.subpageText
				end
				if nsRule.onlyFirstLevel then
					ok = ok and title.rootText == title.baseText
				end
				if ok then
					return pageType
				end
			end
		end
	end

	return nil
end

-- ---------------------------------------------------------------------
-- 核心解析逻辑
-- ---------------------------------------------------------------------

-- @param {string} [lang]
-- @param {string | number} [pageOrNs]
-- - string: 頁面(註:此函數不檢查是否有效) or (偽)命名空間的名字
-- - number: 命名空間數字
-- @param {boolean} [cfg.forcePage] 讓模組強行把輸入解釋成頁面
-- @param {boolean} [cfg.skipCheckPageType] 抑制檢測頁面類型,效果形同 pageOrNs 不是傳頁面
-- @param {boolean} [cfg.skipCheckContent] 抑制使用頁面內容檢測頁面類型
function p._namespace_pagename(lang, pageOrNs, cfg)
	cfg = cfg or {}

	local success, title, PNS, PNSIsTalk, nsid

	--
	-- 一、处理标题
	--
	if not pageOrNs or pageOrNs == '' then
	    -- 如果未指定頁面及命名空間參數,以當前頁面讀取
	    title = mw.title.getCurrentTitle()
	    nsid = title.namespace
	else
		local ns
		if not cfg.forcePage then
			if pageOrNs:match('^-?%d+$') and mw.site.namespaces[tonumber(pageOrNs)] then
				-- 如果用户指定一个数字,优先视为命名空间数字,无相应数字时才视为条目名称
				ns = mw.site.namespaces[tonumber(pageOrNs)]
			else
				-- 再判断是否为命名空间名称
				ns = mw.site.namespaces[pageOrNs]
				if not ns and pageOrNs:match(':$') then
					-- 允許 <ns>:
					ns = mw.site.namespaces[pageOrNs:sub(1, -2)]
				end
			end
		end

		-- 如果为命名空间名称,按命名空间处理,否则按页面标题处理
		if ns then -- pageOrNs 是{{ns:}}能解析的
			-- 請注意:
			-- 如果你想指向一個名為 [[3]] 的條目,你應該
			-- 1. 傳參 :3
			-- 2. 指定 forcePage=true
			-- 以免落到這裡發生錯誤
			-- 想指定名為 [[維基百科]] 的條目同理
			nsid = ns.id
			title = nil
		else
			success, title = pcall(mw.title.new, pageOrNs)
			if not success or not title or title.prefixedText == '' then
				-- 不是一个合法页面名称,报错
				return makeError(string.format(msg('Error', 'pg-error', lang), pageOrNs))
			else
				-- 其他正常標題或偽命名空間,無效的命名空間也被當條目處理
				nsid = title.namespace
				if nsid == 0 or nsid == 1 then
					PNS, PNSIsTalk = mNS._isPseudoNamespace(pageOrNs, true)
				end
			end
		end
	end

	-- 1.1 外部链接与伪命名空间,提前返回
	if title and title.isExternal then
		return msg('Type', 'external', lang)
	elseif PNS then
		-- 偽命名空間
		-- 丟給[[Module:Namespace]]處理
		return mNS._pseudoNamespaceDisplay(lang, PNS, PNSIsTalk)
	end

	-- 1.2 命名空间名称
	local namespace = mNS._namespaceName(lang, nsid)

	-- 如果不需要检测页面类型,或者没有加载页面内容,则直接结束
	if cfg.skipCheckPageType or not title then
		return namespace .. getDefaultSuffix(nsid, lang)
	end

	--
	-- 二、判断頁面内容類型
	--
	local pageType

	-- 特殊处理:固定沙盒
	if isSandbox(title, nsid) then
		return namespace .. msg('Type', 'sandbox', lang)
	end

	-- 特殊处理:编辑提示/邮件提示页面
	pageType = matchNotice(title, nsid)
	if pageType ~= nil then
		local text = msg('Type', pageType, lang)
		local userLink = '[[User:' .. title.rootText .. '|' .. title.rootText .. ']]'
		text = string.gsub(text, '%%u', function() return userLink end)
		return text
	end

	-- 特殊处理:(特例處理)模組討論頁當測試樣例展示效果
	-- 模組討論命名空間不在 EnablePageTypeNamespaces 内,必须在 2.1 之前处理
	if nsid == NS.MODULE_TALK and lowerFirst(title.subpageText) == 'testcases' then
		return namespace .. msg('Type', 'testcases', lang)
	end

	-- 2.1 判断是否应该继续检测页面内容类型
	local isCheckPageType = inArray(nsid, data.NamespaceSuffixConfig.EnablePageTypeNamespaces)
	if not isCheckPageType then
		return namespace .. getDefaultSuffix(nsid, lang)
	end

	-- 2.2 根据内容模型判断,如非标准wikitext则直接结束
	local modelType = data.ContentModelMap[title.contentModel]
	if modelType then
		return namespace .. msg('Type', modelType, lang)
	end

	-- 2.3 是否为重定向
	if title.isRedirect then
		return namespace .. msg('Type', 'redirect', lang)
	end

	-- 2.4 根据子页面名称判断
	-- 追加檢測文檔、沙盒、測試樣例的命名空間
	-- 追加檢測臨時頁面的命名空間
	-- matchSubpageType已对该命名空间该不该追加作出判断
	if pageType == nil then
		pageType = matchSubpageType(title, nsid)
		-- 立即结束,减少不必要嵌入
		if pageType ~= nil then
			return namespace .. msg('Type', pageType, lang)
		end
	end

	-- 2.5 根据页面内容判断
	if not cfg.skipCheckContent then
		local content = title:getContent() or ''
		pageType = matchContentType(content, nsid)
	end
	if pageType then
		return namespace .. msg('Type', pageType, lang)
	end

	return (namespace .. getDefaultSuffix(nsid, lang) or '')
end

function p.main(frame) 
	local args = require('Module:Arguments').getArgs(frame, {
		valueFunc = function (key, value)
			if key == 2 then
				return type(value) == 'string' and mw.text.trim(value) or value
			elseif value then
				value = mw.text.trim(value)
				if value ~= '' then
					return value
				end
			end
			return nil
		end
	})

	local lang = args[1] or args['1'] or args['lang'] or nil
	local pageOrNs = args[2] or args['2'] or args['pageOrNs'] or nil
	local forcePage = yesno(args['forcePage']) or false
	local skipCheckPageType = yesno(args['skipCheckPageType']) or false
	local skipCheckContent = yesno(args['skipCheckContent']) or false

	return p._namespace_pagename(lang, pageOrNs, {
		forcePage = forcePage,
		skipCheckPageType = skipCheckPageType,
		skipCheckContent = skipCheckContent,
	})
end

return p