Module:Alternative forms

local export = {} local m_links = require("Module:links") local m_languages = require("Module:languages")

local rsplit = mw.text.split

-- See if the language's dialectal data module has a label corresponding to the dialect argument. function export.getLabel(dialect, dialect_data) local data = dialect_data[dialect] or ( dialect_data.labels and dialect_data.labels[dialect] ) local alias_of = ( dialect_data.aliases and dialect_data.aliases[dialect] ) if not data then if alias_of then data = dialect_data[alias_of] or ( dialect_data.labels and dialect_data.labels[alias_of] ) end end if data then local display = data.display or dialect if data.appendix then dialect =  .. display ..  else local target = data.link dialect = target and  .. display ..  or display end end return dialect end

function export.make_dialects(raw, lang) local dialect_page = 'Module:'.. lang:getCode ..':Dialects' local dialect_info if raw[1] then dialect_info = mw.title.new(dialect_page).exists and mw.loadData(dialect_page) or false end

local dialects = {}

for _, dialect in ipairs(raw) do		table.insert(dialects, dialect_info and export.getLabel(dialect, dialect_info) or dialect) end

return dialects end

local function track(page) require("Module:debug/track")("alter/" .. page) end

-- Per-param modifiers, which can be specified either as separate parameters (e.g. t2=, pos3=) or as inline modifiers -- , , etc. The key is the name fo the parameter (e.g. "t", "pos") and the value is a table with -- elements as follows: -- * `extra_specs`: An optional table of extra key-value pairs to add to the spec used for parsing the parameter --                 when specified as a separate parameter (e.g. {type = "boolean"} for a Boolean parameter, or --                  {alias_of = "t"} for the "gloss" parameter, which is aliased to "t"), on top of the default, which --                 is {list = true, allow_holes = true}. -- * `convert`: An optional function to convert the raw argument into the form passed to Module:links. --             This function takes three parameters: (1) `arg` (the raw argument); (2) `inline` (true if we're --              processing an inline modifier, false otherwise); (4) `termno` (the logical index of the term being --              processed, starting from 1). -- * `item_dest`: The name of the key used when storing the parameter's value into the processed `term` or `termobj` --               object. Normally the same as the parameter's name. Different in the case of "t", where we store the --               gloss in "gloss", and "g", where we store the genders in "genders". -- * `outer`: If true, store the value into `termobj` rather than `term`. local param_mods = { t = { -- We need to store the t1=/t2= param and the  inline modifier into the "gloss" key of the parsed term, -- because that is what Module:links expects. item_dest = "gloss", },	gloss = { -- The `extra_specs` handles the fact that "gloss" is an alias of "t". extra_specs = {alias_of = "t"}, },	tr = {}, ts = {}, g = { -- We need to store the g1=/g2= param and the  inline modifier into the "genders" key of the parsed term, -- because that is what Module:links expects. item_dest = "genders", convert = function(arg, inline, termno) return rsplit(arg, ",") end, },	id = {}, alt = {}, q = { outer = true, },	qq = { outer = true, },	lit = {}, pos = {}, }

local function get_valid_prefixes local valid_prefixes = {} for param_mod, _ in pairs(param_mods) do		table.insert(valid_prefixes, param_mod) end table.sort(valid_prefixes) return valid_prefixes end

-- Main function for displaying alternative forms. Extracted out from the template-callable function so this can be -- called by other modules (in particular, Module:descendants tree). function export.display_alternative_forms(parent_args, pagename, include_dialect_tags, allow_self_link) local list_with_holes = { list = true, allow_holes = true } local params = { [1] = { required = true, default = "und" }, [2] = list_with_holes, ["sc"] = {}, }

for param_mod, param_mod_spec in pairs(param_mods) do		if not param_mod_spec.extra_specs then params[param_mod] = list_with_holes else local param_spec = mw.clone(list_with_holes) for k, v in pairs(param_mod_spec.extra_specs) do				param_spec[k] = v			end params[param_mod] = param_spec end end

local args = require("Module:parameters").process(parent_args, params) local lang = m_languages.getByCode(args[1], 1) local sc = args["sc"] and require("Module:scripts").getByCode(args["sc"], "sc") or nil

local rawDialects = {} local items = {}

-- Find the maximum index among any of the list parameters. local maxmaxindex = 0 for k, v in pairs(args) do		if type(v) == "table" and v.maxindex and v.maxindex > maxmaxindex then maxmaxindex = v.maxindex end end

if maxmaxindex == 0 then error("Either a positional parameter, alt parameter, id parameter, tr parameter, or ts parameter is required.") end

-- Is set to true if there is a term (entry link, alt text, transliteration, transcription) at the previous index. local prev = false local use_semicolon = false local put

local termno = 0 for i = 1, maxmaxindex do		-- If the previous term parameter was empty and we're not on the first term parameter, -- this term parameter and any others contain dialect or other labels. if i > 1 and not prev then rawDialects = {unpack(args[2], i, maxmaxindex)} break end

local term = args[2][i]

if term ~= ";" then termno = termno + 1

-- Compute whether any of the separate indexed params exist for this index. local any_param_at_index = term ~= nil for k, v in pairs(args) do -- Look for named list parameters. We check: -- (1) key is a string (excludes 2=, a numbered rather than named list param, because it needs to				--    be indexed using `i` instead of `termno`); -- (2) value is a table (1= and sc= are converted into strings or nil rather than lists); -- (3) the value has an entry at index `termno` (the current logical index). if type(k) == "string" and type(v) == "table" and v[termno] then any_param_at_index = true -- Tracking for use of any specific indexed parameter. FIXME: Do we still need this? -- FIXME: If we don't need it, remove the call to track below, add `break` below, and wrap -- the `for` loop in `if not any_param_at_index then` for efficiency purposes. -- break -- Special:WhatLinksHere/Template:tracking/alter/alt -- Special:WhatLinksHere/Template:tracking/alter/id -- Special:WhatLinksHere/Template:tracking/alter/tr -- [etc.] track(k) end end

-- If any of the params used for formatting this term is present, create a term and add it to the list. if any_param_at_index then -- Initialize the `termobj` object and the `term` object passed to full_link in Module:links. local termobj = { joiner = i > 1 and (args[2][i - 1] == ";" and "; " or ", ") or "", term = { lang = lang, sc = sc, term = term, },				}

-- Parse all the term-specific parameters and store in `term` or `termobj`. for param_mod, param_mod_spec in pairs(param_mods) do					local dest = param_mod_spec.item_dest or param_mod local arg = args[param_mod][termno] if arg then if param_mod_spec.convert then arg = param_mod_spec.convert(arg, false, termno) end local obj = param_mod_spec.outer and termobj or termobj.term obj[dest] = arg end end

-- Check for inline modifier, e.g. מרים. But exclude HTML entry with, , -- or similar in it, caused by wrapping an argument in, or similar. Basically, all tags -- of the sort we parse here should consist of a less-than sign, plus letters, plus a colon, e.g. , -- so if we see a tag on the outer level that isn't in this format, we don't try to parse it. The -- restriction to the outer level is to allow generated HTML inside of e.g. qualifier tags, such as -- foo. if term and term:find("<") and not term:find("^[^<]*<[a-z]*[^a-z:]") then if not put then put = require("Module:parse utilities") end local run = put.parse_balanced_segment_run(term, "<", ">") local function parse_err(msg) -- Add 1 before first term index starts at 2. error(msg .. ": " .. (i + 1) .. "=" .. table.concat(run)) end termobj.term.term = run[1] ~= "" and run[1] or nil

for j = 2, #run - 1, 2 do						if run[j + 1] ~= "" then parse_err("Extraneous text '" .. run[j + 1] .. "' after modifier") end local modtext = run[j]:match("^<(.*)>$") if not modtext then parse_err("Internal error: Modifier '" .. modtext .. "' isn't surrounded by angle brackets") end local prefix, arg = modtext:match("^([a-z]+):(.*)$") if not prefix then parse_err(("Modifier %s lacks a prefix, should begin with one of %s followed by a colon"):format( run[j], table.concat(get_valid_prefixes, ","))) end local param_mod_spec = param_mods[prefix] if not param_mod_spec then parse_err(("Unrecognized prefix '%s' in modifier %s, should be one of %s"):format( prefix, run[j], table.concat(get_valid_prefixes, ","))) end local dest = param_mod_spec.item_dest or prefix local obj = param_mod_spec.outer and termobj or termobj.term if obj[dest] then parse_err("Modifier '" .. prefix .. "' occurs twice, second occurrence " .. run[j]) end if param_mod_spec.convert then arg = param_mod_spec.convert(arg, true, termno) end obj[dest] = arg end end

-- FIXME: Either we should have a general mechanism in `param_mods` for default values, or (better) modify -- Module:links so it can handle nil for .genders. termobj.term.genders = termobj.term.genders or {}

-- If the displayed term (from .term or .alt) has an embedded comma, use a semicolon to join the terms. local term_text = termobj.term.term or termobj.term.alt if not use_semicolon and term_text then if term_text:find(",", 1, true) then use_semicolon = true end end

-- If the to-be-linked term is the same as the pagename, display it unlinked. if not allow_self_link and termobj.term.term and lang:makeEntryName(termobj.term.term) == pagename then track("term is pagename") termobj.term.alt = termobj.term.alt or termobj.term.term termobj.term.term = nil end

table.insert(items, termobj) prev = true else if math.max(args.alt.maxindex, args.id.maxindex, args.tr.maxindex, args.ts.maxindex) >= termno then track("too few terms") end

prev = false end end end

-- The template must have either items or dialect labels. if items[1] == nil and rawDialects[1] == nil then error("No terms found!") end

-- If any term had an embedded comma, override all joiners to be semicolons. if use_semicolon then for i, item in ipairs(items) do			if i > 1 then item.joiner = "; " end end end

-- Format all the items, including joiners, pre-qualifiers and post-qualifiers. for i, item in ipairs(items) do local preq_text = item.q and require("Module:qualifier").format_qualifier(item.q) .. " " or "" items[i] = item.joiner .. preq_text .. m_links.full_link(item.term, nil, allow_self_link) .. (item.qq and " " .. require("Module:qualifier").format_qualifier(item.qq) or "") end

-- Construct the final output. if include_dialect_tags then -- If there are dialect or similar tags, construct them now and append to final output. local dialects = export.make_dialects(rawDialects, lang) if #dialects > 0 then local dialect_label if lang:hasTranslit then dialect_label = " – ''" .. table.concat(dialects, ", ") .. "''"			else dialect_label = " (" .. table.concat(dialects, ", ") .. ")" end

-- Fixes the problem of  being added to  at the end of last dialect parameter dialect_label = mw.ustring.gsub(dialect_label, "", "") table.insert(items, dialect_label) end end

return table.concat(items) end

-- Template-callable function for displaying alternative forms. function export.create(frame) local parent_args = frame:getParent.args local title = mw.title.getCurrentTitle local PAGENAME = title.text return export.display_alternative_forms(parent_args, title, "include dialect tags") end

function export.categorize(frame) local content = {}

local title = mw.title.getCurrentTitle local titletext = title.text local namespace = title.nsText local subpagename = title.subpageText

-- subpagename ~= titletext if it is a documentation page if namespace == "Module" and subpagename == titletext then local langCode = mw.ustring.match(titletext, "^([^:]+):") local lang = m_languages.getByCode(langCode) or error('"' .. langCode .. '" is not a valid language code.') content.canonicalName = lang:getCanonicalName

local categories = [=[

]=]

categories = mw.ustring.gsub(categories, "<([^>]+)>", content) return categories end end

return export