Module:gender and number: Difference between revisions
No edit summary |
No edit summary |
||
| (10 intermediate revisions by 3 users not shown) | |||
| Line 1: | Line 1: | ||
local export = {} | |||
local | local debug_track_module = "Module:debug/track" | ||
local load_module = "Module:load" | |||
local pron_qualifier_module = "Module:pron qualifier" | |||
local parameters_module = "Module:parameters" | |||
local string_utilities_module = "Module:string utilities" | |||
local table_module = "Module:table" | |||
local utilities_module = "Module:utilities" | |||
local | local concat = table.concat | ||
local insert = table.insert | |||
local function debug_track(...) | |||
debug_track = require(debug_track_module) | |||
return debug_track(...) | |||
end | |||
local function deep_copy(...) | |||
deep_copy = require(table_module).deepCopy | |||
return deep_copy(...) | |||
end | |||
local function format_categories(...) | |||
format_categories = require(utilities_module).format_categories | |||
return format_categories(...) | |||
end | |||
local function format_pron_qualifiers(...) | |||
format_pron_qualifiers = require(pron_qualifier_module).format_qualifiers | |||
return format_pron_qualifiers(...) | |||
end | |||
local function load_data(...) | |||
load_data = require(load_module).load_data | |||
return load_data(...) | |||
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 gender_and_number_data | |||
local function get_gender_and_number_data() | |||
gender_and_number_data, get_gender_and_number_data = load_data("Module:gender and number/data"), nil | |||
return gender_and_number_data | |||
end | |||
-- | --[==[ intro: | ||
This module creates standardised displays for gender and number. It converts a gender specification into Wiki/HTML format. | |||
A gender/number specification consists of one or more gender/number elements, separated by hyphens. Examples are: | |||
{"n"} (neuter gender), {"f-p"} (feminine plural), {"m-an-p"} (masculine animate plural), | |||
{"pf"} (perfective aspect). Each gender/number element has the following properties: | |||
# A code, as used in the spec, e.g. {"f"} for feminine, {"p"} for plural. | |||
# A type, e.g. `gender`, `number` or `animacy`. Each element in a given spec must be of a different type. | |||
# A display form, which in turn consists of a display code and a tooltip gloss. The display code | |||
may not be the same as the spec code, e.g. the spec code {"an"} has display code {"anim"} and tooltip | |||
gloss ''animate''. | |||
# A category into which lemmas of the right part of speech are placed if they have a gender/number | |||
spec containing the given element. For example, a noun with gender/number spec {"m-an-p"} is placed | |||
into the categories `<var>lang</var> masculine nouns`, `<var>lang</var> animate nouns` and `<var>lang</var> pluralia tantum`. | |||
]==] | |||
-- Version of format_list that can be invoked from a template. | --[==[ | ||
Version of format_list that can be invoked from a template. | |||
]==] | |||
function export.show_list(frame) | function export.show_list(frame) | ||
local | local params = { | ||
[1] = {list = true}, | |||
["lang"] = {type = "language"}, | |||
local | } | ||
local iargs = process_params(frame.args, params) | |||
return export.format_list(iargs[1], iargs.lang) | |||
return export.format_list( | |||
end | end | ||
-- Older entry point; equivalent to format_genders() except that it formats the | --[==[ | ||
Older entry point; equivalent to format_genders() except that it formats the | |||
categories and returns them appended to the formatted gender text rather than | |||
returning the formatted text and categories separately. | |||
]==] | |||
function export.format_list(specs, lang, pos_for_cat, sort_key) | function export.format_list(specs, lang, pos_for_cat, sort_key) | ||
debug_track("gender and number/old-format-list") | |||
local text, cats = export.format_genders(specs, lang, pos_for_cat) | local text, cats = export.format_genders(specs, lang, pos_for_cat) | ||
if | if not cats then | ||
return text | return text | ||
end | end | ||
return text .. | return text .. format_categories(cats, lang, sort_key) | ||
end | |||
local function autoadd_abbr(display) | |||
if not display then | |||
error("Internal error: '.display' for gender/number code is missing") | |||
end | |||
if display:find("<abbr", nil, true) then | |||
return display | |||
end | |||
return ('%s'):format(display, display) | |||
end | end | ||
-- Format one or more gender/number specifications. Each spec is either a string, e.g. "f-p", or | -- Add qualifiers, labels and references to a formatted gender/number spec. `spec` is the object describing the | ||
-- gender/number spec, which should optionally contain: | |||
-- * left qualifiers in `q` or (for compatibility) `qualifiers`, an array of strings; | |||
-- * right qualifiers in `qq`, an array of strings; | |||
-- * left labels in `l`, an array of strings; | |||
-- * right labels in `ll`, an array of strings; | |||
-- * references in `refs`, an array either of strings (formatted reference text) or objects containing fields `text` | |||
-- (formatted reference text) and optionally `name` and/or `group`; | |||
-- `formatted` is the formatted version of the term itself, and `lang` is the optional language object passed into | |||
-- format_genders(). | |||
local function add_qualifiers_and_refs(formatted, spec, lang) | |||
local function field_non_empty(field) | |||
local list = spec[field] | |||
if not list then | |||
return nil | |||
end | |||
if type(list) ~= "table" then | |||
error(("Internal error: Wrong type for `spec.%s`=%s, should be \"table\""):format( | |||
field, mw.dumpObject(list))) | |||
end | |||
return list[1] | |||
end | |||
if field_non_empty("q") or field_non_empty("qq") or field_non_empty("l") or field_non_empty("ll") or | |||
field_non_empty("qualifiers") or field_non_empty("refs") then | |||
formatted = format_pron_qualifiers{ | |||
lang = lang, | |||
text = formatted, | |||
q = spec.q, | |||
qq = spec.qq, | |||
qualifiers = spec.qualifiers, | |||
l = spec.l, | |||
ll = spec.ll, | |||
refs = spec.refs, | |||
} | |||
end | |||
return formatted | |||
end | |||
--[==[ | |||
Format one or more gender/number specifications. Each spec is either a string, e.g. {"f-p"}, or a table of the form | |||
{ {spec = "SPEC", qualifiers = {"QUALIFIER", "QUALIFIER", ...}}} where `.spec` is a gender/number spec such as {"f-p"} | |||
and `.qualifiers` is a list of qualifiers to display before the formatted gender/number spec. `.spec` must be present | |||
but `.qualifiers` may be omitted. | |||
The function returns two values: | |||
# the formatted text; | |||
# a list of the categories to add. | |||
If `lang` (which should be a language object) and `pos_for_cat` (which should be a plural part of speech) are given, | |||
gender categories such as `German masculine nouns` or `Russian imperfective verbs` are added to the categories, and | |||
request categories such as `Requests for gender in <var>lang</var> entries` or | |||
`Requests for animacy in <var>lang</var> entries` may also be added. Otherwise, if only `lang` is given, only request | |||
categories may be returned. If both are omitted, the returned list is empty. | |||
]==] | |||
function export.format_genders(specs, lang, pos_for_cat) | function export.format_genders(specs, lang, pos_for_cat) | ||
local formatted_specs | local formatted_specs, categories, seen_types = {} | ||
local all_is_nounclass = nil | local all_is_nounclass = nil | ||
local full_langname = lang and lang:getFullName() or nil | |||
local function do_gender_spec(spec, parts) | local function do_gender_spec(spec, parts) | ||
local types = {} | local types = {} | ||
local codes = (gender_and_number_data or get_gender_and_number_data()).codes | |||
for key, code in ipairs(parts) do | for key, code in ipairs(parts) do | ||
-- Is this code valid? | -- Is this code valid? | ||
if not codes[code] then | if not codes[code] then | ||
error('The tag "' .. code .. '" in the gender specification "' .. spec.spec .. '" is not valid.') | error('The tag "' .. code .. '" in the gender specification "' .. spec.spec .. '" is not valid. See [[Module:gender and number]] for a list of valid tags.') | ||
end | end | ||
| Line 141: | Line 188: | ||
types[typ] = true | types[typ] = true | ||
parts[key] = autoadd_abbr(codes[code].display) | |||
-- Generate categories if called for. | -- Generate categories if called for. | ||
| Line 151: | Line 194: | ||
local cat = codes[code].cat | local cat = codes[code].cat | ||
if cat then | if cat then | ||
if not categories then | |||
categories = {} | |||
end | |||
insert(categories, full_langname .. " " .. cat) | |||
end | end | ||
if seen_types[typ] and seen_types[typ] ~= code then | if not seen_types then | ||
cat = | seen_types = {} | ||
elseif seen_types[typ] and seen_types[typ] ~= code then | |||
cat = (gender_and_number_data or get_gender_and_number_data()).multicode_cats[typ] | |||
if cat then | if cat then | ||
if not categories then | |||
categories = {} | |||
end | |||
insert(categories, full_langname .. " " .. cat) | |||
end | end | ||
end | end | ||
seen_types[typ] = code | seen_types[typ] = code | ||
end | |||
if lang and codes[code].req then | |||
local type_for_req = typ | |||
if code == "?" then | |||
-- Keep in mind `pos_for_cat` may be nil here. | |||
type_for_req = pos_for_cat == "verbs" and "aspect" or "gender" | |||
end | |||
if not categories then | |||
categories = {} | |||
end | |||
insert(categories, "Requests for " .. type_for_req .. " in " .. full_langname .. " entries") | |||
end | end | ||
end | end | ||
-- Add the processed codes together with non-breaking spaces | -- Add the processed codes together with non-breaking spaces | ||
if | if not parts[2] and parts[1] then | ||
return parts[1] | return parts[1] | ||
else | |||
return concat(parts, " ") | |||
end | end | ||
end | end | ||
| Line 174: | Line 237: | ||
spec = {spec = spec} | spec = {spec = spec} | ||
end | end | ||
local is_nounclass | local spec_spec, is_nounclass = spec.spec | ||
-- If the specification starts with cX, then it is a noun class specification. | -- If the specification starts with cX, then it is a noun class specification. | ||
if | if spec_spec:match("^%d") or spec_spec:match("^c[^-]") then | ||
is_nounclass = true | is_nounclass = true | ||
code = | local code = spec_spec:gsub("^c", "") | ||
local text | local text | ||
if code == "?" then | if code == "?" then | ||
text = '<abbr class="noun-class | text = '<abbr class="noun-class" title="noun class missing">?</abbr>' | ||
if lang then | |||
if not categories then | |||
categories = {} | |||
end | |||
insert(categories, "Requests for noun class in " .. full_langname .. " entries") | |||
end | |||
else | else | ||
text = '<abbr class="noun-class | text = '<abbr class="noun-class" title="noun class ' .. code .. '">' .. code .. "</abbr>" | ||
if lang and pos_for_cat then | if lang and pos_for_cat then | ||
if not categories then | |||
categories = {} | |||
end | |||
insert(categories, full_langname .. " class " .. code .. " POS") | |||
end | end | ||
end | end | ||
local | local text_with_qual = add_qualifiers_and_refs(text, spec, lang) | ||
insert(formatted_specs, text_with_qual) | |||
else | else | ||
-- Split the parts and iterate over each part, converting it into its display form | -- Split the parts and iterate over each part, converting it into its display form | ||
local parts = | local parts = split(spec.spec, "-", true, true) | ||
local | local combined_codes = (gender_and_number_data or get_gender_and_number_data()).combinations | ||
if lang then | |||
-- Check if the specification is valid | |||
--elseif langinfo.genders then | |||
-- local valid_genders = {} | |||
-- for _, g in ipairs(langinfo.genders) do valid_genders[g] = true end | |||
-- | |||
-- if not valid_genders[spec.spec] then | |||
-- local valid_string = {} | |||
-- for i, g in ipairs(langinfo.genders) do valid_string[i] = g end | |||
-- error('The gender specification "' .. spec.spec .. '" is not valid for ' .. langinfo.names[1] .. ". Valid are: " .. concat(valid_string, ", ")) | |||
-- end | |||
--end | |||
end | |||
local has_combined = false | local has_combined = false | ||
| Line 210: | Line 291: | ||
if not has_combined then | if not has_combined then | ||
if formatted_specs[1] then | |||
insert(formatted_specs, "or") | |||
end | |||
insert(formatted_specs, add_qualifiers_and_refs(do_gender_spec(spec, parts), spec, lang)) | |||
else | else | ||
-- This logic is to handle combined gender specs like 'mf' and 'mfbysense'. | |||
local all_parts = {{}} | local all_parts = {{}} | ||
local extra_displays | |||
local this_formatted_specs = {} | |||
for | for _, code in ipairs(parts) do | ||
if combined_codes[code] then | if combined_codes[code] then | ||
local new_all_parts = {} | local new_all_parts = {} | ||
for _, one_parts in ipairs(all_parts) do | for _, one_parts in ipairs(all_parts) do | ||
for _, one_code in ipairs(combined_codes[code].codes) do | for _, one_code in ipairs(combined_codes[code].codes) do | ||
local new_combined_parts = | local new_combined_parts = deep_copy(one_parts) | ||
insert(new_combined_parts, one_code) | |||
insert(new_all_parts, new_combined_parts) | |||
end | end | ||
end | end | ||
| Line 228: | Line 315: | ||
local extra_cat = combined_codes[code].cat | local extra_cat = combined_codes[code].cat | ||
if extra_cat then | if extra_cat then | ||
if not categories then | |||
categories = {} | |||
end | |||
insert(categories, full_langname .. " " .. extra_cat) | |||
end | |||
end | |||
local extra_display = combined_codes[code].display | |||
if extra_display then | |||
if not extra_displays then | |||
extra_displays = {} | |||
end | end | ||
insert(extra_displays, autoadd_abbr(extra_display)) | |||
end | end | ||
else | else | ||
for _, one_parts in ipairs(all_parts) do | for _, one_parts in ipairs(all_parts) do | ||
insert(one_parts, code) | |||
end | end | ||
end | end | ||
| Line 239: | Line 336: | ||
for _, parts in ipairs(all_parts) do | for _, parts in ipairs(all_parts) do | ||
if this_formatted_specs[1] then | |||
insert(this_formatted_specs, "or") | |||
end | |||
insert(this_formatted_specs, do_gender_spec(spec, parts)) | |||
end | end | ||
if extra_displays then | |||
for _, display in ipairs(extra_displays) do | |||
insert(this_formatted_specs, display) | |||
end | |||
end | end | ||
insert(formatted_specs, add_qualifiers_and_refs( | |||
concat(this_formatted_specs, " "), spec, lang)) | |||
end | end | ||
| Line 280: | Line 363: | ||
end | end | ||
if lang and pos_for_cat then | if categories and lang and pos_for_cat then | ||
for i, cat in ipairs(categories) do | for i, cat in ipairs(categories) do | ||
categories[i] = cat:gsub("POS", pos_for_cat) | categories[i] = cat:gsub("POS", pos_for_cat) | ||
| Line 286: | Line 369: | ||
end | end | ||
if | if all_is_nounclass then | ||
-- Add the processed codes together with slashes | -- Add the processed codes together with slashes | ||
return '<span class="gender | return '<span class="gender">class ' .. concat(formatted_specs, "/") .. "</span>", categories | ||
else | else | ||
-- Add the processed codes together with | -- Add the processed codes together with spaces | ||
return '<span class="gender | return '<span class="gender">' .. concat(formatted_specs, " ") .. "</span>", categories | ||
end | end | ||
end | end | ||
return export | return export | ||