This module implements the template {{etymon}}
.
local export = {}
local m_links = require("Module:links")
local m_string_utilities = require("Module:string utilities")
local page_data = mw.loadData("Module:headword/data").page
local check_ancestor = require("Module:etymology").check_ancestor
local concat = table.concat
local find = string.find
local find_templates = require("Module:template parser").find_templates
local format_categories = require("Module:utilities").format_categories
local full_link = m_links.full_link
local get_lang = require("Module:languages").getByCode
local get_link_page = m_links.get_link_page
local get_section = require("Module:pages").get_section
local gsub = string.gsub
local html_create = mw.html.create
local insert = table.insert
local ipairs = ipairs
local language_anchor = require("Module:anchors").language_anchor
local max = math.max
local new_title = mw.title.new
local pairs = pairs
local process_params = require("Module:parameters").process
local split = m_string_utilities.split
local sub = string.sub
local tostring = tostring
local trim = m_string_utilities.trim
local type = type
local unpack = unpack
local frame
-- Get a language object.
local function getLang(code)
return get_lang(code, nil, true) or require("Module:languages/errorGetBy").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 = trim(parts)
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 = "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 = "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 == etymonLangcode then
argsOf] = templateArgs
disambiguationCount = (disambiguationCount or 0) + 1
end
end
end
if redirectedFrom and disambiguationCount then
disambiguationCount = (disambiguationCount or 0) + disambiguationCount
end
-- If scraping produced a result, there's nothing left to do.
if argsOf then
return
-- Else if we've already followed a redirect and still found nothing, record the template as missing.
elseif redirectedFrom then
argsOf = "missing"
return
end
-- Check if the page is a redirect, and if not record the template as missing.
if not redirectTarget then
argsOf = "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 = argsOf
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 = string.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 == 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
end
-- : {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 = {
= {false, false, "From", "From", "from", false, false},
= {false, false, "From", "] from", "from", false},
= {false, false, "From", "From", "from", true},
= {"blend.", "blend", "Blend of", "] of", "a blend of", true},
= {"bor.", "borrowing", "Borrowed from", "] from", "borrowed from", false},
= {"lbor.", "learned_borrowing", "Learned borrowing from", "] from", "borrowed from", false},
= {"obor.", "orthographic_borrowing", "Orthographic borrowing from", "] from", "borrowed from", false},
= {"slbor.", "semi-learned_borrowing", "Semi-learned borrowing from", "] from", "borrowed from", false},
= {"der.", "derived_terms", "Derived from", "] from", "from", false},
= {"calq.", "calque", "Calque of", "] of", "a calque of", false},
= {"sl.", "semantic loan", "Semantic loan of", "] of", "a semantic loan of", false},
= {"bf.", "back-formation", "Back-formation from", "] from", "a back-formation from", false},
= {"translit.", "transliteration", "Transliteration", "] of", "borrowed from", false},
= {"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
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 == nil then
local templateLang = getLang(args)
-- Add the page to alreadySeen, which keeps track of what's already been added to the tree and the depth reached.
alreadySeen = true
-- Loop over each parameter in the current template.
for i, param in ipairs(args) do
if i > 1 and find(param, ">") 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
elseif i > 1 then
-- Reached a keyword.
if param == "conf" or param == "unc" then
confidence = param
elseif keywordDict ~= 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 ~= 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 ~= false then
tree = tree .. " .. "|<abbr title=\"" .. gsub(keywordDict, "_", " ") .. "\" style=\"color:var(--wikt-palette-black,#202122);font-style:italic;text-decoration:none\">" .. keywordDict .. "</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.
if #subtrees == 1 then
tree = subtrees .. tree
elseif #subtrees >= 2 then
local subtreeString = ""
for i,v in ipairs(subtrees) do
if 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 == #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
templateLang = getLang(args)
-- Stop if we encounter an already-seen term.
if alreadyWritten ~= nil then
break
end
alreadyWritten = true
for i, param in ipairs(args) do
if i > 1 and find(param, ">") 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) then
complexParams = true
break
end
groupType = derType
if confidence == "unc" then
groupConfidence = "unc"
end
insert(group, param)
elseif i > 1 then
-- 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)
end
if text == "" then
-- Start the sentence.
if groupConfidence == "conf" and not usePlusTemplates then
text = keywordDict
elseif groupConfidence == "conf" and usePlusTemplates then
text = keywordDict
else
text = "Possibly " .. keywordDict
end
else
-- Add a phrase onto the sentence.
if groupConfidence == "conf" then
text = text .. ", " .. keywordDict
else
text = text .. ", possibly " .. keywordDict
end
end
-- Add the links.
for i = 1,#group do
etymonLang, etymonTitle, etymonId = getParts(templateLang, group)
--Make sure ID exists prior to linking to it.
if type(getArgs(templateLang, group)) ~= "table" then
etymonId = nil
end
if etymonLang:getCanonicalName() ~= currLang:getCanonicalName() then
group = etymonLang:makeWikipediaLink() .. " " .. full_link({lang=etymonLang, term=etymonTitle, id=etymonId}, "term")
currLang = etymonLang
else
group = 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 categories = {}
local alreadySeenByEtyCategories = {}
local function etyCategories(title, lang, args, isTopLevel, passedThroughOtherLanguage, inInhChain)
local etymonLang, categoryEtymonTitle, etymonTitle, normTitle, etymonId, etymonLangName, etymonNormLangName, etymonArgs, key, L2_key, etymonPassedThroughOtherLanguage, etymonInInhChain, categoryName
local currGroupLength = 0
local derType = "from"
local templateLang = getLang(args)
local langName = lang:getFullName()
for i, param in ipairs(args) do
if i > 1 and find(param, ">") 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)
categoryEtymonTitle = normTitle
if sub(categoryEtymonTitle, 1, 15) == "Reconstruction:" then
categoryEtymonTitle = gsub(categoryEtymonTitle, "^Reconstruction:+%/", "*")
end
-- FIXME: this should use :getCanonicalName() for the target language name and :getDisplayForm() for the source language name. Currently uses :getCanonicalName() for both.
-- Add basic derivation categories.
if etymonPassedThroughOtherLanguage and langName == etymonNormLangName then
categories = true
end
if etymonNormLangName ~= langName then
categories = true
end
if etymonNormLangName ~= langName and etymonInInhChain then
categories = true
end
-- Add borrowing categories.
if isTopLevel then
if derType == "bor" or derType == "lbor" or derType == "slbor" then
categories = true
end
if derType == "lbor" then
categories = true
elseif derType == "calque" then
categories = true
elseif derType == "sl" then
categories = true
elseif derType == "slbor" then
categories = true
elseif derType == "translit" then
categories = true
elseif derType == "bf" then
categories = true
elseif derType == "blend" then
categories = true
elseif derType == "obor" then
categories = true
end
end
-- Add affix categories.
if type(etymonArgs) == "table" and etymonArgs ~= nil and (derType == "af" or "derType" == "afeq") and not etymonPassedThroughOtherLanguage then
-- Ugly duplicated code...
if (etymonArgs == "prefix" or etymonArgs == "suffix" or etymonArgs == "interfix" or etymonArgs == "infix") then
if etymonArgs == "prefix" then
categoryName = langName .. " terms prefixed with " .. categoryEtymonTitle
elseif etymonArgs == "suffix" then
categoryName = langName .. " terms suffixed with " .. categoryEtymonTitle
elseif etymonArgs == "interfix" then
categoryName = langName .. " terms interfixed with " .. categoryEtymonTitle
elseif etymonArgs == "infix" then
categoryName = langName .. " terms infixed with " .. categoryEtymonTitle
end
-- Add ID if necessary for disambiguation.
if disambiguationCount > 1 then
categoryName = categoryName .. " (" .. etymonId .. ")"
end
categories = true
end
end
-- Add root categories.
if type(etymonArgs) == "table" and etymonArgs == "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 > 1 then
categoryName = categoryName .. " (" .. etymonId .. ")"
end
categories = true
end
-- Recurse into the etymon.
if derType ~= "afeq" and derType ~= "influence" and alreadySeenByEtyCategories == nil and type(etymonArgs) == "table" then
alreadySeenByEtyCategories = true
etyCategories(title, lang, etymonArgs, false, etymonPassedThroughOtherLanguage, etymonInInhChain)
end
elseif i > 1 and param ~= "unc" and param ~= "conf" then
derType = param
currGroupLength = 0
end
end
if isTopLevel then
local output = {}
local sortkey = lang:makeSortKey(title)
for category, _ in pairs(categories) do
insert(output, "]")
end
return concat(output)
end
end
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 ] for more details.")
elseif id == title or id == page_data.pagename then
error("The `id` parameter must not be the same as the page title. Be more creative. See the ] 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 paramLang, paramTitle, paramId
local currKeyword = "from"
local singleAfParam = "not in group"
local output = ""
for _, param in ipairs(params) do
if find(param, ">") then
--In this case, `templateLang` is the same as `lang` because we are at the top level.
paramLang, paramTitle, paramId = getParts(lang, param)
-- Check for link errors.
full_link({lang=lang, term=paramTitle, id=paramId}, "term")
--Add a maintenance category if an invalid ID is provided.
if getArgs(lang, param) == "missing" or getArgs(lang, param) == "redlink" then
if page_data.namespace == "" or page_data.namespace == "Reconstruction" or page_data.namespace == "Appendix" then
output = "]"
else
output = "]"
end
end
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 ] for more details.")
end
elseif currKeyword == "inh" then
check_ancestor(lang, paramLang)
elseif keywordDict and keywordDict then
if singleAfParam == "not in group" then
singleAfParam = param
else
singleAfParam = "found group"
end
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 ] for more details.")
end
elseif param ~= "unc" and param ~= "conf" and param ~= "afeq" and keywordDict == 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 ] for more details.")
end
return output
end
function export.main(_frame)
frame = _frame
-- Process argument input.
local args = process_params(frame:getParent().args, mw.loadData("Module:parameters/data").etymon)
local lang = args
-- Store non-numeric parameters as locals, then treat the main numeric list as `args`.
local id = args
local title = args
local text = args
local tree = args
local exnihilo = args
local pos = args
args = args
-- The `title` parameter is used for overriding the page title.
if title == nil then
-- Get the canonical pagename.
title = page_data.pagename
-- Determine if current term is reconstructed.
if page_data.namespace == "Reconstruction" or lang:hasType("reconstructed") then
title = "*" .. title
end
end
local output = {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
argsOf .. ">" .. title .. ">" .. id] = args
-- Add anchor and categories to output.
insert(output, tostring(html_create("ul")
:attr("id", language_anchor(lang, id))
:allDone()
))
if page_data.namespace == "" or page_data.namespace == "Reconstruction" or page_data.namespace == "Appendix" then
insert(output, etyCategories(title, lang, args, true, false, true))
end
-- Special categories.
if exnihilo then
insert(output, format_categories({lang:getFullName() .. " terms coined ex nihilo"}, lang))
end
-- Insert tree.
if tree then
insert(output, frame:extensionTag("templatestyles", nil, {src="Module:etymon/styles.css"}))
insert(output, (etyTree(title, lang, args, {}, true, false, "")))
insert(output, format_categories({lang:getFullName() .. " entries with etymology trees"}, lang))
end
-- Insert text.
if text then
insert(output, format_categories({lang:getFullName() .. " entries with etymology texts"}, lang))
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
return concat(output)
end
return export