Module:ConvertNumeric: Difference between revisions
Richardpruen (talk | contribs) m 1 revision imported: Wikipedia article on Nicotine modules needed |
imported>Johnuniq please don't use tricky syntax: stick to boring stuff that works |
||
(2 intermediate revisions by 2 users not shown) | |||
Line 3: | Line 3: | ||
-- When editing, preview with: [[Module_talk:ConvertNumeric/testcases]] | -- When editing, preview with: [[Module_talk:ConvertNumeric/testcases]] | ||
-- First, edit [[Module:ConvertNumeric/sandbox]], then preview with [[Module_talk:ConvertNumeric/sandbox/testcases]] | -- First, edit [[Module:ConvertNumeric/sandbox]], then preview with [[Module_talk:ConvertNumeric/sandbox/testcases]] | ||
require('strict') | |||
local ones_position = { | local ones_position = { | ||
Line 189: | Line 190: | ||
} | } | ||
local | local engord_tens_end = { | ||
['twentieth'] = 20, | |||
['thirtieth'] = 30, | |||
['fortieth'] = 40, | |||
['fiftieth'] = 50, | |||
['sixtieth'] = 60, | |||
['seventieth'] = 70, | |||
['eightieth'] = 80, | |||
['ninetieth'] = 90, | |||
['twentieth'] | |||
['thirtieth'] | |||
['fortieth'] | |||
['fiftieth'] | |||
['sixtieth'] | |||
['seventieth'] = 70, | |||
['eightieth'] | |||
['ninetieth'] | |||
} | } | ||
local eng_tens_cont = { | local eng_tens_cont = { | ||
['twenty'] | ['twenty'] = 20, | ||
['thirty'] | ['thirty'] = 30, | ||
['forty'] | ['forty'] = 40, | ||
['fifty'] | ['fifty'] = 50, | ||
['sixty'] | ['sixty'] = 60, | ||
['seventy'] = 70, | ['seventy'] = 70, | ||
['eighty'] | ['eighty'] = 80, | ||
['ninety'] | ['ninety'] = 90, | ||
} | } | ||
-- Converts a given valid roman numeral (and some invalid roman numerals) to a number. Returns -1, errorstring on error | -- Converts a given valid roman numeral (and some invalid roman numerals) to a number. Returns { -1, errorstring } on error. | ||
local function roman_to_numeral(roman) | local function roman_to_numeral(roman) | ||
if type(roman) ~= "string" then return -1, "roman numeral not a string" end | if type(roman) ~= "string" then return -1, "roman numeral not a string" end | ||
Line 259: | Line 239: | ||
end | end | ||
-- Converts a given integer between 0 and 100 to English text (e.g. 47 -> forty-seven) | -- Converts a given integer between 0 and 100 to English text (e.g. 47 -> forty-seven). | ||
local function numeral_to_english_less_100(num, ordinal, plural, zero) | local function numeral_to_english_less_100(num, ordinal, plural, zero) | ||
local terminal_ones, terminal_tens | local terminal_ones, terminal_tens | ||
Line 290: | Line 270: | ||
end | end | ||
-- Converts a given integer (in string form) between 0 and 1000 to English text (e.g. 47 -> forty-seven) | -- Converts a given integer (in string form) between 0 and 1000 to English text (e.g. 47 -> forty-seven). | ||
local function numeral_to_english_less_1000(num, use_and, ordinal, plural, zero) | local function numeral_to_english_less_1000(num, use_and, ordinal, plural, zero) | ||
num = tonumber(num) | num = tonumber(num) | ||
Line 302: | Line 282: | ||
end | end | ||
-- Converts an English | -- Converts an ordinal in English text from 'zeroth' to 'ninety-ninth' inclusive to a number [0–99], else -1. | ||
local function english_to_ordinal(english) | local function english_to_ordinal(english) | ||
local eng = string.lower(english or '') | local eng = string.lower(english or '') | ||
local | local engord_lt20 = {} -- ones_position_ord{} keys & values swapped | ||
local | for k, v in pairs( ones_position_ord ) do | ||
local | engord_lt20[v] = k | ||
end | |||
if engord_lt20[eng] then | |||
return engord_lt20[eng] -- e.g. first -> 1 | |||
elseif engord_tens_end[eng] then | |||
return engord_tens_end[eng] -- e.g. ninetieth -> 90 | |||
else | |||
local tens, ones = string.match(eng, '^([a-z]+)[%s%-]+([a-z]+)$') | |||
if tens and ones then | |||
local tens_cont = eng_tens_cont[tens] | |||
local ones_end = engord_lt20[ones] | |||
if tens_cont and ones_end then | |||
return tens_cont + ones_end -- e.g. ninety-ninth -> 99 | |||
end | |||
end | |||
end | |||
return -1 -- Failed | |||
end | |||
-- Converts a number in English text from 'zero' to 'ninety-nine' inclusive to a number [0–99], else -1. | |||
local function english_to_numeral(english) | |||
local eng = string.lower(english or '') | |||
local eng_lt20 = { ['single'] = 1 } -- ones_position{} keys & values swapped | |||
for k, v in pairs( ones_position ) do | |||
eng_lt20[v] = k | |||
end | |||
if eng_lt20[eng] then | if eng_lt20[eng] then | ||
return eng_lt20[eng] --e.g. | return eng_lt20[eng] -- e.g. one -> 1 | ||
elseif | elseif eng_tens_cont[eng] then | ||
return | return eng_tens_cont[eng] -- e.g. ninety -> 90 | ||
else | else | ||
local tens, ones = string.match(eng, '^([a-z]+)%-([a-z]+)$') | local tens, ones = string.match(eng, '^([a-z]+)[%s%-]+([a-z]+)$') | ||
if tens and ones then | if tens and ones then | ||
local tens_cont = eng_tens_cont[tens] | local tens_cont = eng_tens_cont[tens] | ||
local ones_end = eng_lt20[ones] | local ones_end = eng_lt20[ones] | ||
if tens_cont and ones_end then | if tens_cont and ones_end then | ||
return tens_cont + ones_end --e.g. ninety- | return tens_cont + ones_end -- e.g. ninety-nine -> 99 | ||
end | end | ||
end | end | ||
end | end | ||
return -1 -- | return -1 -- Failed | ||
end | end | ||
Line 380: | Line 387: | ||
end | end | ||
-- Rounds a number to the nearest two-word number (round = up, down, or "on" for round to nearest) | -- Rounds a number to the nearest two-word number (round = up, down, or "on" for round to nearest). | ||
-- Numbers with two digits before the decimal will be rounded to an integer as specified by round. | -- Numbers with two digits before the decimal will be rounded to an integer as specified by round. | ||
-- Larger numbers will be rounded to a number with only one nonzero digit in front and all other digits zero. | -- Larger numbers will be rounded to a number with only one nonzero digit in front and all other digits zero. | ||
Line 515: | Line 522: | ||
-- capitalize (boolean): whether to capitalize the result (e.g. 'One' instead of 'one') | -- capitalize (boolean): whether to capitalize the result (e.g. 'One' instead of 'one') | ||
-- use_and (boolean): whether to use the word 'and' between tens/ones place and higher places | -- use_and (boolean): whether to use the word 'and' between tens/ones place and higher places | ||
-- hyphenate (boolean): whether to hyphenate all words in the result, useful | -- hyphenate (boolean): whether to hyphenate all words in the result, useful as an adjective | ||
-- ordinal (boolean): whether to produce an ordinal (e.g. 'first' instead of 'one') | -- ordinal (boolean): whether to produce an ordinal (e.g. 'first' instead of 'one') | ||
-- plural (boolean): whether to pluralize the resulting number | -- plural (boolean): whether to pluralize the resulting number | ||
Line 620: | Line 627: | ||
local function _numeral_to_english2(args) | local function _numeral_to_english2(args) | ||
local num = args.num | local num = tostring(args.num) | ||
num = num:gsub("^%s*(.-)%s*$", "%1") -- Trim whitespace | |||
num = num:gsub(",", "") -- Remove commas | |||
num = num:gsub("^<span[^<>]*></span>", "") -- Generated by Template:age | |||
if num ~= '' then -- a fraction may have an empty whole number | |||
if not num:find("^%-?%d*%.?%d*%-?[Ee]?[+%-]?%d*$") then | |||
-- Input not in a valid format, try to eval it as an expr to see | |||
-- if that produces a number (e.g. "3 + 5" will become "8"). | |||
local noerr, result = pcall(mw.ext.ParserFunctions.expr, num) | |||
if noerr then | |||
num = result | |||
end | end | ||
end | end | ||
end | end | ||
-- | -- Call helper function passing args | ||
return _numeral_to_english( | return _numeral_to_english( | ||
num, | num, | ||
Line 656: | Line 664: | ||
roman_to_numeral = roman_to_numeral, | roman_to_numeral = roman_to_numeral, | ||
spell_number = _numeral_to_english, | spell_number = _numeral_to_english, | ||
spell_number2 = _numeral_to_english2, | spell_number2 = _numeral_to_english2, | ||
english_to_ordinal = english_to_ordinal, | english_to_ordinal = english_to_ordinal, | ||
english_to_numeral = english_to_numeral, | |||
} | } | ||
Line 666: | Line 675: | ||
function p._english_to_ordinal(frame) -- callable via {{#invoke:ConvertNumeric|_english_to_ordinal|First}} | function p._english_to_ordinal(frame) -- callable via {{#invoke:ConvertNumeric|_english_to_ordinal|First}} | ||
return english_to_ordinal(frame.args[1]) | return english_to_ordinal(frame.args[1]) | ||
end | |||
function p._english_to_numeral(frame) -- callable via {{#invoke:ConvertNumeric|_english_to_numeral|One}} | |||
return english_to_numeral(frame.args[1]) | |||
end | end | ||
function p.numeral_to_english(frame) | function p.numeral_to_english(frame) | ||
local args = frame.args | local args = frame.args | ||
-- Tail call to helper function passing args from frame | |||
return _numeral_to_english2{ | |||
['num'] = args[1], | |||
['numerator'] = args['numerator'], | |||
['denominator'] = args['denominator'], | |||
['capitalize'] = args['case'] == 'U' or args['case'] == 'u', | |||
['use_and'] = args['sp'] ~= 'us', | |||
['hyphenate'] = args['adj'] == 'on', | |||
['ordinal'] = args['ord'] == 'on', | |||
['plural'] = args['pl'] == 'on', | |||
['links'] = args['lk'], | |||
['negative_word'] = args['negative'], | |||
['round'] = args['round'], | |||
['zero'] = args['zero'], | |||
['use_one'] = args['one'] == 'one' -- experiment: using '|one=one' makes fraction 2+1/2 give "two and one-half" instead of "two and a half" | |||
} | |||
args['case'] == 'U' or args['case'] == 'u', | |||
args['sp'] ~= 'us', | |||
args['adj'] == 'on', | |||
args['ord'] == 'on', | |||
args['pl'] == 'on', | |||
args['lk'], | |||
args['negative'], | |||
args['round'], | |||
args['zero'], | |||
args['one'] == 'one' -- experiment: using '|one=one' makes fraction 2+1/2 give "two and one-half" instead of "two and a half" | |||
end | end | ||
Line 707: | Line 708: | ||
if div >= 1 then return decToHexDigit(div)..dig[mod+1] else return dig[mod+1] end | if div >= 1 then return decToHexDigit(div)..dig[mod+1] else return dig[mod+1] end | ||
end -- I think this is supposed to be done with a tail call but first I want something that works at all | end -- I think this is supposed to be done with a tail call but first I want something that works at all | ||
---- finds all the decimal numbers in the input text and hexes each of them | ---- finds all the decimal numbers in the input text and hexes each of them | ||
function p.decToHex(frame) | function p.decToHex(frame) |