Module:number list: Difference between revisions

No edit summary
No edit summary
 
(16 intermediate revisions by 2 users not shown)
Line 1: Line 1:
local m_links = require("Module:links")
local m_str_utils = require("Module:string utilities")
local char = string.char
local concat = table.concat
local gsub = m_str_utils.gsub
local insert = table.insert
local list_to_set = require("Module:table").listToSet
local sort = table.sort
local split = m_str_utils.split
local u = m_str_utils.char
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
local upper = string.upper
local export = {}
local export = {}
 
local decimal_strategy
local m_links = require("Module:links")


--[=[
--[=[
Line 20: Line 33:
]=]
]=]


local form_types = {
local default_form_types = {
{key = "cardinal", display = "[[wikt:cardinal number|Cardinal]]"},
{key = "cardinal", display = "[[wikt:cardinal number|Cardinal]]"},
{key = "ordinal", display = "[[wikt:ordinal number|Ordinal]]"},
{key = "ordinal", display = "[[wikt:ordinal number|Ordinal]]"},
{key = "ordinal_abbr", display = "[[wikt:ordinal number|Ordinal]] [[wikt:abbreviation]]"},
{key = "ordinal_abbr", display = "[[wikt:ordinal number|Ordinal]] [[wikt:abbreviation|abbreviation]]"},
{key = "adverbial", display = "[[wikt:adverbial number|Adverbial]]"},
{key = "adverbial", display = "[[wikt:adverbial number|Adverbial]]"},
{key = "multiplier", display = "[[wikt:multiplier|Multiplier]]"},
{key = "multiplier", display = "[[wikt:multiplier|Multiplier]]"},
Line 42: Line 55:
lower = true,
lower = true,
}
}
local function track(page)
require("Module:debug/track")("number list/" .. page)
return true
end


