Module:roots
Appearance
- This module lacks a documentation subpage. Please create it.
- Useful links: subpage list • links • transclusions • testcases • sandbox
local export = {}
local force_cat = false -- for debugging
local M = require("Module:module loader").init({
require = {
str_utils = "Module:string utilities",
utilities = "Module:utilities",
links = "Module:links",
headword = "Module:headword",
languages = "Module:languages",
parameters = "Module:parameters",
inflection_table = "Module:inflection-table",
},
loadData = {
radicals = "Module:roots/data/radicals",
},
lazy = {
concat = function() return table.concat end,
insert = function() return table.insert end,
},
})
local UI_STRINGS = {
no_langcode = "Please provide a language code.",
invalid_langcode = "Invalid language code: %s",
not_a_table = "rootTable is not a table",
min_radicals = "Root must have at least %s %s.",
max_radicals = "Root must have at most %s %s.",
unrecognized_pos = "Unrecognized %s radical %s in %s",
unrecognized_mem = "Unrecognized radical %s in %s",
not_single_letter = "'%s' in the root '%s' should be a single letter.",
invalid_validation = "Unknown validation model: %s",
only_one_root = "There should be only one root.",
bad_root = "Invalid %s root '%s': %s",
wrong_separator = "Radicals should be separated by '%s'.",
root_vs_pagename = "Root is different from the pagename.",
move_to_appendix = "The root page should be in the Appendix namespace. Please move it to : [[%s]]",
appendix_subpage = "Appendix:%s roots/",
root_category = "%s terms belonging to the root %s%s",
derived_category = "%s terms derived from the %s root %s%s",
derived_language = "%s terms derived from %s",
root_heading = "\n! [[w:%s|%s]]",
sense_wrapper = "‎ <span class=\"gloss-content\">(%s)</span>",
article_semitic = "Semitic root",
article_generic = "Root (linguistics)",
}
local DEFAULT_LINK_TARGET = "bare"
local DEFAULT_HEADWORD = {}
local function split_root(root, separator)
if not separator then
return { root }
end
return mw.text.split(root, separator, true)
end
local function radicals_word(count)
return count == 1 and "radical" or "radicals"
end
function export.get_data_module_name(langcode)
return "Module:roots/data/" .. langcode
end
local function try_load_data(langcode)
local ok, data = pcall(mw.loadData, export.get_data_module_name(langcode))
if ok then
return data
end
return nil
end
function export.get_data(langcode)
return try_load_data(langcode)
end
local function get_lang_and_data(frame)
local code = frame.args["lang"]
if not code or code == "" then
error(UI_STRINGS.no_langcode)
end
local lang = M.languages.getByCode(code)
if not lang then
error(UI_STRINGS.invalid_langcode:format(code))
end
return lang, try_load_data(code) or {}
end
local function resolve_radicals(data)
if data.validation == "positional" or data.radicals then
return data.radicals
end
if data.shared_radicals then
return M.radicals[data.shared_radicals]
end
return nil
end
local function transliterate_root(root_string, romanization)
if not romanization then
return nil
end
local out = root_string
for _, pair in ipairs(romanization) do
out = M.str_utils.gsub(out, pair[1], pair[2])
end
return out
end
export.transliterate = transliterate_root
local function validate_root(rootTable, joined_root, data, radical_set)
if type(rootTable) ~= "table" then
return nil, UI_STRINGS.not_a_table
end
local len = #rootTable
if data.min_radicals and len < data.min_radicals then
return nil, UI_STRINGS.min_radicals:format(data.min_radicals, radicals_word(data.min_radicals))
end
if data.max_radicals and len > data.max_radicals then
return nil, UI_STRINGS.max_radicals:format(data.max_radicals, radicals_word(data.max_radicals))
end
if data.validation == "positional" then
for i, radical in ipairs(rootTable) do
local set, label
if i == 1 then
set, label = radical_set.initial, "initial"
elseif i == len then
set, label = radical_set.final, "final"
else
set, label = radical_set.medial, "medial"
end
if not set[radical] then
return nil, UI_STRINGS.unrecognized_pos:format(label, radical, joined_root)
end
end
elseif data.validation == "membership" then
for _, radical in ipairs(rootTable) do
if not radical_set[radical] then
return nil, UI_STRINGS.unrecognized_mem:format(radical, joined_root)
end
end
elseif data.validation == "single_letter" then
for _, radical in ipairs(rootTable) do
if mw.ustring.len(radical) ~= 1 then
return nil, UI_STRINGS.not_single_letter:format(radical, joined_root)
end
end
elseif data.validation ~= nil then
return nil, UI_STRINGS.invalid_validation:format(tostring(data.validation))
end
return true
end
local function link(term, alt, translit, id, lang)
if term == "" or term == "—" then
return term
end
return M.links.full_link({
term = term,
alt = alt ~= term and alt or nil,
tr = translit,
lang = lang,
id = id,
})
end
local function root_category(lang, source, root, qualifier)
local suffix = (qualifier and qualifier ~= "") and (" (" .. qualifier .. ")") or ""
if lang:getCode() == source:getCode() then
return UI_STRINGS.root_category:format(lang:getFullName(), root, suffix)
end
return UI_STRINGS.derived_category:format(
lang:getFullName(), source:getCanonicalName(), root, suffix)
end
local function check_root(source, root)
local data = try_load_data(source:getCode())
if not data then
return true
end
local bare = (root:gsub("^%*", ""))
local radicals = split_root(bare, data.separator)
if data.separator and #radicals == 1 and mw.ustring.len(bare) > 1 then
return nil, UI_STRINGS.wrong_separator:format(data.separator)
end
return validate_root(radicals, bare, data, resolve_radicals(data))
end
function export.get_link_page(lang, root)
local data = try_load_data(lang:getCode())
if not data or data.link_target ~= "appendix_subpage" then
return nil
end
export.assert_root(lang, root)
return UI_STRINGS.appendix_subpage:format(lang:getCanonicalName()) .. (root:gsub("^%*", ""))
end
function export.assert_root(source, root)
local ok, err = check_root(source, root)
if not ok then
error(UI_STRINGS.bad_root:format(source:getCanonicalName(), root, err))
end
return true
end
function export.get_categories(lang, source, roots, ids)
local categories = {}
ids = ids or {}
if lang:getCode() ~= source:getCode() then
M.insert(categories,
UI_STRINGS.derived_language:format(lang:getFullName(), source:getCanonicalName()))
end
for index, root in ipairs(roots) do
M.insert(categories, root_category(lang, source, root, ids[index]))
export.assert_root(source, root)
end
return categories
end
function export.categorize(lang, source, roots, ids, sort_key)
return M.utilities.format_categories(
export.get_categories(lang, source, roots, ids), lang, sort_key, nil, force_cat)
end
function export.show(frame)
local params = {
[1] = { required = true, type = "language" },
[2] = { required = true, type = "language" },
[3] = { required = true, list = true },
["id"] = { list = true, allow_holes = true },
["sort"] = {},
}
local args = M.parameters.process(frame:getParent().args, params)
return export.categorize(args[1], args[2], args[3], args.id, args.sort)
end
function export.root(frame)
local lang, data = get_lang_and_data(frame)
local radical_set = resolve_radicals(data)
local link_target = data.link_target or DEFAULT_LINK_TARGET
local output = {}
local categories = {}
local title = mw.title.getCurrentTitle()
local namespace = title.nsText
local params = {
[1] = { list = true },
["nocat"] = { type = "boolean" },
["plain"] = { type = "boolean" },
["notext"] = { type = "boolean" },
["sense"] = {},
}
if link_target == "bare" then
params["head"] = true
params["pagename"] = true
end
local args = M.parameters.process(frame:getParent().args, params)
local roots = args[1]
local roots_len = #roots
local plain = args["plain"]
if frame.args["plain"] then
plain = true
end
local separator = data.separator
local identity
local subpage = ""
if link_target == "appendix_subpage" then
subpage = UI_STRINGS.appendix_subpage:format(lang:getCanonicalName())
local fulltitle = title.fullText
fulltitle = M.str_utils.gsub(fulltitle, M.str_utils.gsub(subpage, "([^%w])", "%%%1"), "")
identity = fulltitle
else
identity = args["pagename"] or mw.loadData("Module:headword/data").pagename
end
local rootLetters = {}
if roots_len == 0 and not args["pagename"] and namespace == "Template" and data.preview_root then
M.insert(rootLetters, split_root(data.preview_root, separator))
elseif roots_len ~= 0 then
for _, root in ipairs(roots) do
M.insert(rootLetters, split_root(root, separator))
end
else
if link_target == "bare" then
local head = frame.args["head"]
if args["head"] ~= nil and identity ~= lang:stripDiacritics(args["head"]) then
error(UI_STRINGS.root_vs_pagename)
end
if head and args["head"] ~= nil then
identity = args["head"]
end
end
M.insert(rootLetters, split_root(identity, separator))
end
local joined_roots = {}
for i, rootLetter in ipairs(rootLetters) do
M.insert(joined_roots, M.concat(rootLetter, separator))
local ok, err = validate_root(rootLetter, joined_roots[i], data, radical_set)
if not ok then
error(err)
end
end
local sense = args["sense"]
local sense_formatted = ""
if sense ~= nil and sense ~= "" then
sense_formatted = UI_STRINGS.sense_wrapper:format(sense)
end
if identity == joined_roots[1] then
if link_target == "appendix_subpage" and namespace == "" then
error(UI_STRINGS.move_to_appendix:format(subpage .. joined_roots[1]))
end
if roots_len > 1 then
error(UI_STRINGS.only_one_root)
end
local headword_args = {
lang = lang,
pos_category = "roots",
categories = {},
heads = { identity },
nomultiwordcat = true,
}
local headword_opts = data.headword or DEFAULT_HEADWORD
if headword_opts.translits then
headword_args.translits = { transliterate_root(identity, data.romanization) }
end
if headword_opts.nolemmacat then
headword_args.nolemmacat = true
end
M.insert(output, M.headword.full_headword(headword_args))
if args["nocat"] then
return M.concat(output)
end
return M.concat(output) .. M.concat(categories)
else
local link_texts = {}
local term_counts = {}
for i, joined_root in ipairs(joined_roots) do
local link_text
if link_target == "appendix_subpage" then
link_text = subpage .. joined_root
else
link_text = joined_root
end
local translit = data.romanization and transliterate_root(joined_root, data.romanization) or nil
M.insert(link_texts, link(link_text, joined_root, translit, sense, lang) .. sense_formatted)
M.insert(categories, M.utilities.format_categories(
{ root_category(lang, lang, joined_root, sense) }, lang, nil, nil, force_cat))
M.insert(term_counts, mw.site.stats.pagesInCategory(
root_category(lang, lang, joined_root, sense), "pages"))
end
if args["nocat"] or plain then
if args["nocat"] then
return M.concat(link_texts, ", ")
end
return M.concat(link_texts, ", ") .. M.concat(categories)
else
local link_text_output = ""
for i, link_text in ipairs(link_texts) do
link_text_output = link_text_output ..
"\n|-\n| " .. link_text ..
"\n|-\n| [[:Category:" ..
root_category(lang, lang, joined_roots[i], sense) ..
"|" .. term_counts[i] .. " term" .. (term_counts[i] == 1 and "" or "s") .. "]]\n"
end
local wikicode = M.inflection_table.make_top{
title = "-",
palette = data.palette,
class = "floatright tr-alongside"
}
local article = lang:inFamily("sem") and UI_STRINGS.article_semitic or UI_STRINGS.article_generic
wikicode = wikicode .. UI_STRINGS.root_heading:format(article, #term_counts == 1 and "Root" or "Roots")
wikicode = wikicode .. link_text_output
wikicode = wikicode .. M.inflection_table.make_bottom{}
return wikicode .. M.concat(categories)
end
end
end
return export