模組:Families

維基詞典,自由的多語言詞典

此模块用於檢索和管理維基詞典的各種語言系屬及其相關資訊。請參閱Wiktionary:語系以了解更多資訊。

此模块提供對其他模块的存取。 要從模板中存取資訊,請參閱Module:families/templates

資訊本身儲存在 Module:families/data 中,該模块不應被任何其他模块直接使用,只能透過 Module:families 提供的函數存取資料。

找尋語言系屬

此模組導出許多用於查找語言系屬的函數。

getByCode

getByCode(code)

尋找代碼與所提供代碼相符的語言系屬。如果存在,則傳回代表該族的 Family 物件,否則返回 nil

尋找代碼與所提供的代碼相符的系列。 如果存在,則傳回代表家庭的 Family 物件。 否則,它會返回零。

getByCanonicalName

getByCanonicalName(name)

尋找其規範名稱(維基詞典上用於表示該語言的名稱)與所提供的名稱相符的語言系屬。如果存在,則回傳代表家庭的 Family 物件,否則回傳 nil。 家族的規範名稱應該始終是唯一的(維基詞典上,兩個語言系屬共享相同的規範名稱是錯誤的),因此這保證最多給出一個結果。

Family 物件

Family 物件是從上述函數之一傳回的。它是一個語言系屬及其關聯資料的 Lua 表示形式。它有許多可以呼叫的方法(使用 : 語法),例如:

local m_families = require("Module:families")
local fam = m_families.getByCode("ine")
local name = fam:getCanonicalName()
-- "name" will now be "Indo-European"

Family:getCode

:getCode()

返回語言系屬的代碼。例如:印歐語系的 "ine"

Family:getCanonicalName

:getCanonicalName()

返回語言系屬的規範名稱。這是維基詞典上用來代表該系屬的唯一名稱。例如:印歐語系的"印歐語系"

Family:getDisplayForm

:getDisplayForm()

返回語言系屬的顯示形式。對於語言系屬,這與 :getCategoryName("nocap") 傳回的值相同,即它讀取「NAME」(如"印歐語系")(和英語維基詞典不同,中文維基詞典的 NAME 亦包含語言系屬後綴)。對於語言和僅用於詞源的語言,這與規範名稱相同;對於文字,它讀取「NAME」(例如"阿拉伯文")。 :makeCategoryLink 中使用的顯示文字始終與顯示形式相同。

Family:getAllNames

:getAllNames()

傳回該系屬已知的所有名稱的表,包括規範名稱。這些名稱不能保證是唯一的,有時多個系屬共享同一名稱。例如:斯拉夫語支的 {"Slavic", "Slavonic"}

Family:getFamily

:getFamily()

傳回該系屬的上層系屬的 Family 物件。

Family:getProtoLanguage

:getProtoLanguage()

傳回該系屬的祖語的Language物件(如果存在;請參閱 Module:languages)。

Family:getCategoryName

:getCategoryName(nocap)

傳回該系屬的主要分類的名稱。例如:"日耳曼語族" 的分類 Category:日耳曼語族

除非給予可選參數 nocap,否則傳回值開頭的系屬名稱將大寫;這對於分類名稱是正確的,但如果系屬名稱是小寫且該函數的傳回值用在句子中間,則不正確。(以下文字為針對英語維基詞典的情況。)例如,代碼為 qfa-mix 的偽系屬名稱為 "混合語言",當用作分類名稱 Category:派生自混合語言的詞,該名稱應保持小寫,但在 Category:混合語言 中應大寫。如果您正在考慮使用getCategoryName("nocap"),請改用 getDisplayForm()

Family:getWikidataItem

:getWikidataItem()

回傳該系屬的維基數據項。

Family:getWikipediaArticle

:getWikipediaArticle()

傳回該系屬的維基百科條目,通常源自 :getWikidataItem()


local export = {}