--[=[
--[=[
Line 80: Line 98:
end
end


local function list_to_set(list)
-- Count keys in a set table (never use `#` on these; it is not the set cardinality).
local set = {}
local function set_size(set)
for _, item in ipairs(list) do
local n = 0
set[item] = true
for _ in pairs(set) do
end
n = n + 1
return set
end
 
function export.get_data_module_name(langcode, must_exist)
local module_name = "Module:number list/data/" .. langcode
if must_exist and not mw.title.new(module_name).exists then
error(("Data module [[%s]] for language code '%s' does not exist"):format(module_name, langcode))
end
end
return module_name
return n
end
end


local function power_of(n)
function export.get_data_module_name(langcode)
return "1" .. string.rep("0", n)
return "Module:number list/data/" .. langcode
end
end


Line 112: Line 123:
-- Parse a form with modifiers such as 'vuitanta-vuit<tag:Central>' or 'سیزده<tr:sizdah>'
-- Parse a form with modifiers such as 'vuitanta-vuit<tag:Central>' or 'سیزده<tr:sizdah>'
-- or 'سیزده<tr:sizdah><tag:Iranian>' into its component parts. Return a form object, i.e. an object with fields
-- or 'سیزده<tr:sizdah><tag:Iranian>' into its component parts. Return a form object, i.e. an object with fields
-- `form` for the form, and `tr`, `tag`, `q`, `qq` or `link` for the modifiers. The `tag` field is a tag list
-- `form` for the form, and `tr`, `tag`, `q`, `qq`, `g` or `link` for the modifiers. The `tag` field is a tag list
-- (see above).
-- (see above).
function export.parse_form_and_modifiers(form_with_modifiers)
function export.parse_form_and_modifiers(form_with_modifiers)
Line 129: Line 140:
if prefix == "tag" then
if prefix == "tag" then
if retval.tag then
if retval.tag then
table.insert(retval.tag, content)
insert(retval.tag, content)
else
else
retval.tag = {content}
retval.tag = {content}
end
end
elseif prefix == "q" or prefix == "qq" or prefix == "tr" or prefix == "link" then
elseif prefix == "q" or prefix == "qq" or prefix == "tr" or prefix == "link" or prefix == "id" or prefix == "g" or prefix == "alt" then
if retval[prefix] then
if retval[prefix] then
error(("Duplicate modifier '%s' in data module form, already saw value '%s': %s"):format(prefix,
error(("Duplicate modifier '%s' in data module form, already saw value '%s': %s"):format(prefix,
Line 173: Line 184:
function export.numbers_greater_than(a, b)
function export.numbers_greater_than(a, b)
return export.numbers_less_than(b, a)
return export.numbers_less_than(b, a)
end
local POSITIONAL_DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
local MAX_SAFE_INTEGER = 9007199254740991
local function get_digit_maps(base, digit_alphabet)
digit_alphabet = digit_alphabet or POSITIONAL_DIGITS
if #digit_alphabet < base then
error(("Number system base %s exceeds available digits in digit alphabet"):format(base))
end
local digit_to_value = {}
local value_to_digit = {}
for i = 1, base do
local digit = digit_alphabet:sub(i, i)
digit_to_value[digit] = i - 1
value_to_digit[i - 1] = digit
end
return digit_to_value, value_to_digit
end
local function decimal_to_base(num, base, value_to_digit)
if num == 0 then
return value_to_digit[0]
end
local parts = {}
while num > 0 do
insert(parts, 1, value_to_digit[num % base])
num = math.floor(num / base)
end
return concat(parts)
end
local function parse_positional_to_number(key, base, digit_to_value)
local n = 0
for i = 1, #key do
local value = digit_to_value[key:sub(i, i)]
if not value then
return nil
end
n = n * base + value
if n > MAX_SAFE_INTEGER then
return nil
end
end
return n
end
local function safe_integer_power(base, exp)
local result = 1
for _ = 1, exp do
if result > MAX_SAFE_INTEGER / base then
return nil
end
result = result * base
end
return result
end
local function normalize_positional_key(raw_number, opts)
local base = opts.base
local case_insensitive = opts.case_insensitive
local digit_to_value = opts.digit_to_value
local value_to_digit = opts.value_to_digit
local strip_separator = opts.strip_separator
local interpret_plain_decimal = opts.interpret_plain_decimal
local zero_digit = opts.zero_digit
local key
if type(raw_number) == "number" then
if raw_number < 0 or raw_number % 1 ~= 0 then
error(("Non-negative integer expected for positional number system, got '%s'"):format(raw_number))
end
key = decimal_to_base(raw_number, base, value_to_digit)
else
key = tostring(raw_number)
if strip_separator and strip_separator ~= "" then
key = gsub(key, strip_separator, "")
end
-- For compatibility with existing data/modules, plain decimal-digit *input* can be interpreted
-- as decimal and then converted into the configured positional key space.
if interpret_plain_decimal and key:find("^%d+$") then
key = decimal_to_base(tonumber(key), base, value_to_digit)
end
end
if case_insensitive then
key = upper(key)
end
if key == "" then
return zero_digit
end
for i = 1, #key do
local digit = key:sub(i, i)
if not digit_to_value[digit] then
error(("Extraneous characters in number: '%s'"):format(key))
end
end
key = key:gsub("^" .. zero_digit .. "+", "")
return key == "" and zero_digit or key
end
end


-- Given a number form, convert it to its independent (un-affixed) form. This only makes sense for certain languages
-- Given a number form, convert it to its independent (un-affixed) form. This only makes sense for certain languages
-- where there is a difference between independent and affixed forms of numerals. Currently the only such language
-- where there is a difference between independent and affixed forms of numerals. Currently the only such language
-- is Swahili, where e.g. the cardinal number form for 3 is affixed [[wikt:-tatu]], independent [[wikt:tatu]], and the ordinal
-- is Swahili, where e.g. the cardinal number form for 3 is affixed [[-tatu]], independent [[tatu]], and the ordinal
-- number form is [[wikt:-a tatu]], independent [[wikt:tatu]]. We rely on a set of Lua pattern substitutions to convert from
-- number form is [[-a tatu]], independent [[tatu]]. We rely on a set of Lua pattern substitutions to convert from
-- affixed to independent form.
-- affixed to independent form.
--
--
Line 188: Line 301:
for _, entry in ipairs(m_data.unaffix) do
for _, entry in ipairs(m_data.unaffix) do
local from, to = unpack(entry)
local from, to = unpack(entry)
form = mw.ustring.gsub(form, from, to)
form = gsub(form, from, to)
end
end
return form
return form
Line 194: Line 307:


-- Convert the given number form (taken from the data for `lang`, after parsing the form for modifiers and stripping
-- Convert the given number form (taken from the data for `lang`, after parsing the form for modifiers and stripping
-- the modifiers) to an entry name. The form may have links and/or accent/length marks that need to be stripped.
-- the modifiers) to the stripped-text version of the form. The form may have links and/or accent/length marks that need
local function form_to_entry_name(form, lang)
-- to be stripped.
return (lang:makeEntryName(m_links.remove_links(form)))
local function form_to_stripped_form(form, lang)
return lang:stripDiacritics(m_links.remove_links(form))
end
end


Line 208: Line 322:
return true
return true
end
end
local entry_name = form_to_entry_name(formobj.form, lang)
local stripped_form = form_to_stripped_form(formobj.form, lang)
return entry_name == pagename or maybe_unaffix(m_data, entry_name) == pagename
if stripped_form == pagename or maybe_unaffix(m_data, stripped_form) == pagename then
return true
end
if formobj.alt then
local stripped_alt = form_to_stripped_form(formobj.alt, lang)
if stripped_alt == pagename or maybe_unaffix(m_data, stripped_alt) == pagename then
return true
end
end
return false
end
end


-- Given the data for a language and a number (which should be in string representation), find the next and previous
-- Given the data for a language and a number (which should be in string representation), find the next and previous
-- numbers to display (in string representation).
-- numbers to display (in string representation).
local function get_next_and_prev_keys(m_data, numstr)
local function get_next_and_prev_keys(m_data, numstr, strategy, lookup_data)
local numdata = export.lookup_data(m_data, numstr)
local numdata = lookup_data(numstr)
if not numdata then
if not numdata then
return nil, nil
return nil, nil
Line 224: Line 347:
-- Find the next/previous numbers by sorting all the keys and locating the number in question among them.
-- Find the next/previous numbers by sorting all the keys and locating the number in question among them.
local sorted_list = {}
local sorted_list = {}
local seen = {}
local index = 1
local index = 1
for key, _ in pairs(m_data.numbers) do
for key, _ in pairs(m_data.numbers) do
sorted_list[index] = key
local normalized_key = strategy.normalize_data_key(key)
index = index + 1
if not seen[normalized_key] then
seen[normalized_key] = true
sorted_list[index] = normalized_key
index = index + 1
end
end
end


table.sort(sorted_list, export.numbers_less_than)
sort(sorted_list, strategy.compare_keys)


-- We could binary search to save time, but given that we already sort, which is supra-linear, it won't
-- We could binary search to save time, but given that we already sort, which is supra-linear, it won't
-- matter to search linearly.
-- matter to search linearly.
for i, key in ipairs(sorted_list) do
for i, key in ipairs(sorted_list) do
if export.format_fixed(key) == numstr then
if key == numstr then
nextnum = nextnum or sorted_list[i + 1]
nextnum = nextnum or sorted_list[i + 1]
prevnum = prevnum or sorted_list[i - 1]
prevnum = prevnum or sorted_list[i - 1]
Line 244: Line 372:


if nextnum then
if nextnum then
nextnum = export.format_fixed(nextnum)
nextnum = strategy.normalize_data_key(nextnum)
end
end
if prevnum then
if prevnum then
prevnum = export.format_fixed(prevnum)
prevnum = strategy.normalize_data_key(prevnum)
end
end


Line 265: Line 393:
-- with different types, e.g. the ordinal and fractional forms for a given number are the same), but will
-- with different types, e.g. the ordinal and fractional forms for a given number are the same), but will
-- throw an error if different numbers are seen.
-- throw an error if different numbers are seen.
table.insert(retval, {num, typ})
insert(retval, {num, typ})
end
end
end
end
Line 284: Line 412:


return retval
return retval
end
local function index_of_number_type(t, type)
for i, subtable in ipairs(t) do
if subtable.key == type then
return i
end
end
end
end


Line 298: Line 418:
-- the numeral type that the form should appear before or after.
-- the numeral type that the form should appear before or after.
-- The transformations are applied in order.
-- The transformations are applied in order.
local function add_form_types(additional_types)
local function add_form_types(form_types, additional_types)
local types = require("Module:table").deepcopy(form_types)
local types = require("Module:table").deepCopy(form_types)
for _, type in ipairs(additional_types) do
for _, additional_type in ipairs(additional_types) do
type = require("Module:table").shallowcopy(type)
if not (additional_type.before or additional_type.after) then
local i
insert(types, additional_type)
if type.before or type.after then
else
i = index_of_number_type(types, type.before or type.after)
if additional_type.before and additional_type.after then
end
error("The form type '" .. additional_type.key .. "' is specifying both before and after, which is not allowed")
-- For now, simply log an error message
end
-- if the "before" or "after" number type was not found,
 
-- and insert the number type at the end.
local anchor, index = additional_type.before or additional_type.after
if i then
 
if type.before then
for i, another_type in ipairs(types) do
table.insert(types, i - 1, type)
if another_type.key == anchor then
else
index = i
table.insert(types, i + 1, type)
break
end
end
 
if index and additional_type.after then
index = index + 1
end
end
else
 
table.insert(types, type)
additional_type = require("Module:table").shallowCopy(additional_type)
if type.before or type.after then
additional_type.before, additional_type.after = nil, nil
if not index then
mw.log("Number type "
mw.log("Number type "
.. (type.before or type.after)
.. (additional_type.before or additional_type.after)
.. " was not found.")
.. " was not found.")
insert(types, additional_type)
else
insert(types, index, additional_type)
end
end
end
end
type.before, type.after = nil, nil
end
end
return types
return types
Line 330: Line 459:
-- Return all form types for the language in question, in order.
-- Return all form types for the language in question, in order.
function export.get_number_types(m_data)
function export.get_number_types(m_data)
local final_form_types = form_types
local form_types = default_form_types
if m_data.additional_number_types then
if m_data.additional_number_types then
final_form_types = add_form_types(m_data.additional_number_types)
return add_form_types(form_types, m_data.additional_number_types)
else
return form_types
end
end
return final_form_types
end
end


Line 342: Line 472:
return number_type.display
return number_type.display
else
else
return (number_type.key:gsub("^.", string.upper):gsub("_", " "))
return (number_type.key:gsub("^.", upper):gsub("_", " "))
end
end
end
end
Line 355: Line 485:
local parts = { numstr:sub(-start) }
local parts = { numstr:sub(-start) }
for i = start + 1, #numstr, group do
for i = start + 1, #numstr, group do
table.insert(parts, 1, numstr:sub(-(i + group - 1), -i))
insert(parts, 1, numstr:sub(-(i + group - 1), -i))
end
end


return table.concat(parts, separator)
return concat(parts, separator)
end
end


Line 398: Line 528:
end
end


return numstr:gsub("[0-9]", function (digit)
return numstr:gsub("%d", function (digit)
return mw.ustring.char(zero_codepoint + tonumber(digit))
return u(zero_codepoint + tonumber(digit))
end)
end)
end
end
Line 424: Line 554:
mantissa = ""
mantissa = ""
elseif #kstr == 1 then
elseif #kstr == 1 then
mantissa = kstr .. " x "
mantissa = kstr .. " × "
else
else
mantissa = kstr:gsub("^([0-9])", "%1.") .. " x "
mantissa = kstr:gsub("^([0-9])", "%1.") .. " × "
end
end
local scientific = mantissa .. exponent
local scientific = mantissa .. exponent
Line 434: Line 564:
return fixed .. " (" .. scientific .. ")"
return fixed .. " (" .. scientific .. ")"
end
end
end
local function derive_related_numbers_decimal(cur_num, cur_data, next_num, prev_num, lookup_data)
local k, m
if cur_num == "0" then
k = 0
m = 1
else
local kstr, mstr = cur_num:match("^([0-9]*[1-9])(0*)$")
if not kstr then
error("Internal error: Unable to match number '" .. cur_num .. "'")
elseif #kstr > 15 then
error("Can't handle number with more than 15 digits before the trailing zeros: '" .. cur_num .. "'")
end
k = tonumber(kstr)
m = #mstr
end
local function make_greater_power_of_ten(power)
return cur_num .. ("0"):rep(power)
end
local function make_lesser_power_of_ten(power)
local desired_zeros = m - power
if desired_zeros < 0 then
return nil
end
return k .. ("0"):rep(desired_zeros)
end
local next_outer_data, prev_outer_data
local next_outer_num, prev_outer_num = cur_data.next_outer, cur_data.prev_outer
local power_of_10_sequence = { 1, 3, 2, 6 }
if next_outer_num then
next_outer_data = lookup_data(next_outer_num, "next outer")
else
local function try(num)
local data = (not next_num or export.numbers_greater_than(num, next_num)) and lookup_data(num) or nil
if data then
next_outer_num = num
next_outer_data = data
end
return data
end
if not try((k + 1) .. ("0"):rep(m)) and k == 1 then
for _, power_of_10 in ipairs(power_of_10_sequence) do
if try(make_greater_power_of_ten(power_of_10)) then
break
end
end
end
end
if prev_outer_num then
prev_outer_data = lookup_data(prev_outer_num, "previous outer")
else
local function try(num)
local data = (not prev_num or export.numbers_less_than(num, prev_num)) and lookup_data(num) or nil
if data then
prev_outer_num = num
prev_outer_data = data
end
return data
end
if not (k == 0 or m == 0) then
local num_to_try
if k == 1 then
num_to_try = "9" .. ("0"):rep(m - 1)
else
num_to_try = (k - 1) .. ("0"):rep(m)
end
if not try(num_to_try) and k == 1 then
for _, power_of_10 in ipairs(power_of_10_sequence) do
local power_num_to_try = make_lesser_power_of_ten(power_of_10)
if power_num_to_try and try(power_num_to_try) then
break
end
end
end
end
end
local upper_data, lower_data
local upper_num, lower_num = cur_data.upper, cur_data.lower
if upper_num then
upper_data = lookup_data(upper_num, "upper")
else
upper_num = make_greater_power_of_ten(1)
if upper_num == next_num or cur_num == "0" then
upper_num = nil
else
upper_data = lookup_data(upper_num)
end
end
if lower_num then
lower_data = lookup_data(lower_num, "lower")
elseif not (k == 0 or m == 0) then
lower_num = make_lesser_power_of_ten(1)
if lower_num == prev_num then
lower_num = nil
else
lower_data = lookup_data(lower_num)
end
end
return next_outer_num, next_outer_data, prev_outer_num, prev_outer_data, upper_num, upper_data, lower_num, lower_data
end
local function derive_related_numbers_positional(strategy, cur_num, cur_data, next_num, prev_num, lookup_data)
local zero_digit = strategy.zero_digit
local base = strategy.base
local power_base = strategy.power_base
local value_to_digit = strategy.value_to_digit
local digit_to_value = strategy.digit_to_value
local power_sequence = strategy.power_sequence
local significant = cur_num:gsub(zero_digit .. "+$", "")
local m = #cur_num - #significant
local kstr = significant == "" and zero_digit or significant
local k_value = parse_positional_to_number(kstr, base, digit_to_value)
local next_outer_data, prev_outer_data
local next_outer_num, prev_outer_num = cur_data.next_outer, cur_data.prev_outer
local function value_to_key(value)
return decimal_to_base(value, base, value_to_digit)
end
local function make_greater_power(power)
if not k_value then
return nil
end
local factor = safe_integer_power(power_base, power)
if not factor then
return nil
end
local cur_value = parse_positional_to_number(cur_num, base, digit_to_value)
if not cur_value or cur_value > MAX_SAFE_INTEGER / factor then
return nil
end
return value_to_key(cur_value * factor)
end
local function make_lesser_power(power)
local factor = safe_integer_power(power_base, power)
if not factor then
return nil
end
local cur_value = parse_positional_to_number(cur_num, base, digit_to_value)
if not cur_value or cur_value % factor ~= 0 then
return nil
end
return value_to_key(cur_value / factor)
end
if next_outer_num then
next_outer_data = lookup_data(next_outer_num, "next outer")
else
local function try(num)
local data = (not next_num or strategy.compare_keys(next_num, num)) and lookup_data(num) or nil
if data then
next_outer_num = num
next_outer_data = data
end
return data
end
if k_value then
if not try(decimal_to_base(k_value + 1, base, value_to_digit) .. zero_digit:rep(m)) and k_value == 1 then
for _, power in ipairs(power_sequence) do
if try(make_greater_power(power)) then
break
end
end
end
end
end
if prev_outer_num then
prev_outer_data = lookup_data(prev_outer_num, "previous outer")
else
local function try(num)
local data = (not prev_num or strategy.compare_keys(num, prev_num)) and lookup_data(num) or nil
if data then
prev_outer_num = num
prev_outer_data = data
end
return data
end
if not (cur_num == zero_digit or m == 0) and k_value then
local num_to_try
if k_value == 1 then
num_to_try = value_to_digit[base - 1] .. zero_digit:rep(m - 1)
else
num_to_try = decimal_to_base(k_value - 1, base, value_to_digit) .. zero_digit:rep(m)
end
if not try(num_to_try) and k_value == 1 then
for _, power in ipairs(power_sequence) do
local power_num_to_try = make_lesser_power(power)
if power_num_to_try and try(power_num_to_try) then
break
end
end
end
end
end
local upper_data, lower_data
local upper_num, lower_num = cur_data.upper, cur_data.lower
if upper_num then
upper_data = lookup_data(upper_num, "upper")
else
upper_num = make_greater_power(1)
if upper_num == next_num or cur_num == zero_digit then
upper_num = nil
else
upper_data = lookup_data(upper_num)
end
end
if lower_num then
lower_data = lookup_data(lower_num, "lower")
elseif not (cur_num == zero_digit or m == 0) then
lower_num = make_lesser_power(1)
if lower_num == prev_num then
lower_num = nil
else
lower_data = lookup_data(lower_num)
end
end
return next_outer_num, next_outer_data, prev_outer_num, prev_outer_data, upper_num, upper_data, lower_num, lower_data
end
local function create_positional_strategy(config)
local base = config.base
local digit_alphabet = config.digit_alphabet or POSITIONAL_DIGITS
local case_insensitive = config.case_insensitive
if case_insensitive == nil then
case_insensitive = base <= 36
end
local strip_separator = config.strip_separator or ","
local digit_to_value, value_to_digit = get_digit_maps(base, digit_alphabet)
local zero_digit = value_to_digit[0]
local display_separator = config.display_separator
local display_group = config.display_group
local display_group_start = config.display_group_start
local display_indic = config.display_indic
local display_keys_as_decimal = config.display_keys_as_decimal
if display_keys_as_decimal == nil then
display_keys_as_decimal = true
end
local power_sequence = config.power_sequence or {1, 3, 2, 6}
local power_base = config.power_base or 10
local strategy = {
id = config.id or ("base" .. base),
base = base,
power_base = power_base,
digit_to_value = digit_to_value,
value_to_digit = value_to_digit,
zero_digit = zero_digit,
power_sequence = power_sequence,
}
function strategy.normalize_data_key(raw_number)
return normalize_positional_key(raw_number, {
base = base,
case_insensitive = case_insensitive,
digit_to_value = digit_to_value,
value_to_digit = value_to_digit,
strip_separator = nil,
interpret_plain_decimal = false,
zero_digit = zero_digit,
})
end
function strategy.normalize_input(raw_number)
return normalize_positional_key(raw_number, {
base = base,
case_insensitive = case_insensitive,
digit_to_value = digit_to_value,
value_to_digit = value_to_digit,
strip_separator = strip_separator,
interpret_plain_decimal = true,
zero_digit = zero_digit,
})
end
function strategy.normalize_input_candidates(raw_number)
local key_as_system = normalize_positional_key(raw_number, {
base = base,
case_insensitive = case_insensitive,
digit_to_value = digit_to_value,
value_to_digit = value_to_digit,
strip_separator = strip_separator,
interpret_plain_decimal = false,
zero_digit = zero_digit,
})
local key_as_decimal = normalize_positional_key(raw_number, {
base = base,
case_insensitive = case_insensitive,
digit_to_value = digit_to_value,
value_to_digit = value_to_digit,
strip_separator = strip_separator,
interpret_plain_decimal = true,
zero_digit = zero_digit,
})
if key_as_system == key_as_decimal then
return {key_as_system}
end
return {key_as_system, key_as_decimal}
end
function strategy.lookup_data(m_data, key)
key = strategy.normalize_data_key(key)
local direct = m_data.numbers[key]
if direct then
return direct
end
local parsed = parse_positional_to_number(key, base, digit_to_value)
return parsed and m_data.numbers[parsed] or nil
end
function strategy.compare_keys(a, b)
a = strategy.normalize_data_key(a)
b = strategy.normalize_data_key(b)
if #a ~= #b then
return #a < #b
end
return a < b
end
function strategy.format_key_for_display(key)
key = strategy.normalize_data_key(key)
if display_keys_as_decimal then
local decimal_value = parse_positional_to_number(key, base, digit_to_value)
if decimal_value then
return export.format_number_for_display(decimal_value)
end
end
if display_separator then
if display_indic then
return add_separator(key, display_separator, 2, 3)
end
return add_separator(key, display_separator, display_group or 3, display_group_start)
end
return key
end
function strategy.derive_related_numbers(cur_num, cur_data, next_num, prev_num, lookup_data)
return derive_related_numbers_positional(strategy, cur_num, cur_data, next_num, prev_num, lookup_data)
end
return strategy
end
decimal_strategy = {
id = "decimal",
base = 10,
zero_digit = "0",
power_sequence = {1, 3, 2, 6},
}
function decimal_strategy.normalize_data_key(raw_number)
return export.format_fixed(raw_number)
end
function decimal_strategy.normalize_input(raw_number)
local normalized = tostring(raw_number):gsub(",", "")
if not normalized:find("^%d+$") then
error("Extraneous characters in parameter 2: should be decimal number (integer): '" .. normalized .. "'")
end
return normalized
end
function decimal_strategy.normalize_input_candidates(raw_number)
return {decimal_strategy.normalize_input(raw_number)}
end
function decimal_strategy.lookup_data(m_data, key)
return export.lookup_data(m_data, key)
end
function decimal_strategy.compare_keys(a, b)
return export.numbers_less_than(a, b)
end
function decimal_strategy.format_key_for_display(key)
return export.format_number_for_display(key)
end
function decimal_strategy.derive_related_numbers(cur_num, cur_data, next_num, prev_num, lookup_data)
return derive_related_numbers_decimal(cur_num, cur_data, next_num, prev_num, lookup_data)
end
local function resolve_number_system(m_data)
local config = m_data.number_system
if not config then
return decimal_strategy
end
if type(config) == "string" then
config = {id = config}
end
if config.id == "decimal" then
return decimal_strategy
end
if config.id == "base20" then
config.base = config.base or 20
elseif config.id == "base60" then
config.base = config.base or 60
elseif config.id == "positional" then
-- base should be explicitly given or derived below.
elseif config.id == "custom" then
if not (config.module and config.func) then
error("custom number_system requires both module and func")
end
local custom_strategy = require("Module:" .. config.module)[config.func](config)
return custom_strategy
end
config.base = config.base or tonumber(config.id and config.id:match("^base(%d+)$"))
if not config.base then
error("Unsupported number_system id '" .. tostring(config.id) .. "'")
end
if config.base < 2 then
error("number_system base must be >= 2")
end
return create_positional_strategy(config)
end
end


Line 439: Line 1,000:
-- keys of tables.
-- keys of tables.
local function tag_list_to_combined_tag(tag_list)
local function tag_list_to_combined_tag(tag_list)
return table.concat(tag_list, "|||")
return concat(tag_list, "|||")
end
end


Line 459: Line 1,020:
for _, form in ipairs(forms) do
for _, form in ipairs(forms) do
local formobj = export.parse_form_and_modifiers(form)
local formobj = export.parse_form_and_modifiers(form)
table.insert(seen_forms, formobj)
insert(seen_forms, formobj)
local combined_tag = formobj.tag and tag_list_to_combined_tag(formobj.tag) or ""
local combined_tag = formobj.tag and tag_list_to_combined_tag(formobj.tag) or ""
if not forms_by_tag[combined_tag] then
if not forms_by_tag[combined_tag] then
table.insert(seen_tags, combined_tag)
insert(seen_tags, combined_tag)
forms_by_tag[combined_tag] = {}
forms_by_tag[combined_tag] = {}
combined_tags_to_tag_lists[combined_tag] = formobj.tag or {}
combined_tags_to_tag_lists[combined_tag] = formobj.tag or {}
end
end
table.insert(forms_by_tag[combined_tag], formobj)
insert(forms_by_tag[combined_tag], formobj)
end
end


Line 475: Line 1,036:
function export.format_formobj(formobj, m_data, lang)
function export.format_formobj(formobj, m_data, lang)
local left_q = formobj.q and require("Module:qualifier").format_qualifier(formobj.q) .. " " or ""
local left_q = formobj.q and require("Module:qualifier").format_qualifier(formobj.q) .. " " or ""
local right_q = formobj.qq and " " .. require("Module:qualifier").format_qualifier(formobj.qq) or ""
local right_q = ((formobj.g and " " .. require("Module:gender and number").format_genders(split(formobj.g, ",")) or "")
return left_q .. m_links.full_link({
.. (formobj.qq and " " .. require("Module:qualifier").format_qualifier(formobj.qq) or ""))
lang = lang, term = maybe_unaffix(m_data, formobj.form), alt = formobj.form, tr = formobj.tr,
local term = maybe_unaffix(m_data, formobj.form)
}) .. right_q
local alt = formobj.alt
if not alt and term ~= formobj.form then
alt = formobj.form
end
return left_q .. m_links.full_link{
lang = lang, term = term, alt = alt, tr = formobj.tr, id = formobj.id,
} .. right_q
end
end


Line 486: Line 1,053:


local params = {
local params = {
[1] = {required = true},
[1] = {required = true, type = "language", default = "und"},
[2] = {},
[2] = true,
["pagename"] = {},
["pagename"] = true,
["type"] = {},
["type"] = true,
}
}


local parent_args = frame:getParent().args
local parent_args = frame:getParent().args
if parent_args.pagename then
track("show-box-pagename")
end
local args = require("Module:parameters").process(parent_args, params, nil, "number list", "show_box")
local args = require("Module:parameters").process(parent_args, params, nil, "number list", "show_box")


local langcode = args[1] or "und"
local lang = args[1]
local lang = require("Module:languages").getByCode(langcode, "1")
local langcode = lang:getCode()


-- Get the data from the data module. Some modules (e.g. currently [[wikt:Module:number list/data/ka]]) have to be
-- Get the data from the data module. Some modules (e.g. currently [[Module:number list/data/ka]]) have to be
-- loaded with require() because the exported numbers table has a metatable.
-- loaded with require() because the exported numbers table has a metatable.
local module_name = export.get_data_module_name(langcode, "must exist")
local module_name = export.get_data_module_name(langcode)
local m_data = require(module_name)
local m_data = require(module_name)
local number_system = resolve_number_system(m_data)


local pagename = args.pagename or (mw.title.getCurrentTitle().nsText == "Reconstruction" and "*" or "") .. mw.title.getCurrentTitle().subpageText
local pagename = args.pagename or (mw.title.getCurrentTitle().nsText == "Reconstruction" and "*" or "") .. mw.loadData("Module:headword/data").pagename
-- Resolve any risky characters which makeEntryName will escape, so that any matches involving them work correctly.
pagename = (lang:makeEntryName(pagename))
local cur_type = args.type
local cur_type = args.type


-- We represent all numbers as strings in this function to deal with the limited precision inherent in Lua numbers.
-- We represent all numbers as strings in this function to deal with the limited precision inherent in Lua numbers.
-- These large numbers do occur, such as 100 trillion ([[wikt:རབ་བཀྲམ་ཆེན་པོ]]), 1 sextillion, etc. Lua represents all
-- These large numbers do occur, such as 100 trillion ([[རབ་བཀྲམ་ཆེན་པོ]]), 1 sextillion, etc. Lua represents all
-- numbers as 64-bit floats, meaning that some numbers above 2^53 cannot be represented exactly. The first power of
-- numbers as 64-bit floats, meaning that some numbers above 2^53 cannot be represented exactly. The first power of
-- 10 that cannot be represented exactly is 10^22 (ten sextillion in short scale, ten thousand trillion in long
-- 10 that cannot be represented exactly is 10^22 (ten sextillion in short scale, ten thousand trillion in long
Line 529: Line 1,098:
end
end
for _, num_and_type in ipairs(nums_and_types) do
for _, num_and_type in ipairs(nums_and_types) do
local num, typ = unpack(num_and_type)
local num = num_and_type[1]
num = export.format_fixed(num)
num = number_system.normalize_data_key(num)
if cur_num and num ~= cur_num then
if cur_num and num ~= cur_num then
local errparts = {}
local errparts = {}
for _, num_and_type in ipairs(nums_and_types) do
for _, num_and_type in ipairs(nums_and_types) do
local num, typ = unpack(num_and_type)
local num, typ = unpack(num_and_type)
table.insert(errparts, ("%s (%s)"):format(num, typ))
num = number_system.normalize_data_key(num)
insert(errparts, ("%s (%s)"):format(num, typ))
end
end
error("The current page name '" .. pagename .. "' matches the spelling of multiple numbers in [[" ..
error("The current page name '" .. pagename .. "' matches the spelling of multiple numbers in [[" ..
module_name .. "]]: " .. table.concat(errparts, ",") .. ". Please specify the number explicitly.")
module_name .. "]]: " .. concat(errparts, ",") .. ". Please specify the number explicitly.")
else
else
cur_num = num
cur_num = num
Line 545: Line 1,115:
end
end


cur_num = cur_num:gsub(",", "") -- remove thousands separators
local function candidate_matches_pagename(candidate_num)
if not cur_num:find("^%d+$") then
local candidate_data = number_system.lookup_data(m_data, candidate_num)
error("Extraneous characters in parameter 2: should be decimal number (integer): '" .. cur_num .. "'")
if not candidate_data then
return false
end
for numtype, forms in pairs(candidate_data) do
if not non_form_types[numtype] and (not cur_type or numtype == cur_type) then
local form_list = type(forms) == "table" and forms or {forms}
for _, form in ipairs(form_list) do
local formobj = export.parse_form_and_modifiers(form)
if form_equals_pagename(formobj, pagename, m_data, lang) then
return true
end
end
end
end
return false
end
 
local cur_num_candidates = number_system.normalize_input_candidates and
number_system.normalize_input_candidates(cur_num) or
{number_system.normalize_input(cur_num)}
cur_num = cur_num_candidates[1]
if #cur_num_candidates > 1 then
for _, candidate in ipairs(cur_num_candidates) do
if candidate_matches_pagename(candidate) then
cur_num = candidate
break
end
end
end
end


Line 553: Line 1,150:
-- param_for_error is given).
-- param_for_error is given).
local function lookup_data(numstr, param_for_error)
local function lookup_data(numstr, param_for_error)
local retval = export.lookup_data(m_data, numstr)
numstr = number_system.normalize_data_key(numstr)
local retval = number_system.lookup_data(m_data, numstr)
if not retval and param_for_error then
if not retval and param_for_error then
error(('The %s number "%s" specified in the "numbers" table entry for "%s" cannot be found in '
error(('The %s number "%s" specified in the "numbers" table entry for "%s" cannot be found in '
Line 577: Line 1,175:


local form_types = export.get_number_types(m_data)
local form_types = export.get_number_types(m_data)
 
-- LONG COMMENT EXPLAINING TAG HANDLING:
-- LONG COMMENT EXPLAINING TAG HANDLING:
--
--
Line 625: Line 1,223:
local numeral = cur_data[form_type.key]
local numeral = cur_data[form_type.key]
if numeral then
if numeral then
local numerals
local seen_forms, forms_by_tag, seen_tags, combined_tags_to_tag_lists = export.group_numeral_forms_by_tag(
if type(numeral) == "string" then
type(numeral) == "table" and numeral or {numeral}
numerals = {numeral}
)
elseif type(numeral) == "table" then
numerals = numeral
end
 
local seen_forms, forms_by_tag, seen_tags, combined_tags_to_tag_lists = export.group_numeral_forms_by_tag(numerals)
forms_by_tag_per_form_type[form_type] = forms_by_tag
forms_by_tag_per_form_type[form_type] = forms_by_tag
seen_tags_per_form_type[form_type] = seen_tags
seen_tags_per_form_type[form_type] = seen_tags
Line 670: Line 1,263:
local common1 = set_intersection(cur_tag_set, list_to_set(tag_list1))
local common1 = set_intersection(cur_tag_set, list_to_set(tag_list1))
local common2 = set_intersection(cur_tag_set, list_to_set(tag_list2))
local common2 = set_intersection(cur_tag_set, list_to_set(tag_list2))
if #common1 ~= #common2 then
local n_common1, n_common2 = set_size(common1), set_size(common2)
return #common1 < #common2
if n_common1 ~= n_common2 then
return n_common1 > n_common2 -- larger overlap with current tag list first
end
end
-- Then compare inversely by number of tags not in common with the current tag list (which is equivalent to
-- When overlap ties, shorter tag lists first (untagged default before explicit <tag:...> rows).
-- comparing by total number of tags, since tags should be distinct).
if #tag_list1 ~= #tag_list2 then
if #tag_list1 ~= #tag_list2 then
return #tag_list1 > #tag_list2
return #tag_list1 < #tag_list2
end
end
-- Finally, compare by the original ordering in the number data, but if a tag is the same as the current
-- Finally, compare by the original ordering in the number data, but if a tag is the same as the current
Line 684: Line 1,277:
return index1 < index2
return index1 < index2
end
end
table.sort(combined_tags, compare_tags)
sort(combined_tags, compare_tags)
end
end


Line 697: Line 1,290:
local pagename_among_forms = false
local pagename_among_forms = false
for _, formobj in ipairs(forms_by_tag[tag]) do
for _, formobj in ipairs(forms_by_tag[tag]) do
table.insert(formatted_tag_forms, export.format_formobj(formobj, m_data, lang))
insert(formatted_tag_forms, export.format_formobj(formobj, m_data, lang))
if form_equals_pagename(formobj, pagename, m_data, lang) then
if form_equals_pagename(formobj, pagename, m_data, lang) then
pagename_among_forms = true
pagename_among_forms = true
Line 705: Line 1,298:
if tag ~= "" then
if tag ~= "" then
local tag_list = combined_tags_to_tag_lists[tag]
local tag_list = combined_tags_to_tag_lists[tag]
tag = table.concat(tag_list, " / ")
tag = concat(tag_list, " / ")
end
end
local displayed_number_type = export.display_number_type(form_type) .. (tag == "" and "" or (" (%s)"):format(tag))
local displayed_number_type = export.display_number_type(form_type) .. (tag == "" and "" or (" (%s)"):format(tag))
Line 712: Line 1,305:
end
end


table.insert(formatted_forms, " &nbsp;&nbsp;&nbsp; ''" .. displayed_number_type .. "'': " ..
insert(formatted_forms, " &nbsp;&nbsp;&nbsp; ''" .. displayed_number_type .. "'': " ..
table.concat(formatted_tag_forms, ", "))
concat(formatted_tag_forms, ", "))
end
end


Line 724: Line 1,317:


-- Current number in header
-- Current number in header
local cur_display = export.format_number_for_display(cur_num)
local cur_display = number_system.format_key_for_display(cur_num)


local numeral
local numeral
Line 730: Line 1,323:
numeral = export.generate_non_arabic_numeral(m_data.numeral_config, cur_num)
numeral = export.generate_non_arabic_numeral(m_data.numeral_config, cur_num)
elseif cur_data["numeral"] then
elseif cur_data["numeral"] then
numeral = export.format_fixed(cur_data["numeral"])
numeral = number_system.normalize_data_key(cur_data["numeral"])
end
end


if numeral then
if numeral then
cur_display = full_link({lang = lang, alt = numeral, tr = "-"}) .. "<br/><span style=\"font-size: smaller;\">" .. cur_display .. "</span>"
cur_display = full_link{lang = lang, alt = numeral, tr = "-"} .. "<br/><span style=\"font-size: smaller;\">" .. cur_display .. "</span>"
end
end


Line 750: Line 1,343:
--    question, number not considering a number if it's the same as the next/previous number.
--    question, number not considering a number if it's the same as the next/previous number.


local next_num, prev_num = get_next_and_prev_keys(m_data, cur_num)
local next_num, prev_num = get_next_and_prev_keys(m_data, cur_num, number_system, lookup_data)
local next_data = next_num and lookup_data(next_num, "next")
local next_data = next_num and lookup_data(next_num, "next")
local prev_data = prev_num and lookup_data(prev_num, "previous")
local prev_data = prev_num and lookup_data(prev_num, "previous")
 
local next_outer_num, next_outer_data, prev_outer_num, prev_outer_data, upper_num, upper_data, lower_num, lower_data =
--------- Decompose number into mantissa (k) and exponent (m). ----------
number_system.derive_related_numbers(cur_num, cur_data, next_num, prev_num, lookup_data)
 
local k, m
if cur_num == "0" then
k = 0
m = 1
else
local kstr, mstr = cur_num:match("^([0-9]*[1-9])(0*)$")
if not kstr then
error("Internal error: Unable to match number '" .. cur_num .. "'")
elseif #kstr > 15 then
-- This is because some numbers with 16 or more digits can't be represented exactly.
error("Can't handle number with more than 15 digits before the trailing zeros: '" .. cur_num .. "'")
end
k = tonumber(kstr)
m = #mstr
end
 
-- Find the next greater power of 10 for cur_num, up to 10^6. `try` should look up the data for a power of 10
-- and return it if it's available and the number passes any checks, otherwise nil.
local function make_greater_power_of_ten(power)
return cur_num .. string.rep("0", power)
end
 
-- Find the next lesser power of 10 for cur_num, up to 10^6. `try` should look up the data for a power of 10
-- and return it if it's available and the number passes any checks, otherwise nil.
local function make_lesser_power_of_ten(power)
local desired_zeros = m - power
if desired_zeros < 0 then
return nil
end
return k .. string.rep("0", desired_zeros)
end
 
 
local next_outer_data, prev_outer_data
local next_outer_num, prev_outer_num = cur_data.next_outer, cur_data.prev_outer
 
-- When trying to find then next/previous outer numbers, first, if the base-10 mantissa is not 1 or 0, we add 1 to
-- or subtract 1 from the mantissa, keeping the same number of zeros. Hence, for 300, we try 400 for the next outer,
-- 200 for the previous outer. For 900, we try 1000 for the next outer and 800 for the previous outer. If the
-- mantissa is 1, the next outer is computed the same but for the previous outer we use 9 followed by one fewer
-- zero. Hence, for 100 we try 200 for the next outer but 90 for the previous outer. If the mantissa is 0 (i.e. the
-- entire number is 0), we try 10 for the next outer, and have no previous outer.
--
-- Next, if the number is an even power of 10, we try 10x, 1000x greater, 100x greater and 1,000,000x greater, in
-- that sequence. Essentially, first we try the next power of 10; then we try the next short-scale number (billion,
-- trillion, etc. where large numbers follow a 10^3 sequence); then we try the next long-scale number (where large
-- numbers follow a 10^6 sequence); then we try the next Indic-scale number (where large numbers follow a 10^2
-- sequence: lakh, crore, arab, ...). We don't just try powers of 10 in order because then if e.g. we have entries
-- for one million, ten million, one hundred million and one billion, and the current number is one million, the
-- next number will be ten million and the next outer number one hundred million, when it would be cleaner to have
-- one billion as the outer number (and in many cases, there is no Wiktionary entry for one hundred million).
--
-- For the previous outer number, we do an analogous algorithm but make sure we don't try numbers less than 1.
local power_of_10_sequence = { 1, 3, 2, 6 }
 
--------- Determine next outer number. ----------
if next_outer_num then
next_outer_data = lookup_data(next_outer_num, "next outer")
else
local function try(num)
local data = (not next_num or export.numbers_greater_than(num, next_num)) and lookup_data(num) or nil
if data then
next_outer_num = num
next_outer_data = data
end
return data
end
if not try((k + 1) .. string.rep("0", m)) and k == 1 then
-- Try looking up a greater power of ten instead.
for _, power_of_10 in ipairs(power_of_10_sequence) do
if try(make_greater_power_of_ten(power_of_10)) then
break
end
end
end
end
 
--------- Determine previous outer number. ----------
if prev_outer_num then
prev_outer_data = lookup_data(prev_outer_num, "previous outer")
else
local function try(num)
local data = (not prev_num or export.numbers_less_than(num, prev_num)) and lookup_data(num) or nil
if data then
prev_outer_num = num
prev_outer_data = data
end
return data
end
if k == 0 or m == 0 then
-- less than 10; no previous outer num
else
local num_to_try
if k == 1 then
num_to_try = "9" .. string.rep("0", m - 1)
else
num_to_try = (k - 1) .. string.rep("0", m)
end
if not try(num_to_try) and k == 1 then
-- Try looking up a smaller power of ten instead.
for _, power_of_10 in ipairs(power_of_10_sequence) do
local num_to_try = make_lesser_power_of_ten(power_of_10)
if num_to_try and try(num_to_try) then
break
end
end
end
end
end
 
local upper_data, lower_data
local upper_num, lower_num = cur_data.upper, cur_data.lower
 
--------- Determine upper number. ----------
if upper_num then
upper_data = lookup_data(upper_num, "upper")
else
-- Try looking up the next power of ten.
upper_num = make_greater_power_of_ten(1)
if upper_num == next_num then
upper_num = nil
else
upper_data = lookup_data(upper_num)
end
end
 
--------- Determine lower number. ----------
if lower_num then
lower_data = lookup_data(lower_num, "lower")
elseif k == 0 or m == 0 then
-- less than 10; no lower num
else
-- Try looking up the previous power or 10.
lower_num = make_lesser_power_of_ten(1)
if lower_num == prev_num then
lower_num = nil
else
lower_data = lookup_data(lower_num)
end
end


-- For a number `num` (an "adjacent" number to the current number, i.e. either next, previous, next/previous outer,
-- For a number `num` (an "adjacent" number to the current number, i.e. either next, previous, next/previous outer,
Line 925: Line 1,377:
return nil
return nil
end
end
local num_type_data = num_data[cur_type]
local forms = num_data[cur_type]
if not num_type_data then
if not forms then
return nil
return nil
end
elseif type(forms) ~= "table" then
local forms = num_type_data
if type(forms) ~= "table" then
forms = {forms}
forms = {forms}
end
end
Line 936: Line 1,386:
local seen_forms, forms_by_tag = export.group_numeral_forms_by_tag(forms)
local seen_forms, forms_by_tag = export.group_numeral_forms_by_tag(forms)


-- FIXME: `cur_tag` is not defined. This seems to have been missed when multiple tag handling was added in [[Special:Diff/68978046]].
local forms_to_display
local forms_to_display
if cur_tag and forms_by_tag[cur_tag] then
if cur_tag and forms_by_tag[cur_tag] then
Line 945: Line 1,396:
for i, form_to_display in ipairs(forms_to_display) do
for i, form_to_display in ipairs(forms_to_display) do
forms_to_display[i] = form_to_display.link or maybe_unaffix(m_data,
forms_to_display[i] = form_to_display.link or maybe_unaffix(m_data,
form_to_entry_name(form_to_display.form, lang))
form_to_stripped_form(form_to_display.form, lang))
end
end


Line 952: Line 1,403:
for _, form in ipairs(forms_to_display) do
for _, form in ipairs(forms_to_display) do
if not seen_pagenames[form] then
if not seen_pagenames[form] then
table.insert(pagenames_to_display, form)
insert(pagenames_to_display, form)
seen_pagenames[form] = true
seen_pagenames[form] = true
end
end
end
end


num = export.format_number_for_display(num)
if #pagenames_to_display == 0 then
return nil
end
 
num = number_system.format_key_for_display(num)
local num_arrow =
local num_arrow =
arrow == "rarrow" and num .. "&nbsp;&nbsp;→&nbsp;" or
arrow == "rarrow" and num .. "&nbsp;&nbsp;→&nbsp;" or
Line 966: Line 1,421:
local links = {}
local links = {}
for i, term in ipairs(pagenames_to_display) do
for i, term in ipairs(pagenames_to_display) do
links[i] = m_links.language_link{lang = lang, term = term, alt = "[" .. string.char(a + i - 1) .. "]"}
links[i] = m_links.language_link{lang = lang, term = term, alt = "[" .. char(a + i - 1) .. "]"}
end
end
links = "<sup>" .. table.concat(links, ", ") .. "</sup>"
links = "<sup>" .. concat(links, ", ") .. "</sup>"
return arrow == "larrow" and links .. num_arrow or num_arrow .. links
return arrow == "larrow" and links .. num_arrow or num_arrow .. links
else
else
Line 992: Line 1,447:


local canonical_name = lang:getCanonicalName()
local canonical_name = lang:getCanonicalName()
local title = canonical_name .. " numbers"
local title = canonical_name .. " numerals"


local function format_cell(contents, font_size, background, colspan, bold)
local function format_cell(contents, class_name, colspan, bold)
font_size = font_size and (" font-size:%s;"):format(font_size) or ""
class_name = class_name and (" " .. class_name) or ""
background = background and (" background:%s;"):format(background) or ""
colspan = colspan and ('colspan="%s" '):format(colspan) or ""
colspan = colspan and ('colspan="%s" '):format(colspan) or ""
bold = bold and "!" or "|"
bold = bold and "!" or "|"
return ('%s %sstyle="min-width: 6em;%s%s | %s\n'):format(bold, colspan, font_size, background, contents)
return ('%s %sclass="table-cell %s | %s\n'):format(bold, colspan, class_name, contents)
end
end


Line 1,010: Line 1,464:
blank_cell = "|\n"
blank_cell = "|\n"
end
end
local parts = {'|- style="text-align: center; background:#dddddd;"\n'}
local parts = {'|- class="adjacent-panel"\n'}
table.insert(parts, blank_cell)
insert(parts, blank_cell)
table.insert(parts, format_cell(display, "smaller"))
insert(parts, format_cell(display, "adjacent-number"))
table.insert(parts, blank_cell)
insert(parts, blank_cell)
return table.concat(parts)
return concat(parts)
end
end


Line 1,021: Line 1,475:


local function format_display_cell(display)
local function format_display_cell(display)
return format_cell(display, "smaller", "#dddddd")
return format_cell(display, "adjacent-number")
end
end


Line 1,028: Line 1,482:
prev_outer_display = has_outer_display and format_display_cell(prev_outer_display or "") or ""
prev_outer_display = has_outer_display and format_display_cell(prev_outer_display or "") or ""
next_outer_display = has_outer_display and format_display_cell(next_outer_display or "") or ""
next_outer_display = has_outer_display and format_display_cell(next_outer_display or "") or ""
cur_display = format_cell(cur_display, "larger", nil, nil, "bold")
cur_display = format_cell(cur_display, "current-number", nil, "bold")


local forms_display = ('| colspan="%s" style="text-align: center;" | %s\n'):format(
local forms_display = ('| colspan="%s" style="text-align: center;" | %s\n'):format(
has_outer_display and 5 or 3, table.concat(formatted_forms, "<br/>"))
has_outer_display and 5 or 3, concat(formatted_forms, "<br/>"))


local footer_display
local footer_display
if cur_data.wplink then
if cur_data.wplink then
local footer =
local footer =
"[[w:" .. lang:getCode() .. ":Main Page|" .. lang:getCanonicalName() .. " Wikipedia]] article on " ..
"[[w:" .. lang:getCode() .. ":|" .. lang:getCanonicalName() .. " Wikipedia]] article on " ..
m_links.full_link({lang = lang, term = "w:" .. lang:getCode() .. ":" .. cur_data.wplink,
m_links.full_link{lang = lang, term = "w:" .. lang:getCode() .. ":" .. cur_data.wplink,
alt = export.format_number_for_display(cur_num)})
alt = number_system.format_key_for_display(cur_num)}
footer_display = '|- style="text-align: center;"\n' .. format_cell(footer, nil, "#dddddd", has_outer_display and 5 or 3)
footer_display = '|- style="text-align: center;"\n' .. format_cell(footer, "footer-cell", has_outer_display and 5 or 3)
else
else
footer_display = ""
footer_display = ""
Line 1,048: Line 1,502:
" edit]</span>)</sup>"
" edit]</span>)</sup>"


return [=[{| class="floatright" cellpadding="5" cellspacing="0" style="background: #ffffff; border: 1px #aaa solid; border-collapse: collapse; margin-top: .5em;" rules="all"
return [=[{| class="floatright number-box" cellpadding="5" cellspacing="0" style="background: var(--wikt-palette-white, #ffffff); color: inherit; border: 1px var(--border-color-base,#aaa) solid; border-collapse: collapse; margin-top: .5em;" rules="all"
|+ ''']=] .. title .. edit_link .. "'''\n" ..
|+ ''']=] .. title .. edit_link .. "'''\n" ..
upper_display .. '|- style="text-align: center;"\n' ..
upper_display .. '|- style="text-align: center;"\n' ..
prev_outer_display .. prev_display .. cur_display .. next_display .. next_outer_display .. "|-\n" ..
prev_outer_display .. prev_display .. cur_display .. next_display .. next_outer_display .. "|-\n" ..
lower_display .. "|-\n" ..
lower_display .. "|-\n" ..
forms_display .. footer_display .. "|}"
forms_display .. footer_display .. "|}" ..
end
require("Module:TemplateStyles")("Template:number box/styles.css")
 
 
-- Assumes string or nil (or false), the types that can be found in an args table.
local function if_not_empty(val)
if val and mw.text.trim(val) == "" then
return nil
else
return val
end
end
end


Line 1,071: Line 1,516:
local num_type = frame.args["type"]
local num_type = frame.args["type"]


local args = {}
local args = require("Module:parameters").process(frame:getParent().args, {
--cloning parent's args while also assigning nil to empty strings
[1] = {required = true, type = "language", default = "und"},
for pname, param in pairs(frame:getParent().args) do
sc = {type = "script"},
args[pname] = if_not_empty(param)
headlink = true,
end
wplink = true,
alt = true,
tr = true,
[2] = true, -- prev_symbol
[3] = true, -- cur_symbol
[4] = true, -- next_symbol
[5] = true, -- prev_term
[6] = true, -- next_term
card = true, cardalt = true, cardtr = true,
ord = true, ordalt = true, ordtr = true,
adv = true, advalt = true, advtr = true,
mult = true, multalt = true, multtr = true,
dis = true, disalt = true, distr = true,
coll = true, collalt = true, colltr = true,
frac = true, fracalt = true, fractr = true,
opt = true, optx = true, optxalt = true, optxtr = true,
opt2 = true, opt2x = true, opt2xalt = true, opt2xtr = true,
})
 
local lang = args[1]
local sc = args.sc
local headlink = args.headlink
local wplink = args.wplink
local alt = args.alt
local tr = args.tr


local lang = args[1] or (mw.title.getCurrentTitle().nsText == "Template" and "und") or error("Language code has not been specified. Please pass parameter 1 to the template.")
local prev_symbol = args[2]
local sc = args["sc"];
local cur_symbol = args[3]
local headlink = args["headlink"]
local next_symbol = args[4]
local wplink = args["wplink"]
local alt = args["alt"]
local tr = args["tr"]


local prev_symbol = if_not_empty(args[2])
local prev_term = args[5]
local cur_symbol = if_not_empty(args[3]);
local next_term = args[6]
local next_symbol = if_not_empty(args[4])


local prev_term = if_not_empty(args[5])
local cardinal_term = args.card; local cardinal_alt = args.cardalt; local cardinal_tr = args.cardtr
local next_term = if_not_empty(args[6])


local cardinal_term = args["card"]; local cardinal_alt = args["cardalt"]; local cardinal_tr = args["cardtr"]
local ordinal_term = args.ord; local ordinal_alt = args.ordalt; local ordinal_tr = args.ordtr


local ordinal_term = args["ord"]; local ordinal_alt = args["ordalt"]; local ordinal_tr = args["ordtr"]
local adverbial_term = args.adv; local adverbial_alt = args.advalt; local adverbial_tr = args.advtr


local adverbial_term = args["adv"]; local adverbial_alt = args["advalt"]; local adverbial_tr = args["advtr"]
local multiplier_term = args.mult; local multiplier_alt = args.multalt; local multiplier_tr = args.multtr


local multiplier_term = args["mult"]; local multiplier_alt = args["multalt"]; local multiplier_tr = args["multtr"]
local distributive_term = args.dis; local distributive_alt = args.disalt; local distributive_tr = args.distr


local distributive_term = args["dis"]; local distributive_alt = args["disalt"]; local distributive_tr = args["distr"]
local collective_term = args.coll; local collective_alt = args.collalt; local collective_tr = args.colltr


local collective_term = args["coll"]; local collective_alt = args["collalt"]; local collective_tr = args["colltr"]
local fractional_term = args.frac; local fractional_alt = args.fracalt; local fractional_tr = args.fractr


local fractional_term = args["frac"]; local fractional_alt = args["fracalt"]; local fractional_tr = args["fractr"]
local optional1_title = args.opt
local optional1_term = args.optx; local optional1_alt = args.optxalt; local optional1_tr = args.optxtr


local optional1_title = args["opt"]
local optional2_title = args.opt2
local optional1_term = args["optx"]; local optional1_alt = args["optxalt"]; local optional1_tr = args["optxtr"]
local optional2_term = args.opt2x; local optional2_alt = args.opt2xalt; local optional2_tr = args.opt2xtr


local optional2_title = args["opt2"]
track(lang:getCode())
local optional2_term = args["opt2x"]; local optional2_alt = args["opt2xalt"]; local optional2_tr = args["opt2xtr"]


if sc then
track("sc")
end


lang = require("Module:languages").getByCode(lang) or error("The language code \"" .. lang .. "\" is not valid.")
if headlink then
sc = (sc and (require("Module:scripts").getByCode(sc) or error("The script code \"" .. sc .. "\" is not valid.")) or nil)
track("headlink")
end
 
if wplink then
track("wplink")
end
 
if alt then
track("alt")
end
 
if cardinal_alt or ordinal_alt or adverbial_alt or multiplier_alt or distributive_alt or collective_alt or fractional_alt or optional1_alt or optional2_alt then
track("xalt")
end


local subpage = mw.title.getCurrentTitle().subpageText
local subpage = mw.loadData("Module:headword/data").pagename
local is_reconstructed = lang:hasType("reconstructed") or mw.title.getCurrentTitle().nsText == "Reconstruction"
local is_reconstructed = lang:hasType("reconstructed") or mw.title.getCurrentTitle().nsText == "Reconstruction"
alt = alt or (is_reconstructed and "*" or "") .. subpage
-- Commenting out this line prevents passing redundant alts to full_link;
-- however, there may have been a purpose to it.
-- alt = alt or (is_reconstructed and "*" or "") .. subpage


if num_type == "cardinal" then
if num_type == "cardinal" then
cardinal_term = (is_reconstructed and "*" or "") .. subpage
cardinal_term = cardinal_term or (is_reconstructed and "*" or "") .. subpage
cardinal_alt = alt
cardinal_alt = cardinal_alt or alt
cardinal_tr = tr
cardinal_tr = cardinal_tr or tr
elseif num_type == "ordinal" then
elseif num_type == "ordinal" then
ordinal_term = (is_reconstructed and "*" or "") .. subpage
ordinal_term = ordinal_term or (is_reconstructed and "*" or "") .. subpage
ordinal_alt = alt
ordinal_alt = ordinal_alt or alt
ordinal_tr = tr
ordinal_tr = ordinal_tr or tr
end
end


Line 1,138: Line 1,621:


if prev_term or prev_symbol then
if prev_term or prev_symbol then
previous = m_links.full_link({lang = lang, sc = sc, term = prev_term, alt = "&nbsp;&lt;&nbsp;&nbsp;" .. prev_symbol, tr = "-"})
previous = m_links.full_link{lang = lang, sc = sc, term = prev_term, alt = "&nbsp;&lt;&nbsp;&nbsp;" .. prev_symbol, tr = "-", no_alt_ast = true}
end
end


local current = m_links.full_link({lang = lang, sc = sc, alt = cur_symbol, tr = "-"})
local current = m_links.full_link{lang = lang, sc = sc, alt = cur_symbol, tr = "-", no_alt_ast = true}


local next = ""
local next = ""


if next_term or next_symbol then
if next_term or next_symbol then
next = m_links.full_link({lang = lang, sc = sc, term = next_term, alt = next_symbol .. "&nbsp;&nbsp;&gt;&nbsp;", tr = "-"})
next = m_links.full_link{lang = lang, sc = sc, term = next_term, alt = next_symbol .. "&nbsp;&nbsp;&gt;&nbsp;", tr = "-", no_alt_ast = true}
end
end


Line 1,152: Line 1,635:


if cardinal_term then
if cardinal_term then
table.insert(forms, " &nbsp;&nbsp;&nbsp; ''[[wikt:cardinal number|Cardinal]]'' : " .. m_links.full_link({lang = lang, sc = sc, term = cardinal_term, alt = cardinal_alt, tr = cardinal_tr}))
insert(forms, " &nbsp;&nbsp;&nbsp; ''[[wikt:cardinal number|Cardinal]]'' : " .. m_links.full_link{lang = lang, sc = sc, term = cardinal_term, alt = cardinal_alt, tr = cardinal_tr})
end
end


if ordinal_term then
if ordinal_term then
table.insert(forms, " &nbsp;&nbsp;&nbsp; ''[[wikt:ordinal number|Ordinal]]'' : " .. m_links.full_link({lang = lang, sc = sc, term = ordinal_term, alt = ordinal_alt, tr = ordinal_tr}))
insert(forms, " &nbsp;&nbsp;&nbsp; ''[[wikt:ordinal number|Ordinal]]'' : " .. m_links.full_link{lang = lang, sc = sc, term = ordinal_term, alt = ordinal_alt, tr = ordinal_tr})
end
end


if adverbial_term then
if adverbial_term then
table.insert(forms, " &nbsp;&nbsp;&nbsp; ''[[wikt:adverbial number|Adverbial]]'' : " .. m_links.full_link({lang = lang, sc = sc, term = adverbial_term, alt = adverbial_alt, tr = adverbial_tr}))
insert(forms, " &nbsp;&nbsp;&nbsp; ''[[wikt:adverbial number|Adverbial]]'' : " .. m_links.full_link{lang = lang, sc = sc, term = adverbial_term, alt = adverbial_alt, tr = adverbial_tr})
end
end


if multiplier_term then
if multiplier_term then
table.insert(forms, " &nbsp;&nbsp;&nbsp; ''[[wikt:multiplier|Multiplier]]'' : " .. m_links.full_link({lang = lang, sc = sc, term = multiplier_term, alt = multiplier_alt, tr = multiplier_tr}))
insert(forms, " &nbsp;&nbsp;&nbsp; ''[[wikt:multiplier|Multiplier]]'' : " .. m_links.full_link{lang = lang, sc = sc, term = multiplier_term, alt = multiplier_alt, tr = multiplier_tr})
end
end


if distributive_term then
if distributive_term then
table.insert(forms, " &nbsp;&nbsp;&nbsp; ''[[wikt:distributive number|Distributive]]'' : " .. m_links.full_link({lang = lang, sc = sc, term = distributive_term, alt = distributive_alt, tr = distributive_tr}))
insert(forms, " &nbsp;&nbsp;&nbsp; ''[[wikt:distributive number|Distributive]]'' : " .. m_links.full_link{lang = lang, sc = sc, term = distributive_term, alt = distributive_alt, tr = distributive_tr})
end
end


if collective_term then
if collective_term then
table.insert(forms, " &nbsp;&nbsp;&nbsp; ''[[wikt:collective number|Collective]]'' : " .. m_links.full_link({lang = lang, sc = sc, term = collective_term, alt = collective_alt, tr = collective_tr}))
insert(forms, " &nbsp;&nbsp;&nbsp; ''[[wikt:collective number|Collective]]'' : " .. m_links.full_link{lang = lang, sc = sc, term = collective_term, alt = collective_alt, tr = collective_tr})
end
end


if fractional_term then
if fractional_term then
table.insert(forms, " &nbsp;&nbsp;&nbsp; ''[[wikt:fractional|Fractional]]'' : " .. m_links.full_link({lang = lang, sc = sc, term = fractional_term, alt = fractional_alt, tr = fractional_tr}))
insert(forms, " &nbsp;&nbsp;&nbsp; ''[[wikt:fractional|Fractional]]'' : " .. m_links.full_link{lang = lang, sc = sc, term = fractional_term, alt = fractional_alt, tr = fractional_tr})
end
end


if optional1_title then
if optional1_title then
table.insert(forms, " &nbsp;&nbsp;&nbsp; ''" .. optional1_title .. "'' : " .. m_links.full_link({lang = lang, sc = sc, term = optional1_term, alt = optional1_alt, tr = optional1_tr}))
insert(forms, " &nbsp;&nbsp;&nbsp; ''" .. optional1_title .. "'' : " .. m_links.full_link{lang = lang, sc = sc, term = optional1_term, alt = optional1_alt, tr = optional1_tr})
end
end


if optional2_title then
if optional2_title then
table.insert(forms, " &nbsp;&nbsp;&nbsp; ''" .. optional2_title .. "'' : " .. m_links.full_link({lang = lang, sc = sc, term = optional2_term, alt = optional2_alt, tr = optional2_tr}))
insert(forms, " &nbsp;&nbsp;&nbsp; ''" .. optional2_title .. "'' : " .. m_links.full_link{lang = lang, sc = sc, term = optional2_term, alt = optional2_alt, tr = optional2_tr})
end
end


Line 1,191: Line 1,674:
if wplink then
if wplink then
footer =
footer =
"[[w:" .. lang:getCode() .. ":Main Page|" .. lang:getCanonicalName() .. " Wikipedia]] article on " ..
"[[w:" .. lang:getCode() .. ":|" .. lang:getCanonicalName() .. " Wikipedia]] article on " ..
m_links.full_link({lang = lang, sc = sc, term = "w:" .. lang:getCode() .. ":" .. wplink, alt = alt, tr = tr})
m_links.full_link{lang = lang, sc = sc, term = "w:" .. lang:getCode() .. ":" .. wplink, alt = alt, tr = tr}
end
end


return [=[{| class="floatright" cellpadding="5" cellspacing="0" style="background: #ffffff; border: 1px #aaa solid; border-collapse: collapse; margin-top: .5em;" rules="all"
return [=[{| class="floatright number-box" cellpadding="5" cellspacing="0" rules="all"
|+ ''']=] .. header .. [=['''
|+ ''']=] .. header .. [=['''
|-
|-
| style="width: 64px; background:#dddddd; text-align: center; font-size:smaller;" | ]=] .. previous .. [=[
| class="adjacent-slot" | ]=] .. previous .. [=[


! style="width: 98px; text-align: center; font-size:larger;" | ]=] .. current .. [=[
! class="current-slot" | ]=] .. current .. [=[


| style="width: 64px; text-align: center; background:#dddddd; font-size:smaller;" | ]=] .. next .. [=[
| class="adjacent-slot" | ]=] .. next .. [=[


|-
|-
| colspan="3" style="text-align: center;" | ]=] .. table.concat(forms, "<br/>") .. [=[
| colspan="3" class="form-slot" | ]=] .. concat(forms, "<br/>") .. [=[


|-
|-
| colspan="3" style="text-align: center; background: #dddddd;" | ]=] .. footer .. [=[
| colspan="3" class="footer-slot" | ]=] .. footer .. [=[


|}]=]
|}]=] .. require("Module:TemplateStyles")("Template:number box/styles.css")
end
end


return export
return export