Module:etymon
Appearance
- The following documentation is located at Module:etymon/documentation. [edit]
- Useful links: subpage list • links • transclusions • testcases • sandbox
This module implements the template {{etymon}}
.
local export = {}
local anchors_module = "Module:anchors"
local etymology_module = "Module:etymology"
local headword_data_module = "Module:headword/data"
local languages_module = "Module:languages"
local languages_errorgetby_module = "Module:languages/errorGetBy"
local links_module = "Module:links"
local pages_module = "Module:pages"
local parameters_module = "Module:parameters"
local parameters_data_module = "Module:parameters/data"
local string_utilities_module = "Module:string utilities"
local template_parser_module = "Module:template parser"
local templatestyles_module = "Module:TemplateStyles"
local utilities_module = "Module:utilities"
local concat = table.concat
local find = string.find
local gsub = string.gsub
local html_create = mw.html.create
local insert = table.insert
local match = string.match
local max = math.max
local new_title = mw.title.new
local next = next
local require = require
local sub = string.sub
local tostring = tostring
local type = type
local unpack = unpack
--[==[
Loaders for functions in other modules, which overwrite themselves with the target function when called. This ensures modules are only loaded when needed, retains the speed/convenience of locally-declared pre-loaded functions, and has no overhead after the first call, since the target functions are called directly in any subsequent calls.]==]
local function check_ancestor(...)
check_ancestor = require(etymology_module).check_ancestor
return check_ancestor(...)
end
local function find_templates(...)
find_templates = require(template_parser_module).find_templates
return find_templates(...)
end
local function format_categories(...)
format_categories = require(utilities_module).format_categories
return format_categories(...)
end
local function full_link(...)
full_link = require(links_module).full_link
return full_link(...)
end
local function get_lang(...)
get_lang = require(languages_module).getByCode
return get_lang(...)
end
local function get_link_page(...)
get_link_page = require(links_module).get_link_page
return get_link_page(...)
end
local function get_section(...)
get_section = require(pages_module).get_section
return get_section(...)
end
local function language_anchor(...)
language_anchor = require(anchors_module).language_anchor
return language_anchor(...)
end
local function process_params(...)
process_params = require(parameters_module).process
return process_params(...)
end
local function split(...)
split = require(string_utilities_module).split
return split(...)
end
local function templatestyles(...)
templatestyles = require(templatestyles_module)
return templatestyles(...)
end
local function trim(...)
trim = require(string_utilities_module).trim
return trim(...)
end
--[==[
Loaders for objects, which load data (or some other object) into some variable, which can then be accessed as "foo or get_foo()", where the function get_foo sets the object to "foo" and then returns it. This ensures they are only loaded when needed, and avoids the need to check for the existence of the object each time, since once "foo" has been set, "get_foo" will not be called again.]==]
local content_page
local function is_content_page()
content_page, is_content_page = require(pages_module).is_content_page(mw.title.getCurrentTitle()), nil
return content_page
end
local page_data
local function get_page_data()
page_data, get_page_data = mw.loadData(headword_data_module).page, nil
return page_data
end
local frame
-- Get a language object.
local function getLang(code)
return get_lang(code, nil, true) or require(languages_errorgetby_module).code(code, true, true)
end
-- Normalize the language so that special handling of Chinese is accounted for.
-- This is everything in the Sinitic family which isn't a creole, pidgin or mixed language.
local function getNormLang(lang)
if lang:inFamily("zhx") and not lang:inFamily("crp", "qfa-mix") then
return get_lang("zh")
else
return lang
end
end
-- Given an etymon param, return its parts.
local function getParts(templateLang, etymonParam)
local etymonLang, etymonLangcode, etymonPage, etymonId
local parts = split(etymonParam, ">", true)
local n = #parts
for i = 1, n do
parts[i] = trim(parts[i])
end
-- FIXME: this doesn't work properly if nested templates output HTML tags, which is likely to cause bugs that are hard for ordinary users to fix.
if n == 2 then
-- Assume language is the same as the template call if none is provided.
etymonLang, etymonPage, etymonId = templateLang, unpack(parts)
else
etymonLangcode, etymonPage, etymonId = unpack(parts)
etymonLang = getLang(etymonLangcode)
end
return etymonLang, etymonPage, etymonId
end
local argsOf = {}
local disambiguationCount = {}
local function scrapePage(etymonPage, etymonTitle, key, etymonLang, etymonId, redirectedFrom)
local content = etymonTitle:getContent()
if content == nil then
argsOf[key] = "redlink"
return
end
-- Search only the relevant L2 entry, unless it's a redirect, in which case search the whole page.
local redirectTarget = etymonTitle.redirectTarget
if not redirectTarget then
content = get_section(content, etymonLang:getFullName(), 2)
if content == nil then
argsOf[key] = "missing"
return
end
end
local etymonLangcode = etymonLang:getFullCode()
local L2_key = etymonLangcode .. ">" .. etymonPage
-- Search for the template on the page (even if this is a redirect page).
-- FIXME: mw.uri.anchorEncode on IDs. Not possible to implement until ">" syntax is fixed (see comment in getParts).
for template in find_templates(content) do
if template:get_name() == "etymon" then
local templateArgs = template:get_arguments()
if templateArgs[1] == etymonLangcode then
argsOf[L2_key .. ">" .. templateArgs["id"]] = templateArgs
disambiguationCount[L2_key] = (disambiguationCount[L2_key] or 0) + 1
end
end
end
if redirectedFrom and disambiguationCount[L2_key] then
disambiguationCount[redirectedFrom] = (disambiguationCount[redirectedFrom] or 0) + disambiguationCount[L2_key]
end
-- If scraping produced a result, there's nothing left to do.
if argsOf[key] then
return
-- Else if we've already followed a redirect and still found nothing, record the template as missing.
elseif redirectedFrom then
argsOf[key] = "missing"
return
end
-- Check if the page is a redirect, and if not record the template as missing.
if not redirectTarget then
argsOf[key] = "missing"
return
end
-- Otherwise, try again with the redirect target.
etymonPage = redirectTarget.prefixedText
scrapePage(etymonPage, redirectTarget, L2_key .. ">" .. etymonId, etymonLang, etymonId, L2_key)
-- Record the value as the same as the redirect's.
argsOf[key] = argsOf[etymonLangcode .. ">" .. etymonPage .. ">" .. etymonId]
end
-- Given an etymon, scrape the page and get its parameters.
-- This function returns either: a table of the params, "missing", "redlink", or "nolink"
local function getArgs(templateLang, etymonParam)
-- Get normalized parts of the etymon parameter.
local etymonLang, etymonPage, etymonId = getParts(templateLang, etymonParam)
-- "?" is a special value that unlinks the page. TODO: Figure this out...
if etymonId == "?" then
return "nolink"
end
-- If multiple terms are linked like A//B, only look at A.
etymonPage = match(etymonPage, "^(.-)//") or etymonPage
etymonPage = get_link_page(etymonPage, etymonLang)
etymonLang = getNormLang(etymonLang)
-- Find the parameters by scraping etymonPage.
-- Store data in the argsOf table to save time in case the same etymon is accessed again.
-- The key is a normalized version of etymonParam.
local key = etymonLang:getFullCode() .. ">" .. etymonPage .. ">" .. etymonId
if argsOf[key] == nil then
local etymonTitle = new_title(etymonPage)
if not etymonTitle then
-- This shouldn't happen: all unsupported titles should be resolved at this stage.
error("Invalid page title \"" .. etymonPage .. "\" encountered.")
end
scrapePage(etymonPage, etymonTitle, key, etymonLang, etymonId)
end
return argsOf[key]
end
-- [tag]: {abbreviation, label glossary anchor, start text, start text plus, middle text, forms groups}
-- Note: the keywords `afeq`, `conf`, and `unc` are also recognized, but do not use this dictionary.
-- Please do not add any new keywords without discussion or this list will get extremely unwieldy.
-- If we decide to add keywords for each thing I will have to figure out a systematic way to organize them.
local keywordDict = {
["from"] = {false, false, "From", "From", "from", false, false},
["inh"] = {false, false, "From", "[[Appendix:Glossary#inherited|Inherited]] from", "from", false},
["af"] = {false, false, "From", "From", "from", true},
["blend"] = {"blend.", "blend", "Blend of", "[[Appendix:Glossary#blend|Blend]] of", "a blend of", true},
["bor"] = {"bor.", "borrowing", "Borrowed from", "[[Appendix:Glossary#borrowing|Borrowed]] from", "borrowed from", false},
["lbor"] = {"lbor.", "learned_borrowing", "Learned borrowing from", "[[Appendix:Glossary#learned_borrowing|Learned borrowing]] from", "borrowed from", false},
["obor"] = {"obor.", "orthographic_borrowing", "Orthographic borrowing from", "[[Appendix:Glossary#orthographic_borrowing|Orthographic borrowing]] from", "borrowed from", false},
["slbor"] = {"slbor.", "semi-learned_borrowing", "Semi-learned borrowing from", "[[Appendix:Glossary#semi-learned_borrowing|Semi-learned borrowing]] from", "borrowed from", false},
["der"] = {"der.", "derived_terms", "Derived from", "[[Appendix:Glossary#derived_terms|Derived]] from", "from", false},
["calque"] = {"calq.", "calque", "Calque of", "[[Appendix:Glossary#calque|Calque]] of", "a calque of", false},
["sl"] = {"sl.", "semantic loan", "Semantic loan of", "[[Appendix:Glossary#semantic_loan|Semantic loan]] of", "a semantic loan of", false},
["bf"] = {"bf.", "back-formation", "Back-formation from", "[[Appendix:Glossary#Back-formation|Back-formation]] from", "a back-formation from", false},
["translit"] = {"translit.", "transliteration", "Transliteration", "[[Appendix:Glossary#transliteration|Transliteration]] of", "borrowed from", false},
["vrd"] = {"vrd.", "vṛddhi derivative", "Vṛddhi derivative of", "[[vṛddhi|Vṛddhi]] derivative of", "a vṛddhi derivative of", false},
["influence"] = {"influ.", "contamination", "", "", "", false}
}
-- This function takes an etymon and recursively builds a tree to display in an entry.
local function etyTree(currTitle, lang, args, alreadySeen, isTopLevel, isUncertain, label)
local treeWidth = 0
local treeHeight = 0
local subtree, subtreeHeight, subtreeWidth, etymonLang, etymonPage, etymonArgs
local subtrees = {}
local currId = ""
if type(args) == "table" then
currId = args["id"]
end
local key = getNormLang(lang):getFullCode() .. ">" .. get_link_page(currTitle, lang) .. ">" .. currId
local derType, confidence, ignoreEtymons = "from", "conf", false
-- Only recurse when an etymon has params and was not included in the tree previously.
if type(args) == "table" and alreadySeen[key] == nil then
local i, templateLang = 1, getLang(args[1])
-- Add the page to alreadySeen, which keeps track of what's already been added to the tree and the depth reached.
alreadySeen[key] = true
-- Loop over each parameter in the current template, starting from 2.
while true do
i = i + 1
local param = args[i]
if param == nil then
break
elseif find(param, ">", nil, true) and not ignoreEtymons then
etymonLang, etymonPage = getParts(templateLang, param)
-- Scrape the page and get the parameters.
etymonArgs = getArgs(templateLang, param)
-- Recurse into the etymon and append its tree to the list of subtrees.
subtree, subtreeHeight, subtreeWidth = etyTree(etymonPage, etymonLang, etymonArgs, alreadySeen, false, confidence == "unc", derType)
insert(subtrees, subtree)
treeHeight = max(treeHeight, subtreeHeight)
treeWidth = treeWidth + subtreeWidth
else
-- Reached a keyword.
if param == "conf" or param == "unc" then
confidence = param
elseif keywordDict[param] ~= nil then
ignoreEtymons = false
confidence = "conf"
derType = param
else
ignoreEtymons = true
end
end
end
end
-- Create link.
local link = "<span style=\"display:inline-block\" class=\"etyl\">" .. lang:getCanonicalName() .. "</span> <span style=\"display:inline-block\">"
if isTopLevel then
link = link .. full_link({lang = lang, alt = "'''" .. currTitle .. "'''"}, "term")
elseif currId == "" then
link = link .. full_link({lang = lang, term = currTitle}, "term")
else
link = link .. full_link({lang = lang, term = currTitle, id = currId}, "term")
end
link = link .. "</span>"
-- Create tree.
local tree = ""
if #subtrees == 1 then
-- Add long top connector.
tree = tree .. "<span style=\"position:relative;height:20px;border-right:2px solid var(--wikt-palette-grey,#9e9e9e)\"></span>"
elseif #subtrees >= 2 then
--Add short top connector.
tree = tree .. "<span style=\"position:relative;height:10px;border-right:2px solid var(--wikt-palette-grey,#9e9e9e)\"></span>"
end
--Create term block.
tree = tree .. "<div style=\"position:relative;text-align:center;padding:5px 10px;background:var(--wikt-palette-beige,#fffbf2);color:inherit;border:1px solid var(--wikt-palette-lightgrey,#ccc);border-radius:4px\">" .. link
-- Add derivation and uncertainty labels.
-- TODO: make the CSS less horrible.
if (label ~= "" and keywordDict[label][1] ~= false) or isUncertain then
tree = tree .. "<span style=\"z-index:1;position:absolute;transform:translate(-50%);top:calc(100% + 5px);left:50%;border-radius:2px;background:var(--wikt-palette-cyan,#eaffff);color:inherit;font-size:12px;height:10px;line-height:10px\">"
if label ~= "" and keywordDict[label][1] ~= false then
tree = tree .. "[[Appendix:Glossary#" .. keywordDict[label][2] .. "|<abbr title=\"" .. gsub(keywordDict[label][2], "_", " ") .. "\" style=\"color:var(--wikt-palette-black,#202122);font-style:italic;text-decoration:none\">" .. keywordDict[label][1] .. "</abbr>]]"
if isUncertain then
-- Add uncertainty label next to the derivation label.
tree = tree .. "<abbr title=\"uncertain\" style=\"position:absolute;top:50%;transform:translate(0,-48%);left:calc(100% + 2px);font-size:10px;border-radius:2px;background:var(--wikt-palette-pink,#ffe0f0);color:inherit;padding:1px 2px;font-weight:bold;text-decoration:none\">?</abbr>"
end
elseif isUncertain then
-- Add uncertainty label in the middle.
tree = tree .. "<abbr title=\"uncertain\" style=\"position:absolute;top:50%;left:50%;transform:translate(calc(-50% - 1px),-50%);font-size:10px;border-radius:2px;background:var(--wikt-palette-pink,#ffe0f0);color:inherit;padding:1px 2px;font-weight:bold;text-decoration:none\">?</span>"
end
tree = tree .. "</span>"
end
tree = tree .. "</div>"
-- Append subtrees.
local n_subtrees = #subtrees
if n_subtrees == 1 then
tree = subtrees[1] .. tree
elseif n_subtrees >= 2 then
local i, subtreeString = 0, ""
while true do
i = i + 1
local v = subtrees[i]
if v == nil then
break
elseif i == 1 then
-- Add left connector.
v = v .. "<span style=\"align-self:start;left:50%;width:calc(50% + 0.25em);height:10px;position:relative;border-bottom:2px solid var(--wikt-palette-grey,#9e9e9e);border-left:2px solid var(--wikt-palette-grey,#9e9e9e);border-bottom-left-radius:4px\"></span>"
elseif i == n_subtrees then
-- Add right connector.
v = v .. "<span style=\"align-self:end;right:50%;width:calc(50% + 0.25em);height:10px;position:relative;border-bottom:2px solid var(--wikt-palette-grey,#9e9e9e);border-right:2px solid var(--wikt-palette-grey,#9e9e9e);border-bottom-right-radius:4px\"></span>"
else
-- Add a short bottom connector and middle connector.
v = v .. "<span style=\"position:relative;height:10px;border-right:2px solid var(--wikt-palette-grey,#9e9e9e)\"></span><span style=\"position:relative;width:calc(100% + 0.5em);border-bottom:2px solid var(--wikt-palette-grey,#9e9e9e)\"></span>"
end
-- Add column div.
v = "<div style=\"display:flex;flex-direction:column;align-items:center\">" .. v .. "</div>"
subtreeString = subtreeString .. v
end
tree = "<div style=\"position:relative;display:flex;column-gap:0.5em;align-items:end\">" .. subtreeString .. "</div>" .. tree
else
--Reached a leaf node.
treeWidth = treeWidth + 1
end
-- Add outer divs.
if isTopLevel then
tree = "<div style=\"width:fit-content;margin:auto;padding:0.5em;display:flex;flex-direction:column;align-items:center\">" .. tree .. "</div>"
tree = "<div class=\"etytree NavFrame\" data-etytree-height=\"" .. treeHeight + 1 .. "\" data-etytree-width=\"" .. treeWidth .. "\"><div class=\"NavHead\" style=\"background:var(--wikt-palette-lightergrey,#eeeeee);color:inherit\"><div style=\"width:25em\">Etymology tree</div></div><div class=\"NavContent\" style=\"overflow:auto\">" .. tree .. "</div></div>"
end
return tree, treeHeight + 1, treeWidth
end
-- This function takes an etymon and generates some text to display in an entry.
-- Currently, it is only able to handle simple combinations of parameters.
local function etyText(title, lang, args, usePlusTemplates, maxDepth)
local text = ""
local depth = 1
local alreadyWritten = {}
local key, currLang, group, groupType, groupConfidence, confidence, derType, foundGroup, complexParams, ignoreEtymons, etymonLang, etymonTitle, etymonId, templateLang
-- Loop and continuously expand the sentence until we reach the end of the chain.
while not maxDepth or depth <= maxDepth do
group, groupType, groupConfidence, confidence, derType, foundGroup, complexParams, ignoreEtymons, currLang = {}, "from", "conf", "conf", "from", false, false, false, lang
key = getNormLang(lang):getFullCode() .. ">" .. get_link_page(title, lang) .. ">" .. args["id"]
templateLang = getLang(args[1])
-- Stop if we encounter an already-seen term.
if alreadyWritten[key] ~= nil then
break
end
alreadyWritten[key] = true
local i = 1 -- Iterate from 2.
while true do
i = i + 1
local param = args[i]
if param == nil then
break
elseif find(param, ">", nil, true) and not ignoreEtymons then
-- The text should only continue if `args` is either (not including `influence` or `afeq` etymons):
-- A single etymon, or single `af` group. Otherwise the parameters are too "complex" and are rejected.
-- TODO: add smarter handling for complex parameters.
if foundGroup or (#group == 1 and not keywordDict[derType][6]) then
complexParams = true
break
end
groupType = derType
if confidence == "unc" then
groupConfidence = "unc"
end
insert(group, param)
else
-- Reached a keyword.
if param == "unc" then
confidence = param
elseif param == "afeq" or param == "influence" then
ignoreEtymons = true
if #group == 1 then
foundGroup = true
end
else
ignoreEtymons = false
confidence = "conf"
derType = param
if #group == 1 then
foundGroup = true
end
end
end
end
if complexParams or #group == 0 then
break
end
if #group == 1 then
args = getArgs(templateLang, group[1])
end
if text == "" then
-- Start the sentence.
if groupConfidence == "conf" and not usePlusTemplates then
text = keywordDict[groupType][3]
elseif groupConfidence == "conf" and usePlusTemplates then
text = keywordDict[groupType][4]
else
text = "Possibly " .. keywordDict[groupType][5]
end
else
-- Add a phrase onto the sentence.
if groupConfidence == "conf" then
text = text .. ", " .. keywordDict[groupType][5]
else
text = text .. ", possibly " .. keywordDict[groupType][5]
end
end
-- Add the links.
for i = 1, #group do
etymonLang, etymonTitle, etymonId = getParts(templateLang, group[i])
--Make sure ID exists prior to linking to it.
if type(getArgs(templateLang, group[i])) ~= "table" then
etymonId = nil
end
if etymonLang:getCanonicalName() ~= currLang:getCanonicalName() then
group[i] = etymonLang:makeWikipediaLink() .. " " .. full_link({lang = etymonLang, term = etymonTitle, id = etymonId}, "term")
currLang = etymonLang
else
group[i] = full_link({lang = etymonLang, term = etymonTitle, id = etymonId}, "term")
end
end
text = text .. " " .. concat(group, " + ")
depth = depth + 1
if #group >= 2 then
break
end
lang = etymonLang
title = etymonTitle
if type(args) ~= "table" then
break
end
end
-- Add a period at the end of the sentence.
if text ~= "" then
text = text .. "."
end
return text
end
-- This function take an etymon and recursively generates categories to add to the entry.
-- Currently the behaviour tries to emulate existing templates including {{dercat}}.
-- More specific and useful categories are planned pending consensus (e.g. take confidence into account).
local function etyCategories(title, langName, args, passedThroughOtherLanguage, inInhChain, categories, seen)
local etymonLang, categoryEtymonTitle, etymonTitle, normTitle, etymonId, etymonLangName, etymonNormLangName, etymonArgs, key, L2_key, etymonPassedThroughOtherLanguage, etymonInInhChain, categoryName
local templateLang, currGroupLength, derType, isTopLevel = getLang(args[1]), 0, "from"
if categories == nil then
categories, isTopLevel = {}, true
end
local i = 1 -- Iterate from 2.
while true do
i = i + 1
local param = args[i]
if param == nil then
break
elseif find(param, ">", nil, true) then
currGroupLength = currGroupLength + 1
etymonLang, etymonTitle, etymonId = getParts(templateLang, param)
normTitle = get_link_page(etymonTitle, etymonLang)
L2_key = getNormLang(etymonLang):getFullCode() .. ">" .. normTitle
key = L2_key .. ">" .. etymonId
etymonLangName = etymonLang:getCanonicalName()
etymonNormLangName = getNormLang(etymonLang):getFullName()
etymonInInhChain = inInhChain and (derType == "from" or derType == "inh")
etymonPassedThroughOtherLanguage = passedThroughOtherLanguage or langName ~= etymonNormLangName
etymonArgs = getArgs(templateLang, param)
-- FIXME: this should use :getCanonicalName() for the target language name and :getDisplayForm() for the source language name. Currently uses :getCanonicalName() for both.
if isTopLevel then
--Add a maintenance category if an invalid ID is provided.
if etymonArgs == "missing" or etymonArgs == "redlink" then
if content_page == nil and is_content_page() or content_page then
categories[langName .. " entries referencing etymons with invalid IDs"] = true
else
categories["Entries referencing etymons with invalid IDs/hidden"] = true
end
end
-- Add borrowing categories at the top level only.
if derType == "bor" or derType == "lbor" or derType == "slbor" then
categories[langName .. " terms borrowed from " .. etymonLangName] = true
end
if derType == "lbor" then
categories[langName .. " learned borrowings from " .. etymonLangName] = true
elseif derType == "calque" then
categories[langName .. " terms calqued from " .. etymonLangName] = true
elseif derType == "sl" then
categories[langName .. " semantic loans from " .. etymonLangName] = true
elseif derType == "slbor" then
categories[langName .. " semi-learned borrowings from " .. etymonLangName] = true
elseif derType == "translit" then
categories[langName .. " transliterations of " .. etymonLangName .. " terms"] = true
elseif derType == "bf" then
categories[langName .. " back-formations"] = true
elseif derType == "blend" then
categories[langName .. " blends"] = true
elseif derType == "vrd" then
categories[langName .. " vrddhi derivatives"] = true
elseif derType == "obor" then
categories[langName .. " orthographic borrowings from " .. etymonLangName] = true
end
end
-- Add basic derivation categories.
if etymonPassedThroughOtherLanguage and langName == etymonNormLangName then
categories[langName .. " terms borrowed back into " .. etymonLangName] = true
end
if etymonNormLangName ~= langName then
categories[langName .. " terms derived from " .. etymonLangName] = true
end
if etymonNormLangName ~= langName and etymonInInhChain then
categories[langName .. " terms inherited from " .. etymonLangName] = true
end
categoryEtymonTitle = normTitle
if sub(categoryEtymonTitle, 1, 15) == "Reconstruction:" then
categoryEtymonTitle = gsub(categoryEtymonTitle, "^Reconstruction:[^/]+/", "*")
end
-- Add affix categories.
local etymonArgsType = type(etymonArgs)
if etymonArgsType == "table" and etymonArgs["pos"] ~= nil and (derType == "af" or "derType" == "afeq") and not etymonPassedThroughOtherLanguage then
-- Ugly duplicated code...
if (etymonArgs["pos"] == "prefix" or etymonArgs["pos"] == "suffix" or etymonArgs["pos"] == "interfix" or etymonArgs["pos"] == "infix") then
if etymonArgs["pos"] == "prefix" then
categoryName = langName .. " terms prefixed with " .. categoryEtymonTitle
elseif etymonArgs["pos"] == "suffix" then
categoryName = langName .. " terms suffixed with " .. categoryEtymonTitle
elseif etymonArgs["pos"] == "interfix" then
categoryName = langName .. " terms interfixed with " .. categoryEtymonTitle
elseif etymonArgs["pos"] == "infix" then
categoryName = langName .. " terms infixed with " .. categoryEtymonTitle
end
-- Add ID if necessary for disambiguation.
if disambiguationCount[L2_key] > 1 then
categoryName = categoryName .. " (" .. etymonId .. ")"
end
categories[categoryName] = true
end
end
-- Add root categories.
if etymonArgsType == "table" and etymonArgs["pos"] == "root" then
if etymonPassedThroughOtherLanguage then
categoryName = langName .. " terms derived from the " .. etymonLangName .. " root " .. categoryEtymonTitle
else
categoryName = langName .. " terms belonging to the root " .. categoryEtymonTitle
end
-- Add ID if necessary for disambiguation.
if disambiguationCount[L2_key] > 1 then
categoryName = categoryName .. " (" .. etymonId .. ")"
end
categories[categoryName] = true
end
-- Recurse into the etymon.
if (
not (derType == "afeq" or derType == "influence") and
(seen == nil or seen[key] == nil) and
etymonArgsType == "table"
) then
if seen == nil then
seen = {}
end
seen[key] = true
etyCategories(title, langName, etymonArgs, etymonPassedThroughOtherLanguage, etymonInInhChain, categories, seen)
end
elseif not (param == "unc" or param == "conf") then
derType = param
currGroupLength = 0
end
end
return categories
end
-- TODO: this should all be integrated into etyCategories at the top-level pass.
local function paramsSanityCheck(lang, params, id, title, pos)
if mw.ustring.len(id) < 2 then
error("The `id` parameter must have at least two characters. See the [[Template:etymon/documentation#Parameters|documentation]] for more details.")
elseif id == title or id == (page_data or get_page_data()).pagename then
error("The `id` parameter must not be the same as the page title. Be more creative. See the [[Template:etymon/documentation#Parameters|documentation]] for more details.")
end
if pos and pos ~= "prefix" and pos ~= "suffix" and pos ~= "interfix" and pos ~= "infix" and pos ~= "root" then
error("Unknown value provided for `pos`. Allowed values are: prefix, suffix, interfix, infix, root.")
end
local i, currKeyword, singleAfParam, paramLang = 0, "from", "not in group"
while true do
i = i + 1
local param = params[i]
if param == nil then
break
elseif find(param, ">", nil, true) then
--In this case, `templateLang` is the same as `lang` because we are at the top level.
paramLang = getParts(lang, param)
if currKeyword == "from" then
if paramLang:getFullCode() ~= lang:getFullCode() then
error("Error: " .. param .. " is associated with `from` (same-language derivation) but is of language `" .. paramLang:getFullCode() .. "`, which does not match the current entry language (`" .. lang:getFullCode() .. "`); see the [[Template:etymon/documentation#Derivation keywords|documentation]] for more details.")
end
elseif currKeyword == "inh" then
check_ancestor(lang, paramLang)
elseif keywordDict[currKeyword] and keywordDict[currKeyword][6] then
singleAfParam = singleAfParam ~= "not in group" and "found group" or param
elseif (currKeyword == "bor" or currKeyword == "lbor" or currKeyword == "obor" or currKeyword == "slbor" or currKeyword == "der" or currKeyword == "calque" or currKeyword == "sl") and (paramLang:getCode() == lang:getCode()) then
error("Error: " .. param .. " is associated with `" .. currKeyword .. "` but has the same language (`" .. paramLang:getCode() .. "`) as the current entry; see the [[Template:etymon/documentation#Derivation keywords|documentation]] for more details.")
end
elseif param ~= "unc" and param ~= "conf" and param ~= "afeq" and keywordDict[param] == nil then
error("Received unknown keyword: " .. param)
elseif param ~= "unc" and param ~= "conf" then
currKeyword = param
if singleAfParam == "found group" then
singleAfParam = "not in group"
end
end
end
if singleAfParam ~= "not in group" and singleAfParam ~= "found group" then
error("Detected `af` or group containing only a single etymon: `" .. singleAfParam .. "`; note that `af` and `afeq` groups must have at least two etymons. See the [[Template:etymon/documentation#Derivation keywords|documentation]] for more details.")
end
end
function export.main(_frame)
frame = _frame
-- Process argument input.
local args = process_params(frame:getParent().args, mw.loadData(parameters_data_module).etymon)
local lang = args[1]
-- Store non-numeric parameters as locals, then treat the main numeric list as `args`.
local id = args["id"]
local title = args["title"]
local text = args["text"]
local tree = args["tree"]
local exnihilo = args["exnihilo"]
local pos = args["pos"]
args = args[2]
-- The `title` parameter is used for overriding the page title.
if title == nil then
-- Get the canonical pagename.
title = (page_data or get_page_data()).pagename
-- Determine if current term is reconstructed.
if page_data.namespace == "Reconstruction" then
title = "*" .. title
end
end
paramsSanityCheck(lang, args, id, title, pos)
-- Add the langcode and `id`, to match the format of scraped parameters.
insert(args, 1, lang:getCode())
args["id"] = id
argsOf[args[1] .. ">" .. title .. ">" .. id] = args
-- Add anchor to output.
local output = {tostring(html_create("ul")
:attr("id", language_anchor(lang, id))
:allDone()
)}
local langName, categories = lang:getFullName(), {}
if content_page == nil and is_content_page() or content_page then
local categorySet = etyCategories(title, langName, args, false, true)
for category in next, categorySet do
insert(categories, category)
end
end
-- Special categories.
if exnihilo then
insert(categories, langName .. " terms coined ex nihilo")
end
-- Insert tree.
if tree then
insert(output, templatestyles("Module:etymon/styles.css"))
insert(output, (etyTree(title, lang, args, {}, true, false, "")))
insert(categories, langName .. " entries with etymology trees")
end
-- Insert text.
if text then
insert(categories, langName .. " entries with etymology texts")
end
if text == "++" then
insert(output, etyText(title, lang, args, true, false))
elseif text == "+" then
insert(output, etyText(title, lang, args, true, 1))
elseif text == "-" then
insert(output, etyText(title, lang, args, false, 1))
elseif text ~= nil then
insert(output, etyText(title, lang, args, false, false))
end
if #categories > 0 then
insert(output, format_categories(categories, lang))
end
return concat(output)
end
return export