function export.makeObject(code, data, useRequire)
	local Family = {}
	
	function Family:getCode()
		return self._code
	end
	
	function Family:getCanonicalName()
		return self._rawData[1]
	end
	
	function Family:getDisplayForm()
		return self:getCategoryName("nocap")
	end
	
	function Family:getOtherNames(onlyOtherNames)
		return require("Module:language-like").getOtherNames(self, onlyOtherNames)
	end
	
	function Family:getAliases()
		return self._rawData.aliases or {}
	end
	
	function Family:getVarieties(flatten)
		return require("Module:language-like").getVarieties(self, flatten)
	end
	
	--function Family:getAllNames()
	--	return self._rawData.names
	--end
	
	--[==[Given a list of types as strings, returns true if the family has all of them. 
	
	The following types are recognized:
	* {family}: This object is a family.
	* {full}: This object is a "full" family. This includes all families but a couple of etymology-only
			  families for Old and Middle Iranian languages.
	* {etymology-only}: This object is an etymology-only family, similar to etymology-only languages. There
						are currently only two such families, for Old Iranian languages and Middle Iranian
						languages (which do not represent proper clades and have no proto-languages, hence
						cannot be full families).
	]==]	
	function Family:hasType(...)
		if not self._type then
			self._type = {family = true}
			if self:getNonEtymologicalCode() == self:getCode() then
				self._type.full = true
			else
				self._type["etymology-only"] = true
			end
			if self._rawData.type then
				for _, type in ipairs(mw.text.split(self._rawData.type, "%s*,%s*")) do
					self._type[type] = true
				end
			end
		end
		for _, type in ipairs{...} do
			if not self._type[type] then
				return false
			end
		end
		return true
	end
	
	--[==[Returns a {Family} object for the superfamily that the family belongs to.]==]
	function Family:getFamily()
		if self._familyObject == nil then
			local familyCode = self:getFamilyCode()
			if familyCode then
				self._familyObject = export.getByCode(familyCode, useRequire)
			else
				self._familyObject = false
			end
		end
		return self._familyObject or nil
	end
	
	--[==[Returns the code of the family's superfamily.]==]
	function Family:getFamilyCode()
		if not self._familyCode then
			self._familyCode = self._rawData[3]
		end
		return self._familyCode
	end
	
	--[==[Returns the canonical name of the family's superfamily.]==]
	function Family:getFamilyName()
		if self._familyName == nil then
			local family = self:getFamily()
			if family then
				self._familyName = family:getCanonicalName()
			else
				self._familyName = false
			end
		end
		return self._familyName or nil
	end
	
	--[==[Check whether the family belongs to {superfamily} (which can be a family code or object), and returns a boolean. If more than one is given, returns {true} if the family belongs to any of them. A family is '''not''' considered to belong to itself.]==]
	function Family:inFamily(...)
		for _, superfamily in ipairs{...} do
			if type(superfamily) == "table" then
				superfamily = superfamily:getCode()
			end
			local family, code = self:getFamily()
			while true do
				if not family then
					return false
				end
				code = family:getCode()
				family = family:getFamily()
				-- If family is parent to itself, return false.
				if family and family:getCode() == code then
					return false
				elseif code == superfamily then
					return true
				end
			end
		end
	end
	
	function Family:getParent()
		if self._parentObject == nil then
			local parentCode = self:getParentCode()
			if parentCode then
				self._parentObject = require("Module:languages").getByCode(parentCode, nil, true, true, useRequire)
			else
				self._parentObject = false
			end
		end
		return self._parentObject or nil
	end
	
	function Family:getParentCode()
		if not self._parentCode then
			self._parentCode = self._rawData[5]
		end
		return self._parentCode
	end
	
	function Family:getParentName()
		if self._parentName == nil then
			local parent = self:getParent()
			if parent then
				self._parentName = parent:getCanonicalName()
			else
				self._parentName = false
			end
		end
		return self._parentName or nil
	end
	
	function Family:getParentChain()
		if not self._parentChain then
			self._parentChain = {}
			local parent = self:getParent()
			while parent do
				table.insert(self._parentChain, parent)
				parent = parent:getParent()
			end
		end
		return self._parentChain
	end
	
	function Family:hasParent(...)
		--checkObject("family", nil, ...)
		for _, other_family in ipairs{...} do
			for _, parent in ipairs(self:getParentChain()) do
				if type(other_family) == "string" then
					if other_family == parent:getCode() then return true end
				else
					if other_family:getCode() == parent:getCode() then return true end
				end
			end
		end
		return false
	end
	
	--[==[If the family is etymology-only, iterates through its parents until a regular family is found and returns it. If the family is a regular family, then it simply returns itself.]==]
	function Family:getNonEtymological()
		if not self._nonEtymologicalObject then
			local nonEtymologicalCode = self:getNonEtymologicalCode()
			if nonEtymologicalCode ~= self:getCode() then
				self._nonEtymologicalObject = require("Module:languages").getByCode(nonEtymologicalCode, nil, nil, true, useRequire)
			else
				self._nonEtymologicalObject = self
			end
		end
		return self._nonEtymologicalObject
	end
	
	function Family:getNonEtymologicalCode()
		return self._nonEtymologicalCode or self:getCode()
	end
	
	function Family:getNonEtymologicalName()
		if self._nonEtymologicalName == nil then
			local nonEtymological = self:getNonEtymological()
			if nonEtymological then
				self._nonEtymologicalName = nonEtymological:getCanonicalName()
			else
				self._nonEtymologicalName = false
			end
		end
		return self._nonEtymologicalName or nil
	end
	
	function Family:getProtoLanguage()
		if self._protoLanguageObject == nil then
			self._protoLanguageObject = require("Module:languages").getByCode(self._rawData.protoLanguage or self:getCode() .. "-pro", nil, true, nil, useRequire) or false
		end
		return self._protoLanguageObject or nil
	end
	
	function Family:getProtoLanguageCode()
		if self._protoLanguageCode == nil then
			local protoLanguage = self:getProtoLanguage()
			self._protoLanguageCode = protoLanguage and protoLanguage:getCode() or false
		end
		return self._protoLanguageCode or nil
	end
	
	function Family:getProtoLanguageName()
		if not self._protoLanguageName then
			self._protoLanguageName = self:getProtoLanguage():getCanonicalName()
		end
		return self._protoLanguageName
	end
	
	function Family:hasAncestor(...)
		-- Go up the family tree until a protolanguage is found.
		local family = self
		local protolang = family:getProtoLanguage()
		while not protolang do
			family = family:getFamily()
			protolang = family:getProtoLanguage()
			-- Return false if the family is its own family, to avoid an infinite loop.
			if family:getFamilyCode() == family:getCode() then
				return false
			end
		end
		-- If the protolanguage is not in the family, it must therefore be ancestral to it. Check if it is a match.
		for _, otherlang in ipairs{...} do
			if (
				type(otherlang) == "string" and protolang:getCode() == otherlang or
				type(otherlang) == "table" and protolang:getCode() == otherlang:getCode()
			) and not protolang:inFamily(self) then
				return true
			end
		end
		-- If not, check the protolanguage's ancestry.
		return protolang:hasAncestor(...)
	end
	
	local function fetch_descendants(self, format)
		local languages = require("Module:languages/code to canonical name")
		local etymology_languages = require("Module:etymology languages/code to canonical name")
		local families = require("Module:families/code to canonical name")
		local descendants = {}
		local family = self:getFamily()
		-- Iterate over all three datasets.
		for _, data in ipairs{languages, etymology_languages, families} do
			for code in pairs(data) do
				local lang = require("Module:languages").getByCode(code, nil, true, true, useRequire)
				if lang:inFamily(self) then
					if format == "object" then
						table.insert(descendants, lang)
					elseif format == "code" then
						table.insert(descendants, code)
					elseif format == "name" then
						table.insert(descendants, lang:getCanonicalName())
					end
				end
			end
		end
		return descendants
	end
	
	function Family:getDescendants()
		if not self._descendantObjects then
			self._descendantObjects = fetch_descendants(self, "object")
		end
		return self._descendantObjects
	end
	
	function Family:getDescendantCodes()
		if not self._descendantCodes then
			self._descendantCodes = fetch_descendants(self, "code")
		end
		return self._descendantCodes
	end
	
	function Family:getDescendantNames()
		if not self._descendantNames then
			self._descendantNames = fetch_descendants(self, "name")
		end
		return self._descendantNames
	end
	
	function Family:hasDescendant(...)
		for _, lang in ipairs{...} do
			if type(lang) == "string" then
				lang = require("Module:languages").getByCode(lang, nil, true, nil, useRequire)
			end
			if lang:inFamily(self) then
				return true
			end
		end
		return false
	end
	
	function Family:getCategoryName(nocap)
		local name = self._rawData[1]
	
		-- If the name already has "languages" in it, don't add it.
		if not name:find("[Ll]anguages$") then
			name = name .. ""
		end
		if not nocap then
			name = mw.getContentLanguage():ucfirst(name)
		end
		return name
	end
	
	function Family:makeCategoryLink()
		return "[[:Category:" .. self:getCategoryName() .. "|" .. self:getDisplayForm() .. "]]"
	end
	
	function Family:getWikidataItem()
		local item = self._rawData[2] or self._rawData.wikidata_item
		
		if not item then
			return nil
		end
		
		if type(item) ~= "number" then
			error("The method getWikidataItem expects the item to be stored as a number, but it is currently a " .. type(code) .. ".")
		end
	
		return "Q" .. item
	end
	
	function Family:getWikipediaArticle()
		return (self:getWikidataItem() and mw.wikibase and mw.wikibase.sitelink(self:getWikidataItem(), 'enwiki')) or
			self:getCategoryName()
	end
	
	function Family:makeWikipediaLink()
		return "[[w:" .. self:getWikipediaArticle() .. "|" .. self:getCanonicalName() .. "]]"
	end
	
	function Family:toJSON()
		if not self._type then
			self:hasType()
		end
		local types = {}
		for type in pairs(self._type) do
			table.insert(types, type)
		end
		
		local ret = {
			canonicalName = self:getCanonicalName(),
			categoryName = self:getCategoryName("nocap"),
			code = self:getCode(),
			family = self._rawData[3],
			protoLanguage = self._rawData.protoLanguage,
			otherNames = self:getOtherNames(true),
			aliases = self:getAliases(),
			varieties = self:getVarieties(),
			type = types,
			wikidataItem = self:getWikidataItem(),
		}
	
		return require("Module:JSON").toJSON(ret)
	end
	
	function Family:getRawData()
		return self._rawData
	end
	
	Family.__index = Family
	
	return data and setmetatable({ _rawData = data, _code = code }, Family) or nil
end

function export.getByCode(code, useRequire)
	local function conditionalRequire(modulename)
		if useRequire then
			return require(modulename)
		else
			return mw.loadData(modulename)
		end
	end
	
	local data = conditionalRequire("Module:families/data")[code]
	if data then
		return export.makeObject(code, data, useRequire)
	end
	
	data = require("Module:families/track-bad-etym-code")(code) and conditionalRequire("Module:families/data/etymology")[code]
	if data then
		return require("Module:languages").makeObject(code, data, useRequire)
	end
	
	return nil
end

function export.getByCanonicalName(name, useRequire)
	local function conditionalRequire(modulename)
		if useRequire then
			return require(modulename)
		else
			return mw.loadData(modulename)
		end
	end
	
	local byName = conditionalRequire("Module:families/canonical names")
	local code = byName and byName[name] or
		byName[name:match("^(.*) languages$")]

	return export.getByCode(code, useRequire)
end

return export