Modul:accel
This module supports the accelerated entry creation gadget, WT:ACCEL. It automatically creates entries according to a set of language-specific rules, located in submodules.
The module will automatically try to merge multiple generated entries into one, if everything but the definitions is the same. Moreover, if the definitions use {{inflection of}}
, then the inflection tags will be combined into a single {{inflection of}}
definition line, separated by a semicolon ;
. In addition, the module will attempt to group multiple semicolon-separated tag sets in a single {{inflection of}}
call that differ in only one dimension, using multipart tags. For example, the following initially-generated entries
==Latin== ===Adjective=== {{head|la|adjective form|head=bonīs}} # {{inflection of|la|bonus||dat|m|p}} ===Adjective=== {{head|la|adjective form|head=bonīs}} # {{inflection of|la|bonus||dat|f|p}} ===Adjective=== {{head|la|adjective form|head=bonīs}} # {{inflection of|la|bonus||dat|n|p}} ===Adjective=== {{head|la|adjective form|head=bonīs}} # {{inflection of|la|bonus||abl|m|p}} ===Adjective=== {{head|la|adjective form|head=bonīs}} # {{inflection of|la|bonus||abl|f|p}} ===Adjective=== {{head|la|adjective form|head=bonīs}} # {{inflection of|la|bonus||abl|n|p}}
will first be grouped into one entry as follows:
==Latin== ===Adjective=== {{head|la|adjective form|head=bonīs}} # {{inflection of|la|bonus||dat|m|p}} # {{inflection of|la|bonus||dat|f|p}} # {{inflection of|la|bonus||dat|n|p}} # {{inflection of|la|bonus||abl|m|p}} # {{inflection of|la|bonus||abl|f|p}} # {{inflection of|la|bonus||abl|n|p}}
Then, the several inflection lines will be combined together into one:
==Latin== ===Adjective=== {{head|la|adjective form|head=bonīs}} # {{inflection of|la|bonus||dat|m|p|;|dat|f|p|;|dat|n|p|;|abl|m|p|;|abl|f|p|;|abl|n|p}}
Finally, the several tag sets in the single {{inflection of}}
call will be grouped into one tag set with multipart tags, like this:
==Latin== ===Adjective=== {{head|la|adjective form|head=bonīs}} # {{inflection of|la|bonus||dat//abl|m//f//n|p}}
The use of multipart tags like this helps by indicating where syncretism occurs and reduces the amount of information that must be processed. The algorithm to do the grouping is quite smart; it will only group when it won't change the semantics of the inflections, and there are multiple possible groupings that yield the same number of tag sets, the one with the fewest number of multipart tags is preferred. As an example of the latter, an entry like this::
==Latin== ===Adjective=== {{head|la|adjective form|head=bonum}} # {{inflection of|la|bonus||acc|m|s|;|nom|n|s|;|acc|n|s|;|voc|n|s}}
will be converted to the following:
==Latin== ===Adjective=== {{head|la|adjective form|head=bonum}} # {{inflection of|la|bonus||acc|m|s|;|nom//acc//voc|n|s}}
It could equally well be converted to the following, which also contains two tag sets:
==Latin== ===Adjective=== {{head|la|adjective form|head=bonum}} # {{inflection of|la|bonus||acc|m//n|s|;|nom//voc|n|s}}
However, this grouping is dispreferred because it results in two multipart tags, while the preferred grouping has only one.
Language-specific submodules
szerkesztésDefault rules
szerkesztésThe module uses a set of default rules which generate entries that should be acceptable in most cases:
- The headword is formatted using
{{head}}
, using the part-of-speech of the lemma plus "form", e.g. noun → noun form, adjective → adjective form, etc.- For the tags
comparative
andsuperlative
, "comparative" and "superlative" are added to before the part of speech instead.
- For the tags
- The definition is formatted using
{{inflection of}}
, and theform
tag is used directly as the form tag of the template. Thus,gen|s
→{{inflection of|lang|...||gen|s}}
. - For some tags, a special-purpose template is used in the definition instead:
p
→{{plural of}}
f
→{{feminine of}}
f|s
→{{feminine singular of}}
m|p
→{{masculine plural of}}
f|p
→{{feminine plural of}}
comparative
→{{comparative of}}
superlative
→{{superlative of}}
These defaults may change in the future as Wiktionary's needs change. Don't rely on particular default values. If in doubt, assume that everything will use {{inflection of}}
, and override anything you want to be different.
Requesting new rules
szerkesztésFirst, consider whether new rules are needed at all. The default rules suffice for many cases, especially if you make sure to provide a value for the form
tag that can be directly inserted into {{inflection of}}
. If you really need language-specific rules, and are not able to edit the module yourself, please file requests for new features at the Grease Pit. Specify:
- What you want to generate the links for. That includes at least a link to the template whose links you want to make green.
- What the generated entries should look like. In particular, which headword-line template it should use, and which form-of template, which parameters they should receive in which situations, and so on. A link to a word that has blue links to all the forms in the template would work best, as an example.
- Ideally, a link to a word that has red links to all the forms. This is useful for testing to see if the generated entries are correct.
Adding new rules
szerkesztésGeneration rules are used to create the entry's contents. The general parts are defined in this module, while the language-specific rules are handled by submodules. Each submodule must return a table containing one function named generate
. This function has two parameters, params
and entry
, and it does not return any value.
Params
szerkesztésThe params
parameter is a table that contains the information about the lemma, the form-of entry to be created, and the acceleration tags. It contains the following values:
lang
- The language code of the language in question.
pos
- The part of speech that the new entry is created for. This is taken from whatever part-of-speech header preceded the template in the lemma entry.
form
- The name of the form. This is whatever was given in
form-form-of
as the form name. gender
- The gender, or
nil
. Whatever was given ingender-gender
as the gender. transliteration
- The transliteration, or
nil
. Whatever was given intransliteration-translit
as the transliteration. origin
- The term that the new entry should link back to. If the
origin-pagename
tag was given, it will be that. Otherwise, it's the same asorigin_pagename
by default. origin_pagename
- The page name of the lemma.
origin_transliteration
- The origin_transliteration, or
nil
. Whatever was given inorigin_transliteration-translit
. target
- The form that the new entry is created for. This is taken automatically from the display form (alt) in the template's link to the form. It is used to give the
head=
parameter if necessary. This is the same astarget_pagename
in most cases, but can be different if the display form contains additional diacritics. target_pagename
- The page name of the entry to be created.
Entry
szerkesztésThe entry
parameter is essentially the return value of the function. It is a table that contains the different parts of the entry that is being created. Some of them will already have a default value when the language-specific function is run, while others are nil
by default. The purpose of the generation function for each language is to fill in these values, or override the defaults, so that the entry is generated according to what is needed for the language. The entry
table contains the following values:
pronunc
- The contents of the "Pronunciation" section, if any. Empty by default.
pos_header
- The name of the level 3 part-of-speech header for the new entry. This does not usually need to be changed, as it automatically matches the part of speech of the main entry. But you can change it if, for example, you are generating a participle entry and you want to show "Participle" instead of "Verb".
head
- The headword template code and all its parameters. By default, it uses
{{head|(lang)|(pos) form}}
, withhead=
andtr=
as necessary. You need to override this if you need something else. def
- The definition line, without the initial
#
. You must override this, as there is no default and the script will fail. inflection
,declension
,conjugation
- The contents of the "Inflection", "Declension" and "Conjugation" sections respectively, if any. Empty by default. This can be used if the new entry is a sub-lemma with its own inflection, such as participles or comparative/superlative forms that inflect themselves.
mutation
- The contents of the "Mutation" section. Empty by default. This appears at level 3 rather than level 4.
altforms
- The contents of the "Alternative forms" section. Empty by default. This appears after the definitions (rather than before, which is more common) and after the sections above (as per WT:EL).
local export = {}
local rsplit = mw.text.split
function no_rule_error(params) -- Intentionally global; better way to do this?
return error(('No rule for "%s" in language "%s".')
:format(params.form, params.lang), 2)
end
messages = require("Module:array")() -- intentionally global
function export.default_entry(params)
local entry = {
pronunc = nil,
pos_header = mw.getContentLanguage():ucfirst(params.pos),
head =
"{{head|" .. params.lang .. "|" .. params.pos .. " form" ..
(params.target ~= params.target_pagename and '|head=' .. params.target or "") ..
(params.transliteration and "|tr=" .. params.transliteration or "") ..
(params.gender and "|g=" .. params.gender or "") ..
"}}",
def =
"{{inflection of|" .. params.lang ..
"|" .. params.origin ..
(params.origin_transliteration and "|tr=" .. params.origin_transliteration or "") ..
"||" .. params.form ..
"}}",
inflection = nil,
declension = nil,
conjugation = nil,
mutation = nil,
altforms = nil,
}
-- Exceptions for some forms
local templates = {
["p"] = "plural of",
["f"] = "feminine of",
["f|s"] = "feminine singular of",
["m|p"] = "masculine plural of",
["f|p"] = "feminine plural of",
}
if params.form == "comparative" or params.form == "superlative" then
entry.head =
"{{head|" .. params.lang .. "|" .. params.form .. " " .. params.pos ..
(params.target ~= params.target_pagename and "|head=" .. params.target or "") ..
(params.gender and "|g=" .. params.gender or "") ..
"}}"
entry.def =
"{{" .. params.form .. " of|" .. params.lang ..
"|" .. params.origin ..
(params.origin_transliteration and "|tr=" .. params.origin_transliteration or "") ..
(params.pos ~= "adjective" and "|POS=" .. params.pos or "") ..
"|nocat=1}}"
elseif params.form == "equative" then
entry.head =
"{{head|" .. params.lang .. "|" .. params.pos .. " " .. params.form .. " form" ..
(params.target ~= params.target_pagename and "|head=" .. params.target or "") ..
(params.gender and "|g=" .. params.gender or "") ..
"}}"
entry.def =
"{{" .. params.form .. " of|" .. params.lang ..
"|" .. params.origin ..
(params.origin_transliteration and "|tr=" .. params.origin_transliteration or "") ..
(params.pos ~= "adjective" and "|POS=" .. params.pos or "") ..
"|nocat=1}}"
elseif templates[params.form] then
entry.def =
"{{" .. templates[params.form] .. "|" .. params.lang ..
"|" .. params.origin ..
(params.origin_transliteration and "|tr=" .. params.origin_transliteration or "") ..
"}}"
end
return entry
end
-- Given a list of tags, split into tag sets (separated by semicolons in
-- the initial list of tags).
local function split_tags_into_tag_sets(tags)
local tag_set_group = {}
local cur_tag_set = {}
for _, tag in ipairs(tags) do
if tag == ";" then
if #cur_tag_set > 0 then
table.insert(tag_set_group, cur_tag_set)
end
cur_tag_set = {}
else
table.insert(cur_tag_set, tag)
end
end
if #cur_tag_set > 0 then
table.insert(tag_set_group, cur_tag_set)
end
return tag_set_group
end
-- Canonicalize multipart shortcuts (e.g. "123" -> "1//2//3") and
-- list shortcuts (e.g. "1s" -> {"1", "s"}); leave others alone.
local function canonicalize_multipart_and_list_shortcuts(tags)
local result = {}
for _, tag in ipairs(tags) do
local expansion = require("Module:form of").lookup_shortcut(tag)
if type(expansion) == "string" and not expansion:find("//", nil, true) then
expansion = tag
end
if type(expansion) == "table" then
for _, t in ipairs(expansion) do
table.insert(result, t)
end
else
table.insert(result, expansion)
end
end
return result
end
-- Split a multipart tag into component tags, normalize each component, and
-- return the resulting list. If MAP_TO_CANONICAL_SHORTCUT is given,
-- attempt to map each normalized component tag to its "canonical shortcut",
-- i.e. the first shortcut listed among its shortcuts.
--
-- If given a two-level multipart tag such as "1:sg//3:pl", the resulting
-- return value will be {"first:singular", "third:plural"}, or {"1:s", "3:p"}
-- if MAP_TO_CANONICAL_SHORTCUT is given.
local function split_and_normalize_tag(tag, map_to_canonical_shortcut)
local m_form_of = require("Module:form of")
local normalized = m_form_of.normalize_tags({tag}, true)
assert(#normalized == 1, "Something is wrong, encountered list tag " .. tag .. ", which should have been canonicalized earlier")
tag = normalized[1]
if tag:find("://") then
-- HTML URL???
return {tag}
else
local tags = rsplit(tag, "//")
if map_to_canonical_shortcut then
for i=1,#tags do
if tags[i]:find(":") then
local split_tags = rsplit(tags[i], ":")
for j=1,#split_tags do
local tagobj = m_form_of.lookup_tag(split_tags[j])
split_tags[j] = tagobj and tagobj.shortcuts and tagobj.shortcuts[1] or split_tags[j]
end
tags[i] = table.concat(split_tags, ":")
else
local tagobj = m_form_of.lookup_tag(tags[i])
tags[i] = tagobj and tagobj.shortcuts and tagobj.shortcuts[1] or tags[i]
end
end
end
return tags
end
end
-- Given a normalized tag, return its tag type, or "unknown" if a tag type
-- cannot be located (either the tag isn't recognized or for some reason
-- it doesn't specify a tag type).
local function get_normalized_tag_type(tag)
local tagobj = require("Module:form of").lookup_tag(tag)
return tagobj and tagobj.tag_type or "unknown"
end
-- Combine multiple semicolon-separated tag sets into multipart tags if
-- possible. We combine tag sets that differ in only one tag in a given
-- dimension, and repeat this until no changes in case we can reduce along
-- multiple dimensions, e.g.
--
-- {{inflection of|la|canus||dat|m|p|;|dat|f|p|;|dat|n|p|;|abl|m|p|;|abl|f|p|;|abl|n|p}}
--
-- {{inflection of|la|canus||dat//abl|m//f//n|p}}
local function combine_tag_sets_into_multipart(tags)
-- First, as an optimization, make sure there are multiple tag sets.
-- Otherwise, do nothing.
local found_semicolon = false
for _, tag in ipairs(tags) do
if tag == ";" then
found_semicolon = true
break
end
end
if not found_semicolon then
return tags
end
local m_table = require("Module:table")
-- Repeat until no changes can be made.
while true do
-- First, canonicalize 1s etc. into 1|s
local canonicalized_tags = canonicalize_multipart_and_list_shortcuts(tags)
local old_canonicalized_tags = canonicalized_tags
-- Then split into tag sets.
local tag_set_group = split_tags_into_tag_sets(canonicalized_tags)
-- Try combining in two different styles ("adjacent-first" =
-- do two passes, where the first pass only combines adjacent
-- tag sets, while the second pass combines nonadjacent tag sets;
-- "all-first" = do one pass combining nonadjacent tag sets).
-- Sometimes one is better, sometimes the other.
--
-- An example where adjacent-first is better:
--
-- {{inflection of|la|medius||m|acc|s|;|n|nom|s|;|n|acc|s|;|n|voc|s}}
--
-- all-first results in
--
-- {{inflection of|la|medius||m//n|acc|s|;|n|nom//voc|s}}
--
-- which isn't ideal.
--
-- If we do adjacent-first, we get
--
-- {{inflection of|la|medius||m|acc|s|;|n|nom//acc//voc|s}}
--
-- which is much better.
--
-- The opposite happens in
--
-- {{inflection of|grc|βουλόμενος||n|nom|s|;|m|acc|s|;|n|acc|s|;|n|voc|s}}
--
-- where all-first results in
--
-- {{inflection of|grc|βουλόμενος||n|nom//acc//voc|s|;|m|acc|s}}
--
-- which is better than the result from adjacent-first, which is
--
-- {{inflection of|grc|βουλόμενος||n|nom//voc|s|;|m//n|acc|s}}
--
-- To handle this conundrum, we try both, and look to see which one
-- results in fewer "combinations" (where a tag with // in it counts
-- as a combination). If both are different but have the same # of
-- combinations, we prefer adjacent-first, we seems generally a better
-- approach.
local tag_set_group_by_style = {}
for _, combine_style in ipairs({"adjacent-first", "all-first"}) do
-- Now, we do two passes. The first pass only combines adjacent
-- tag sets, while the second pass combines nonadjacent tag sets.
-- Copy tag_set_group, since we destructively modify the list.
local tag_sets = m_table.shallowClone(tag_set_group)
local combine_passes
if combine_style == "adjacent-first" then
combine_passes = {"adjacent", "all"}
else
combine_passes = {"all"}
end
for _, combine_pass in ipairs(combine_passes) do
local tag_ind = 1
while tag_ind <= #tag_sets do
local from, to
if combine_pass == "adjacent" then
if tag_ind == 1 then
from = 1
to = 0
else
from = tag_ind - 1
to = tag_ind - 1
end
else
from = 1
to = tag_ind - 1
end
local inner_broken = false
for prev_tag_ind=from,to do
local cur_tag_set = tag_sets[prev_tag_ind]
local tag_set = tag_sets[tag_ind]
if #cur_tag_set == #tag_set then
local mismatch_ind = nil
local innermost_broken = false
for i=1,#tag_set do
local tag1 = split_and_normalize_tag(cur_tag_set[i])
local tag2 = split_and_normalize_tag(tag_set[i])
if not m_table.deepEquals(m_table.listToSet(tag1),
m_table.listToSet(tag2)) then
if mismatch_ind then
innermost_broken = true
break
end
local combined_dims = {}
for _, tag in ipairs(tag1) do
combined_dims[get_normalized_tag_type(tag)] = true
end
for _, tag in ipairs(tag2) do
combined_dims[get_normalized_tag_type(tag)] = true
end
if m_table.size(combined_dims) == 1 and not combined_dims["unknown"] then
mismatch_ind = i
else
innermost_broken = true
break
end
end
end
if not innermost_broken then
-- No break, we either match perfectly or are combinable
if not mismatch_ind then
-- Two identical tag sets
table.remove(tag_sets, tag_ind)
inner_broken = true
break
else
-- Combine tag sets at mismatch_ind, using the canonical shortcuts.
tag1 = cur_tag_set[mismatch_ind]
tag2 = tag_set[mismatch_ind]
tag1 = split_and_normalize_tag(tag1, true)
tag2 = split_and_normalize_tag(tag2, true)
local combined_tag = table.concat(m_table.append(tag1, tag2), "//")
local new_tag_set = {}
for i=1,#cur_tag_set do
if i == mismatch_ind then
table.insert(new_tag_set, combined_tag)
else
local cur_canon_tag = split_and_normalize_tag(cur_tag_set[i])
local canon_tag = split_and_normalize_tag(tag_set[i])
assert(m_table.deepEquals(m_table.listToSet(cur_canon_tag),
m_table.listToSet(canon_tag)))
table.insert(new_tag_set, cur_tag_set[i])
end
end
tag_sets[prev_tag_ind] = new_tag_set
table.remove(tag_sets, tag_ind)
inner_broken = true
break
end
end
end
end
if not inner_broken then
-- No break from inner for-loop. Break from that loop indicates
-- that we found that the current tag set can be combined with
-- a preceding tag set, did the combination and deleted the
-- current tag set. The next iteration then processes the same
-- numbered tag set again (which is actually the following tag
-- set, because we deleted the tag set before it). No break
-- indicates that we couldn't combine the current tag set with
-- any preceding tag set, and need to advance to the next one.
tag_ind = tag_ind + 1
end
end
end
tag_set_group_by_style[combine_style] = tag_sets
end
local tag_set_group
if not m_table.deepEqualsList(tag_set_group_by_style["adjacent-first"], tag_set_group_by_style["all-first"]) then
local function num_combinations(group)
local num_combos = 0
for _, tag_set in ipairs(group) do
for _, tag in ipairs(tag_set) do
if tag:find("//") then
num_combos = num_combos + 1
end
end
end
return num_combos
end
local num_adjacent_first_combos = num_combinations(tag_set_group_by_style["adjacent-first"])
local num_all_first_combos = num_combinations(tag_set_group_by_style["all-first"])
if num_adjacent_first_combos < num_all_first_combos then
tag_set_group = tag_set_group_by_style["adjacent-first"]
elseif num_all_first_combos < num_adjacent_first_combos then
tag_set_group = tag_set_group_by_style["all-first"]
else
tag_set_group = tag_set_group_by_style["adjacent-first"]
end
else
-- Both are the same, pick either one
tag_set_group = tag_set_group_by_style["adjacent-first"]
end
canonicalized_tags = {}
for _, tag_set in ipairs(tag_set_group) do
if #canonicalized_tags > 0 then
table.insert(canonicalized_tags, ";")
end
for _, tag in ipairs(tag_set) do
table.insert(canonicalized_tags, tag)
end
end
if m_table.deepEqualsList(canonicalized_tags, old_canonicalized_tags) then
break
end
-- FIXME, we should consider reversing the transformation 1s -> 1|s,
-- but it's complicated to figure out when the transformation occurred;
-- not really important as both are equivalent
tags = canonicalized_tags
end
return tags
end
-- Test function, callable externally.
function export.test_combine_tag_sets_into_multipart(frame)
local combined_tags = combine_tag_sets_into_multipart(frame.args)
return table.concat(combined_tags, "|")
end
local function find_mergeable(entry, candidates)
local function can_merge(candidate)
for _, key in ipairs({"pronunc", "pos_header", "head", "inflection", "declension", "conjugation", "altforms"}) do
if entry[key] ~= candidate[key] then
return false
end
end
return true
end
for _, candidate in ipairs(candidates) do
if can_merge(candidate) then
return candidate
end
end
return nil
end
-- Merge multiple entries into one if they differ only in the definition, with all other
-- properties the same. The combined entry has multiple definition lines. We then do
-- further frobbing of {{inflection of}} lines:
--
-- 1. Convert lang= param to param 1 (there shouldn't be any remaining cases of accelerator
-- modules generating {{inflection of}} templates with lang=, but we do this just in case).
-- 2. Combine adjacent {{inflection of}} lines that differ only in the tags, e.g.:
--
-- # {{inflection of|la|bonus||nom|m|s}}
-- # {{inflection of|la|bonus||nom|n|s}}
-- # {{inflection of|la|bonus||acc|n|s}}
-- # {{inflection of|la|bonus||voc|n|s}}
--
-- becomes
--
-- # {{inflection of|la|bonus||nom|m|s|;|nom|n|s|;|acc|n|s|;|voc|n|s}}
--
-- 3. Further group {{inflection of}} lines with multiple tag sets (as may be generated b y
-- the previous step) using multipart tags, e.g. for the Latin entry ''bonum'',
--
-- # {{inflection of|la|bonus||nom|m|s|;|nom|n|s|;|acc|n|s|;|voc|n|s}}
--
-- becomes
--
-- # {{inflection of|la|bonus||nom|m|s|;|nom//acc//voc|n|s}}
--
-- This grouping can group across multiple dimensions, e.g. for the Latin entry ''bonīs'',
--
-- # {{inflection of|la|bonus||dat|m|p|;|dat|f|p|;|dat|n|p|;|abl|m|p|;|abl|f|p|;|abl|n|p}}
--
-- becomes
--
-- # {{inflection of|la|bonus||dat//abl|m//f//n|p}}
--
-- Another complex real-world example, for the Old English weak adjective form ''dēorenan'':
--
-- # {{inflection of|ang|dēoren||wk|acc|m|sg|;|wk|acc|f|sg|;|wk|gen|m|sg|;|wk|gen|f|sg|;|wk|gen|n|sg|;|wk|dat|m|sg|;|wk|dat|f|sg|;|wk|dat|n|sg|;|wk|ins|m|sg|;|wk|ins|f|sg|;|wk|ins|n|sg|;|wk|nom|m|pl|;|wk|nom|f|pl|;|wk|nom|n|pl|;|wk|acc|m|pl|;|wk|acc|f|pl|;|wk|acc|n|pl}}
--
-- becomes
--
-- # {{inflection of|ang|dēoren||wk|acc|m//f|sg|;|wk|gen//dat//ins|m//f//n|sg|;|wk|nom//acc|m//f//n|pl}}
--
-- Here, 17 separate tag sets are combined down into 3.
local function merge_entries(entries)
local entries_new = {}
-- First rewrite {{inflection of|...|lang=LANG}} to {{inflection of|LANG|...}}
for i, entry in ipairs(entries) do
local params = entry.def:match("^{{inflection of|([^{}]+)}}$")
if params then
params = rsplit(params, "|", true)
local new_params = {}
for j, param in ipairs(params) do
local lang = param:match("^lang=(.*)$")
if lang then
table.insert(new_params, 1, lang)
else
table.insert(new_params, param)
end
end
entry.def = "{{inflection of|" .. table.concat(new_params, "|") .. "}}"
end
end
for i, entry in ipairs(entries) do
local merge_entry = find_mergeable(entry, entries_new)
if merge_entry then
local params1 = merge_entry.def:match("^{{inflection of|([^{}]+)}}$")
local params2 = entry.def:match("^{{inflection of|([^{}]+)}}$")
merge_entry.def = merge_entry.def .. "\n# " .. entry.def
-- Do some extra-special merging with "inflection of"
if params1 and params2 then
-- Find the last unnamed parameter of the first template
params1 = rsplit(params1, "|", true)
local last_numbered_index
for j, param in ipairs(params1) do
if not param:find("=", nil, true) then
last_numbered_index = j
end
end
-- Add grammar tags of the second template
params2 = rsplit(params2, "|")
local tags = {}
local n = 0
for k, param in ipairs(params2) do
if not param:find("=", nil, true) then
n = n + 1
-- Skip the first three unnamed parameters,
-- which don't indicate grammar tags
if n >= 4 then
-- Now append the tags
table.insert(tags, param)
end
end
end
-- Add the new parameters after the existing ones
params1[last_numbered_index] = params1[last_numbered_index] .. "|;|" .. table.concat(tags, "|")
merge_entry.def = "{{inflection of|" .. table.concat(params1, "|") .. "}}"
end
else
table.insert(entries_new, entry)
end
end
-- Now combine tag sets inside a multiple-tag-set {{inflection of}} call
for i, entry in ipairs(entries) do
local infl_of_params = entry.def:match("^{{inflection of|([^{}]+)}}$")
if infl_of_params then
infl_of_params = rsplit(infl_of_params, "|", true)
-- Find the last unnamed parameter
local last_numbered_index
for j, param in ipairs(infl_of_params) do
if not param:find("=", nil, true) then
last_numbered_index = j
end
end
-- Split the params in three:
-- (1) Params before the inflection tags, and any named params mixed in with the tags
-- (2) The tags themselves
-- (3) Named params after the tags
local pre_tag_params = {}
local tags = {}
local post_tag_params = {}
local n = 0
for j, param in ipairs(infl_of_params) do
if not param:find("=", nil, true) then
n = n + 1
-- Skip the first three unnamed parameters, which don't indicate grammar tags
if n >= 4 then
table.insert(tags, param)
else
table.insert(pre_tag_params, param)
end
elseif n >= last_numbered_index then
table.insert(post_tag_params, param)
else
table.insert(pre_tag_params, param)
end
if not param:find("=", nil, true) then
last_numbered_index = j
end
end
-- Now combine tag sets.
tags = combine_tag_sets_into_multipart(tags)
-- Put the template back together.
local combined_params = {}
for _, param in ipairs(pre_tag_params) do
table.insert(combined_params, param)
end
for _, param in ipairs(tags) do
table.insert(combined_params, param)
end
for _, param in ipairs(post_tag_params) do
table.insert(combined_params, param)
end
entry.def = "{{inflection of|" .. table.concat(combined_params, "|") .. "}}"
end
end
return entries_new
end
local function entries_to_text(entries, lang)
lang = require("Module:languages").getByCode(lang, "lang")
for i, entry in ipairs(entries) do
entry =
(entry.pronunc and "===Pronunciation===\n" .. entry.pronunc .. "\n\n" or "") ..
"===" .. entry.pos_header .. "===\n" ..
entry.head .. "\n\n" ..
"# " .. entry.def ..
(entry.inflection and "\n\n====Inflection====\n" .. entry.inflection or "") ..
(entry.declension and "\n\n====Declension====\n" .. entry.declension or "") ..
(entry.conjugation and "\n\n====Conjugation====\n" .. entry.conjugation or "") ..
(entry.mutation and "\n\n===Mutation===\n" .. entry.mutation or "") ..
(entry.altforms and "\n\n====Alternative forms====\n" .. entry.altforms or "")
entries[i] = entry
end
return "==" .. lang:getCanonicalName() .. "==\n\n" .. table.concat(entries, "\n\n")
end
function export.generate(frame)
local fparams = {
lang = {required = true},
origin_pagename = {required = true},
target_pagename = {required = true},
num = {required = true, type = "number"},
pos = {list = true, allow_holes = true},
form = {list = true, allow_holes = true},
gender = {list = true, allow_holes = true},
transliteration = {list = true, allow_holes = true},
origin = {list = true, allow_holes = true},
origin_transliteration = {list = true, allow_holes = true},
target = {list = true, allow_holes = true},
}
local args = require("Module:parameters").process(frame.args, fparams)
local entries = {}
-- Generate each entry
for i = 1, args.num do
local params = {
lang = args.lang,
origin_pagename = args.origin_pagename,
target_pagename = args.target_pagename,
pos = args.pos[i] or error("The argument \"pos\" is missing for entry " .. i),
form = args.form[i] or error("The argument \"form\" is missing for entry " .. i),
gender = args.gender[i],
transliteration = args.transliteration[i],
origin = args.origin[i] or error("The argument \"origin\" is missing for entry " .. i),
origin_transliteration = args.origin_transliteration[i],
target = args.target[i],
}
params.form = params.form:gsub("|", "|")
-- Make a default entry
local entry = export.default_entry(params)
-- Try to use a language-specific module, if one exists
local success, lang_module = pcall(require, "Module:accel/" .. args.lang)
if success then
lang_module.generate(params, entry)
end
-- Add it to the list
table.insert(entries, entry)
end
-- Merge entries if possible
entries = merge_entries(entries)
entries = entries_to_text(entries, args.lang)
return entries
end
function export.generate_JSON(frame)
local success, entries = pcall(export.generate, frame)
-- If success is false, entries is an error message.
local ret = { [success and "entries" or "error"] = entries, messages = messages }
return require("Module:JSON").toJSON(ret)
end
return export