Jump to content
Toggle menu
  • 8 articles
  • 75 files
  • 7 users
  • 37.8K edits
Neodyland Wiki
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

Module:Roman: Difference between revisions

From Neodyland Wiki
m clarity...
m fully tested in sandbox (note: larger range of numbers supported, CLDR conformance, supports wider sets of conversions from Roman to Arabic); plain-text only now prefered to HTML+CSS
Line 1: Line 1:
--[[
--[[
This module converts Arabic numerals into Roman numerals.
This module converts Arabic numerals into Roman numerals. It currently works for any  
It currently works for any whole number between 0 and 4999999999.
whole number between 0 and 4999999.
 
Please do not modify this code without applying the changes first at Module:Roman/sandbox and testing
Please do not modify this code without applying the changes first at Module:Roman/sandbox and testing  
at Module:Roman/sandbox/testcases and Module talk:Roman/sandbox/testcases.
at Module:Roman/sandbox/testcases and Module talk:Roman/sandbox/testcases.
 
Authors and maintainers:
Authors and maintainers:
* User:RP88
* User:RP88, User:Verdy_p
]]
]]
local p = {}


local p = {}
--[============[
  Private data
-- =======================================
--]============]
-- === Public Functions ==================
-- See CLDR data /common/rbnf/root.xml for "roman-upper" rules. However we still don't
-- =======================================
-- use the rarely supported Roman extension digits after 'M' (in U+2160..2188), but use
-- the more common notation with diacritical overlines ('ↁ'='V̅', 'ↂ'='X̅', etc.).
-- Please avoid using HTML with "text-decoration:overline" style, but use plain-text
-- combining characters (U+0304 and/or U+0305).
local decimalRomans = {
    d0 = { [0] = '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX' },
    d1 = { [0] = '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC' },
    d2 = { [0] = '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM' },
    d3 = { [0] = '', 'M', 'MM', 'MMM', 'I̅V̅', 'V̅', 'V̅I̅', 'V̅I̅I̅', 'V̅I̅I̅I̅', 'I̅X̅' },
    d4 = { [0] = '', 'X̅', 'X̅X̅', 'X̅X̅X̅', 'X̅L̅', 'L̅', 'L̅X̅', 'L̅X̅X̅', 'L̅X̅X̅X̅', 'X̅C̅' },
    d5 = { [0] = '', 'C̅', 'C̅C̅', 'C̅C̅C̅', 'C̅D̅', 'D̅', 'D̅C̅', 'D̅C̅C̅', 'D̅C̅C̅C̅', 'C̅M̅' },
    d6 = { [0] = '', 'M̅', 'M̅M̅', 'M̅M̅M̅', 'Ī̅V̄̅', 'V̄̅', 'V̄̅Ī̅', 'V̄̅Ī̅Ī̅', 'V̄̅Ī̅Ī̅Ī̅', 'Ī̅X̄̅' },
    d7 = { [0] = '', 'X̄̅', 'X̄̅X̄̅', 'X̄̅X̄̅X̄̅', 'X̄̅L̄̅', 'L̄̅', 'L̄̅X̄̅', 'L̄̅X̄̅X̄̅', 'L̄̅X̄̅X̄̅X̄̅', 'X̄̅C̄̅' },
    d8 = { [0] = '', 'C̄̅', 'C̄̅C̄̅', 'C̄̅C̄̅C̄̅', 'C̄̅D̄̅', 'D̄̅', 'D̄̅C̄̅', 'D̄̅C̄̅C̄̅', 'D̄̅C̄̅C̄̅C̄̅', 'C̄̅M̄̅' },
    d9 = { [0] = '', 'M̄̅', 'M̄̅M̄̅', 'M̄̅M̄̅M̄̅', 'M̄̅M̄̅M̄̅M̄̅' },
}
local romanDecimals = {
    -- Basic Latin letters
    N = 0, -- abbreviated "nec" or "non" adverb in Classical Latin
    I = 1, V = 5, X = 10, L = 50, C = 100,
    D = 500,-- TODO: add Medieval "apostrophic/Claudian/lunate" notations like "IƆ"
    M = 1000,
    -- U+0304 .. U+0305 : COMBINING (MACRON|OVERBAR)
    ['\204\132'] = -1000, -- (0xCC,0x84 in UTF-8) multiplier
    ['\204\133'] = -1000, -- (0xCC,0x85 in UTF-8) multiplier, considered equivalent here
    -- U+012A
    ['Ī'] = 1000, -- LATIN LETTER WITH COMBINING MACRON, canonically equivalent to 'I' and U+0304
    -- U+2160 .. U+216F : Roman capital digit symbols (compatibility, monospaced in CJK fonts)
    ['Ⅰ'] = 1, ['Ⅱ'] = 2, ['Ⅲ'] = 3, ['Ⅳ'] = 4, ['Ⅴ'] = 5, ['Ⅵ'] = 6,
    ['Ⅶ'] = 7, ['Ⅷ'] = 8, ['Ⅸ'] = 9, ['Ⅹ'] = 10, ['Ⅺ'] = 11, ['Ⅻ'] = 12,
    ['Ⅼ'] = 50, ['Ⅽ'] = 100, ['Ⅾ'] = 500, ['Ⅿ'] = 1000,
    -- U+2170 .. U+217F : Roman lowercase digit symbols (compatibility, monospaced in CJK fonts)
    ['ⅰ'] = 1, ['ⅱ'] = 2, ['ⅲ'] = 3, ['ⅳ'] = 4, ['ⅴ'] = 5, ['ⅵ'] = 6,
    ['ⅶ'] = 7, ['ⅷ'] = 8, ['ⅸ'] = 9, ['ⅹ'] = 10, ['ⅺ'] = 11, ['ⅻ'] = 12,
    ['ⅼ'] = 50, ['ⅽ'] = 100, ['ⅾ'] = 500, ['ⅿ'] = 1000,
    -- U+2180 .. U+2182 : Old Roman symbols
    ['ↀ'] = 1000, -- = 'I̅' = 'M'. TODO: add Medieval "apostrophic/Claudian/lunate" notations like "CIƆ"; do not confuse it with "CD" (400)
    ['ↁ'] = 5000, -- = 'V̅'. TODO: add Medieval "apostrophic/Claudian/lunate" notations like "DƆ" and "IƆƆ"
    ['ↂ'] = 10000, -- = 'X̅'. TODO: add Medieval "apostrophic/Claudian/lunate" notations like "CCIƆƆ"
    -- U+2183..U+2184 : ROMAN DIGIT (CAPITAL|LOWER) REVERSED C. TODO: add for "apostrophic/Claudian/lunate" notations (and support "Ɔ" OPEN O as aliases)
    -- The reversed "C" is a trailing multiplier by 10 but if it is not paired by a leading "C", the surrounded value will be divided by 2:
    -- * "I" = 1, but if followed by followed by "Ɔ", it takes the value 100:
    -- * when followed by a first "Ɔ" it multiplies it by 10 giving 1000 (assuming "CIƆ"), but if not prefixed by a pairing "C", gives 500 for "IƆ" = "D".
    -- * when followed by a second "Ɔ" it multiplies it by 10 giving 1000 (assuming "CCIƆƆ"), but if not prefixed by a pairing "C", gives 5000 for "IƆƆ" = "DƆ".
    -- * for higher multiples, using overlines is highly preferred for noting multipliers by 1000.
    -- U+2185: ROMAN NUMERAL SIX LATE FORM
    ['ↅ'] = 6, -- = 'VI' (overstriked)
    -- U+2186: ROMAN NUMERAL FIFTY EARLY FORM
    ['ↆ'] = 50, -- = 'L'
    -- U+2187 .. U+2188: ROMAN NUMERAL (ONE HUNDRED|FIFTY) THOUSAND (Archaic, rarely supported in fonts)
    ['ↇ'] = 50000, -- = 'L̅'. TODO: add Medieval "apostrophic/Claudian/lunate" notations like "DƆƆ" and "IƆƆƆ"
    ['ↈ'] = 100000, -- = 'C̅'. TODO: add Medieval "apostrophic/Claudian/lunate" notations like "CCCDƆƆ" and "CCCIƆƆƆ"
}


--[[
--[=================[
Numeral
  Private functions
--]=================]
This function converts an Arabic numeral into a Roman numeral.  It works for values between
0 and 4999999.  The output string may contain HTML tags.  Arabic numeral zero is output as
an empty string.
Usage:
{{#invoke:Roman|Numeral|<value>}}
{{#invoke:Roman|Numeral}} - uses the caller's parameters


Parameters
--[==[
  1: Value to convert into a Roman numeral. Must be at least 0 and less than 5,000,000.
This function returns a string containing the input value formatted as a Roman numeral.
It works for non-negative integers lower than 5 billions (up to 4999999999: this covers
Error Handling:
all unsigned 32-bit integers), otherwise it returns the number formatted using Latin
  If the input does not look like it contains a number or the number is outside of the
digits. The result string will be an UTF-8-encoded plain-text alphabetic string.
  supported range an error message is returned.
]==]--
]]
function convertArabicToRoman(value)
function p.Numeral(frame)
    -- source CDLR data (/common/rbnf/root.xml)
-- if no argument provided than check parent template/module args
    if value >= 1 and value <= 4999999999 and value == math.floor(value) then
local args = frame.args
        local d0, d1, d2, d3, d4, d5, d6, d7
if args[1]==nil then
        d0, value = value % 10, math.floor(value / 10)
args = frame:getParent().args
        d1, value = value % 10, math.floor(value / 10)
end
        d2, value = value % 10, math.floor(value / 10)
        d3, value = value % 10, math.floor(value / 10)
return p._Numeral(args[1])
        d4, value = value % 10, math.floor(value / 10)
        d5, value = value % 10, math.floor(value / 10)
        d6, value = value % 10, math.floor(value / 10)
        d7, value = value % 10, math.floor(value / 10)
        d8, value = value % 10, math.floor(value / 10)
        return table.concat({
            decimalRomans.d9[value],
            decimalRomans.d8[d8],
            decimalRomans.d7[d7],
            decimalRomans.d6[d6],
            decimalRomans.d5[d5],
            decimalRomans.d4[d4],
            decimalRomans.d3[d3],
            decimalRomans.d2[d2],
            decimalRomans.d1[d1],
            decimalRomans.d0[d0],
        })
    elseif value == 0 then
        return 'N' -- for adverbs "nec" or "non" in Classical Latin (which had no zero)
    end
    return tostring(value)
end
end


--[==[
This function converts a plain-text string containing a Roman numeral to an integer.
It works for values between 0 and 4999999999.
]==]--
function convertRomanToArabic(roman)
    if roman == '' then return nil end
    local result, prevRomanDecimal, multiplier = 0, 0, 1
    for i = mw.ustring.len(roman), 1, -1 do
        local currentRomanDecimal = romanDecimals[mw.ustring.upper(mw.ustring.sub(roman, i, i))]
        if currentRomanDecimal == nil then
            return nil
        elseif currentRomanDecimal < 0 then
            multiplier = multiplier * -currentRomanDecimal
        else
            currentRomanDecimal, multiplier = currentRomanDecimal * multiplier, 1
            if currentRomanDecimal < prevRomanDecimal then
                result = result - currentRomanDecimal
            else
                result = result + currentRomanDecimal
                prevRomanDecimal = currentRomanDecimal
            end
        end
    end
    return result
end


--[[
--[==[
_Numeral
This function converts a string containing a Roman numeral to an integer.
It works for values between 0 and 4999999999.
The input string may contain HTML tags using style="text-decoration:overline" (not recommended).
]==]--
function convertRomanHTMLToArabic(roman)
    local result = convertRomanToArabic(roman)
    if result == nil then
        result = tonumber(roman)
    end
    return result
    [==[ DISABLED FOR NOW, NOT REALLY NEEDED AND NOT CORRECTLY TESTED
    local result = 0
    local overline_start_len = mw.ustring.len(overline_start)
    if mw.ustring.sub(roman, 1, overline_start_len) == overline_start then
        local end_tag_start, end_tag_end = mw.ustring.find(roman, overline_end, overline_start_len, true)
        if end_tag_start ~= nil then
            local roman_high = mw.ustring.sub(roman, overline_start_len + 1, end_tag_start - 1)
            local roman_low = mw.ustring.sub(roman, end_tag_end + 1, mw.ustring.len(roman)) or ''
            if (mw.ustring.find(roman_high, "^[mdclxvi]+$") ~= nil) and (mw.ustring.find(roman_low, "^[mdclxvi]*$") ~= nil) then
                result = convertRomanToArabic(roman_high) * 1000 + convertRomanToArabic(roman_low)
            end
        end
    end
    return result
    ]==]
end


This function returns a string containing the input value formatted as a Roman numeral.  It works for values between
--[==[
0 and 4999999. The output string may contain HTML tags.
Helper function to handle error messages.
 
]==]--
Parameters
function outputError(message)
  input: integer or string containing value to convert into a Roman numeral
    return table.concat({
 
        '<strong class="error">Roman Module Error: ', message,
Error Handling:
        '</strong>[[Category:Errors reported by Module Roman]]'
  If the input does not look like it contains a number or the number is outside of the
    })
  supported range an error message is returned.
]]
function p._Numeral(input)
local output = ''
 
if input then
local value = tonumber(input)
if value and (value >= 0) and (value < 5000000) then
output = convertArabicToRomanHTML(value)
else
output = outputError("unsupported value")
end
else
output = outputError("missing value")
end
return output
end
end


--[================[
  Public functions
--]================]


--[[
--[==[
isRoman
isRoman


Tests if the input is a valid Roman numeral. Returns true if so, false if not. For the
Tests if the trimmed input is a valid Roman numeral. Returns true if so, false if not.
purposes of this function, the empty string is not a Roman numeral.
For the purposes of this function, the empty string (after trimming whitespaces) is not a Roman numeral.


Parameters
Parameters
Line 89: Line 189:
Error Handling:
Error Handling:
   If the input is not a valid Roman numeral this function returns false.
   If the input is not a valid Roman numeral this function returns false.
]]
]==]--
function p.isRoman(s)
function p.isRoman(s)
return s and (s ~= '') and (p.toArabic(s) ~= 0)
    return type(s) == 'string' and convertRomanToArabic(mw.text.trim(s)) ~= nil
end
end


 
--[==[
--[[
toArabic
toArabic


This function converts a Roman numeral into an Arabic numeral. It works for values between  
This function converts a Roman numeral into an Arabic numeral.
0 and 4999999.  The empty string is converted to zero.
It works for values between 0 and 4999999999.
'N' is converted to 0 and the empty string is converted to nil.


Parameters
Parameters
Line 105: Line 205:


Error Handling:
Error Handling:
   If the input is not a valid Roman numeral this function returns zero.
   If the input is not a valid Roman numeral this function returns nil.
]]
]==]--
function p.toArabic(roman)
function p.toArabic(roman)
local result = 0
    if type(roman) == 'string' then
 
        roman = mw.text.trim(roman)
if roman and (type(roman)=='string') and (roman ~= '') then
        local result = convertRomanToArabic(roman)
roman = mw.ustring.lower(mw.text.trim(roman))
        if result == nil then
result = convertRomanHTMLToArabic(roman)
            result = tonumber(roman)
end
        end
 
        return result
return result
    elseif type(roman) == 'number' then
        return roman
    else
        return nil
    end
end
end


--[==[
_Numeral


-- =======================================
This function returns a string containing the input value formatted as a Roman numeral.
-- === Private Functions =================
It works for values between 0 and 4999999999.
-- =======================================


local overline_start = '<span style="text-decoration:overline;">'
Parameters
local overline_end = '</span>'
  value: integer or string containing value to convert into a Roman numeral


--[[
Error Handling:
This function returns a string containing the input value formatted as a Roman numeral.  It works for values between
  If the input does not look like it contains a number or the number is outside of the
0 and 4999999. The result string may contain HTML tags.
  supported range an error message is returned.
]]
]==]--
function convertArabicToRomanHTML(value)
function p._Numeral(value)
local result = ''
    if value == nil then
        return outputError('missing value')
if (value < 5000) then
    end
result = convertArabicToRoman(value)
    if type(value) == 'string' then
else
        value = tonumber(value)
local low_value
    elseif type(value) ~= 'number' then
if (math.floor(value) % 5000) >= 4000 then
        return outputError('unsupported value')
low_value = math.floor(value) % 1000;
    end
else
    return convertArabicToRoman(value)
low_value = math.floor(value) % 5000;
end
local high_value = math.floor((value - low_value) / 1000)
local low_roman = convertArabicToRoman(low_value)
local high_roman = convertArabicToRoman(high_value)
result = overline_start .. high_roman .. overline_end .. low_roman
end
return result
end
end


--[==[
Numeral


--[[
This function for MediaWiki converts an Arabic numeral into a Roman numeral.
This function returns a string containing the input value formatted as a Roman numeral. It works for values between  
It works for values between 0 and 4999999999 (includes the whole range of unsigned 32-bit integers).
0 and 4999. The result string will be a simple alphabetic string.
Arabic numeral zero is output as 'N' (for Latin negation adverbs "nec" or "non").
]]
function convertArabicToRoman(value)
local thousands = {'', 'M', 'MM', 'MMM', 'MMMM'}
local hundreds = {'', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM'}
local tens = {'', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC'}
local ones = {'', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'}
local index
local result = ''
if ((value >= 0) and (value < 5000)) then
index = (math.floor(value / 1000) % 5) + 1
result = result .. thousands[index]
index = (math.floor(value / 100) % 10) + 1
result = result .. hundreds[index]
index = (math.floor(value / 10) % 10) + 1
result = result .. tens[index]
index = (math.floor(value) % 10) + 1
result = result .. ones[index]
end
return result
end


Usage:
    {{#invoke:Roman|Numeral|<value>}}
    {{#invoke:Roman|Numeral}} - uses the caller's parameters


--[[
Parameters
This function converts a string containing a Roman numeral to an integer. It works for values between
    1: Value to convert into a Roman numeral. Must be at least 0 and less than 5,000,000.
0 and 4999999. The input string may contain HTML tags.
]]
function convertRomanHTMLToArabic(roman)
local result = 0
if mw.ustring.find(roman, "^[mdclxvi]+$") ~= nil then
result = convertRomanToArabic(roman)
else
local overline_start_len = mw.ustring.len(overline_start)
if mw.ustring.sub(roman, 1, overline_start_len) == overline_start then
local end_tag_start, end_tag_end = mw.ustring.find(roman, overline_end, overline_start_len, true)
if end_tag_start ~= nil then
local roman_high = mw.ustring.sub(roman, overline_start_len + 1, end_tag_start - 1)
local roman_low = mw.ustring.sub(roman, end_tag_end + 1, mw.ustring.len(roman)) or ''
if (mw.ustring.find(roman_high, "^[mdclxvi]+$") ~= nil) and (mw.ustring.find(roman_low, "^[mdclxvi]*$") ~= nil) then
result = convertRomanToArabic(roman_high) * 1000 + convertRomanToArabic(roman_low)
end
end
end
end
return result
end


 
Error Handling:
--[[
    If the input does not look like it contains a number or the number is outside of the
This function converts a string containing a Roman numeral to an integer.  It works for values between
    supported range an error message is returned.
0 and 4999.
]==]--
]]
function p.Numeral(frame)
function convertRomanToArabic(roman)
    -- if no argument provided than check parent template/module args
local romanDecimals = {m = 1000, d = 500, c = 100, l = 50, x = 10, v = 5, i = 1}
    local args = frame.args
local prevRomanDecimal = 0
    if args[1] == nil then
local result = 0
        args = frame:getParent().args
    end
for i = mw.ustring.len(roman), 1, -1 do
    return p._Numeral(args[1])
local c = mw.ustring.sub(roman, i, i)
local currentRomanDecimal = romanDecimals[c]
if currentRomanDecimal == nil then
return 0
end
if prevRomanDecimal > currentRomanDecimal then
result = result - currentRomanDecimal
else
result = result + currentRomanDecimal
end
prevRomanDecimal = currentRomanDecimal
end
return result
end
 
 
--[[
Helper function to handle error messages.
]]
function outputError(error_str)
    local error_str = '<strong class="error">Roman Module Error: ' .. error_str .. '</strong>';
    error_str = '[[Category:Errors reported by Module Roman]]' .. error_str;
    return error_str;
end
end


return p
return p

Revision as of 20:42, 29 April 2015

Module documentation[ view · edit · history · purge ]
This documentation is transcluded from Module:Roman/doc.

Summary

This module contains functions for working with Roman numerals. Currently used by:

Using this module from templates

Numeral

This function converts an Arabic numeral into a Roman numeral. It works for values between 0 (N) and 4999999999 (M̿M̿M̿M̿C̿M̿X̿C̿I̿X̿C̅M̅X̅C̅I̅X̅CMXCIX): this includes the whole range of unsigned 32-bit integers. The output string no longer contain HTML tags. If needed you can add external CSS formatting using a serif font family, or a small-caps font variant. Arabic numeral zero is output as 'N' (for Classical Latin adverbs "Nec" or "non"), like in standard CLDR Data.

If the input does not look like it contains a number or the number is outside of the supported range, an error message is returned. If an error message is returned, the error message will contain code to categorize pages into Category:Errors reported by Module Roman.

Usage:

{{#invoke:Roman|Numeral|''value''}}

Example: {{#invoke:Roman|Numeral|8}} produces VIII.

Arabic

This function converts a Roman numeral into an Arabic numeral. It works for values between 0 (N) and 4999999999 (M̿M̿M̿M̿C̿M̿X̿C̿I̿X̿C̅M̅X̅C̅I̅X̅CMXCIX): this includes the whole range of unsigned 32-bit integers.

If the input does not look like it contains a number or the number is outside of the supported range, an error message is returned. If an error message is returned, the error message will contain code to categorize pages into Category:Errors reported by Module Roman.

Usage:

{{#invoke:Roman|Arabic|''value''}}

Example: {{#invoke:Roman|Arabic|viii}} produces Script error: The function "Arabic" does not exist..

Using this module from Lua code

In order to use the functions in this module from another Lua module you first have to import this module.

Example:

local roman = require('Module:Roman')

_Numeral

This function converts an Arabic numeral into a Roman numeral. It works for values between 0 and 4999999. The output string may contain HTML tags. Arabic numeral zero is output as an empty string. If the input does not look like it contains a number or the number is outside of the supported range an error message is returned. If an error message is returned, the error message will contain code to categorize pages into Category:Errors reported by Module Roman.

Usage:

roman_value = roman._Numeral(value)

isRoman

Tests if the input is a valid Roman numeral. Returns true if so, false if not. For the purposes of this function, the empty string is not a valid Roman numeral.

Usage:

if roman.isRoman(roman_value) then

toArabic

This function converts a Roman numeral into an Arabic numeral. It works for values between 0 and 4999999999. The string 'N' is converted to zero. If the input is not a valid Roman numeral this function attempts to parse it as an Arabic number and returns nil if it also fails.

Usage:

arabic_value = roman.toArabic(value)
--[[
This module converts Arabic numerals into Roman numerals.
It currently works for any  whole number between 0 and 4999999999.

Please do not modify this code without applying the changes first at Module:Roman/sandbox and testing
at Module:Roman/sandbox/testcases and Module talk:Roman/sandbox/testcases.

Authors and maintainers:
* User:RP88, User:Verdy_p
]]
local p = {}

--[============[
   Private data
--]============]
-- See CLDR data /common/rbnf/root.xml for "roman-upper" rules. However we still don't
-- use the rarely supported Roman extension digits after 'M' (in U+2160..2188), but use
-- the more common notation with diacritical overlines ('ↁ'='V̅', 'ↂ'='X̅', etc.).
-- Please avoid using HTML with "text-decoration:overline" style, but use plain-text
-- combining characters (U+0304 and/or U+0305).
local decimalRomans = {
    d0 = { [0] = '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX' },
    d1 = { [0] = '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC' },
    d2 = { [0] = '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM' },
    d3 = { [0] = '', 'M', 'MM', 'MMM', 'I̅V̅', 'V̅', 'V̅I̅', 'V̅I̅I̅', 'V̅I̅I̅I̅', 'I̅X̅' },
    d4 = { [0] = '', 'X̅', 'X̅X̅', 'X̅X̅X̅', 'X̅L̅', 'L̅', 'L̅X̅', 'L̅X̅X̅', 'L̅X̅X̅X̅', 'X̅C̅' },
    d5 = { [0] = '', 'C̅', 'C̅C̅', 'C̅C̅C̅', 'C̅D̅', 'D̅', 'D̅C̅', 'D̅C̅C̅', 'D̅C̅C̅C̅', 'C̅M̅' },
    d6 = { [0] = '', 'M̅', 'M̅M̅', 'M̅M̅M̅', 'Ī̅V̄̅', 'V̄̅', 'V̄̅Ī̅', 'V̄̅Ī̅Ī̅', 'V̄̅Ī̅Ī̅Ī̅', 'Ī̅X̄̅' },
    d7 = { [0] = '', 'X̄̅', 'X̄̅X̄̅', 'X̄̅X̄̅X̄̅', 'X̄̅L̄̅', 'L̄̅', 'L̄̅X̄̅', 'L̄̅X̄̅X̄̅', 'L̄̅X̄̅X̄̅X̄̅', 'X̄̅C̄̅' },
    d8 = { [0] = '', 'C̄̅', 'C̄̅C̄̅', 'C̄̅C̄̅C̄̅', 'C̄̅D̄̅', 'D̄̅', 'D̄̅C̄̅', 'D̄̅C̄̅C̄̅', 'D̄̅C̄̅C̄̅C̄̅', 'C̄̅M̄̅' },
    d9 = { [0] = '', 'M̄̅', 'M̄̅M̄̅', 'M̄̅M̄̅M̄̅', 'M̄̅M̄̅M̄̅M̄̅' },
}
local romanDecimals = {
    -- Basic Latin letters
    N = 0, -- abbreviated "nec" or "non" adverb in Classical Latin
    I = 1, V = 5, X = 10, L = 50, C = 100,
    D = 500,-- TODO: add Medieval "apostrophic/Claudian/lunate" notations like "IƆ"
    M = 1000,
    -- U+0304 .. U+0305 : COMBINING (MACRON|OVERBAR)
    ['\204\132'] = -1000, -- (0xCC,0x84 in UTF-8) multiplier
    ['\204\133'] = -1000, -- (0xCC,0x85 in UTF-8) multiplier, considered equivalent here
    -- U+012A
    ['Ī'] = 1000, -- LATIN LETTER WITH COMBINING MACRON, canonically equivalent to 'I' and U+0304
    -- U+2160 .. U+216F : Roman capital digit symbols (compatibility, monospaced in CJK fonts)
    ['Ⅰ'] = 1, ['Ⅱ'] = 2, ['Ⅲ'] = 3, ['Ⅳ'] = 4, ['Ⅴ'] = 5, ['Ⅵ'] = 6,
    ['Ⅶ'] = 7, ['Ⅷ'] = 8, ['Ⅸ'] = 9, ['Ⅹ'] = 10, ['Ⅺ'] = 11, ['Ⅻ'] = 12,
    ['Ⅼ'] = 50, ['Ⅽ'] = 100, ['Ⅾ'] = 500, ['Ⅿ'] = 1000,
    -- U+2170 .. U+217F : Roman lowercase digit symbols (compatibility, monospaced in CJK fonts)
    ['ⅰ'] = 1, ['ⅱ'] = 2, ['ⅲ'] = 3, ['ⅳ'] = 4, ['ⅴ'] = 5, ['ⅵ'] = 6,
    ['ⅶ'] = 7, ['ⅷ'] = 8, ['ⅸ'] = 9, ['ⅹ'] = 10, ['ⅺ'] = 11, ['ⅻ'] = 12,
    ['ⅼ'] = 50, ['ⅽ'] = 100, ['ⅾ'] = 500, ['ⅿ'] = 1000,
    -- U+2180 .. U+2182 : Old Roman symbols
    ['ↀ'] = 1000, -- = 'I̅' = 'M'. TODO: add Medieval "apostrophic/Claudian/lunate" notations like "CIƆ"; do not confuse it with "CD" (400)
    ['ↁ'] = 5000, -- = 'V̅'. TODO: add Medieval "apostrophic/Claudian/lunate" notations like "DƆ" and "IƆƆ"
    ['ↂ'] = 10000, -- = 'X̅'. TODO: add Medieval "apostrophic/Claudian/lunate" notations like "CCIƆƆ"
    -- U+2183..U+2184 : ROMAN DIGIT (CAPITAL|LOWER) REVERSED C. TODO: add for "apostrophic/Claudian/lunate" notations (and support "Ɔ" OPEN O as aliases)
    -- The reversed "C" is a trailing multiplier by 10 but if it is not paired by a leading "C", the surrounded value will be divided by 2:
    -- * "I" = 1, but if followed by followed by "Ɔ", it takes the value 100:
    -- * when followed by a first "Ɔ" it multiplies it by 10 giving 1000 (assuming "CIƆ"), but if not prefixed by a pairing "C", gives 500 for "IƆ" = "D".
    -- * when followed by a second "Ɔ" it multiplies it by 10 giving 1000 (assuming "CCIƆƆ"), but if not prefixed by a pairing "C", gives 5000 for "IƆƆ" = "DƆ".
    -- * for higher multiples, using overlines is highly preferred for noting multipliers by 1000.
    -- U+2185: ROMAN NUMERAL SIX LATE FORM
    ['ↅ'] = 6, -- = 'VI' (overstriked)
    -- U+2186: ROMAN NUMERAL FIFTY EARLY FORM
    ['ↆ'] = 50, -- = 'L'
    -- U+2187 .. U+2188: ROMAN NUMERAL (ONE HUNDRED|FIFTY) THOUSAND (Archaic, rarely supported in fonts)
    ['ↇ'] = 50000, -- = 'L̅'. TODO: add Medieval "apostrophic/Claudian/lunate" notations like "DƆƆ" and "IƆƆƆ"
    ['ↈ'] = 100000, -- = 'C̅'. TODO: add Medieval "apostrophic/Claudian/lunate" notations like "CCCDƆƆ" and "CCCIƆƆƆ"
}

--[=================[
   Private functions
--]=================]

--[==[
This function returns a string containing the input value formatted as a Roman numeral.
It works for non-negative integers lower than 5 billions (up to 4999999999: this covers
all unsigned 32-bit integers), otherwise it returns the number formatted using Latin
digits. The result string will be an UTF-8-encoded plain-text alphabetic string.
]==]--
function convertArabicToRoman(value)
    -- source CDLR data (/common/rbnf/root.xml)
    if value >= 1 and value <= 4999999999 and value == math.floor(value) then
        local d0, d1, d2, d3, d4, d5, d6, d7
        d0, value = value % 10, math.floor(value / 10)
        d1, value = value % 10, math.floor(value / 10)
        d2, value = value % 10, math.floor(value / 10)
        d3, value = value % 10, math.floor(value / 10)
        d4, value = value % 10, math.floor(value / 10)
        d5, value = value % 10, math.floor(value / 10)
        d6, value = value % 10, math.floor(value / 10)
        d7, value = value % 10, math.floor(value / 10)
        d8, value = value % 10, math.floor(value / 10)
        return table.concat({
            decimalRomans.d9[value],
            decimalRomans.d8[d8],
            decimalRomans.d7[d7],
            decimalRomans.d6[d6],
            decimalRomans.d5[d5],
            decimalRomans.d4[d4],
            decimalRomans.d3[d3],
            decimalRomans.d2[d2],
            decimalRomans.d1[d1],
            decimalRomans.d0[d0],
        })
    elseif value == 0 then
        return 'N' -- for adverbs "nec" or "non" in Classical Latin (which had no zero)
    end
    return tostring(value)
end

--[==[
This function converts a plain-text string containing a Roman numeral to an integer.
It works for values between 0 and 4999999999.
]==]--
function convertRomanToArabic(roman)
    if roman == '' then return nil end
    local result, prevRomanDecimal, multiplier = 0, 0, 1
    for i = mw.ustring.len(roman), 1, -1 do
        local currentRomanDecimal = romanDecimals[mw.ustring.upper(mw.ustring.sub(roman, i, i))]
        if currentRomanDecimal == nil then
            return nil
        elseif currentRomanDecimal < 0 then
            multiplier = multiplier * -currentRomanDecimal
        else
            currentRomanDecimal, multiplier = currentRomanDecimal * multiplier, 1
            if currentRomanDecimal < prevRomanDecimal then
                result = result - currentRomanDecimal
            else
                result = result + currentRomanDecimal
                prevRomanDecimal = currentRomanDecimal
            end
        end
    end
    return result
end

--[==[
This function converts a string containing a Roman numeral to an integer.
It works for values between 0 and 4999999999.
The input string may contain HTML tags using style="text-decoration:overline" (not recommended).
]==]--
function convertRomanHTMLToArabic(roman)
    local result = convertRomanToArabic(roman)
    if result == nil then
        result = tonumber(roman)
    end
    return result
    [==[ DISABLED FOR NOW, NOT REALLY NEEDED AND NOT CORRECTLY TESTED
    local result = 0
    local overline_start_len = mw.ustring.len(overline_start)
    if mw.ustring.sub(roman, 1, overline_start_len) == overline_start then
        local end_tag_start, end_tag_end = mw.ustring.find(roman, overline_end, overline_start_len, true)
        if end_tag_start ~= nil then
            local roman_high = mw.ustring.sub(roman, overline_start_len + 1, end_tag_start - 1)
            local roman_low = mw.ustring.sub(roman, end_tag_end + 1, mw.ustring.len(roman)) or ''
            if (mw.ustring.find(roman_high, "^[mdclxvi]+$") ~= nil) and (mw.ustring.find(roman_low, "^[mdclxvi]*$") ~= nil) then
                result = convertRomanToArabic(roman_high) * 1000 + convertRomanToArabic(roman_low)
            end
        end
    end
    return result
    ]==]
end

--[==[
Helper function to handle error messages.
]==]--
function outputError(message)
    return table.concat({
        '<strong class="error">Roman Module Error: ', message,
        '</strong>[[Category:Errors reported by Module Roman]]'
    })
end

--[================[
   Public functions
--]================]

--[==[
isRoman

Tests if the trimmed input is a valid Roman numeral. Returns true if so, false if not.
For the purposes of this function, the empty string (after trimming whitespaces) is not a Roman numeral.

Parameters
   s: string to test if it is a valid Roman numeral

Error Handling:
   If the input is not a valid Roman numeral this function returns false.
]==]--
function p.isRoman(s)
    return type(s) == 'string' and convertRomanToArabic(mw.text.trim(s)) ~= nil
end

--[==[
toArabic

This function converts a Roman numeral into an Arabic numeral.
It works for values between 0 and 4999999999.
'N' is converted to 0 and the empty string is converted to nil.

Parameters
   roman: string containing value to convert into an Arabic numeral

Error Handling:
   If the input is not a valid Roman numeral this function returns nil.
]==]--
function p.toArabic(roman)
    if type(roman) == 'string' then
        roman = mw.text.trim(roman)
        local result = convertRomanToArabic(roman)
        if result == nil then
            result = tonumber(roman)
        end
        return result
    elseif type(roman) == 'number' then
        return roman
    else
        return nil
    end
end

--[==[
_Numeral

This function returns a string containing the input value formatted as a Roman numeral.
It works for values between 0 and 4999999999.

Parameters
   value: integer or string containing value to convert into a Roman numeral

Error Handling:
   If the input does not look like it contains a number or the number is outside of the
   supported range an error message is returned.
]==]--
function p._Numeral(value)
    if value == nil then
        return outputError('missing value')
    end
    if type(value) == 'string' then
        value = tonumber(value)
    elseif type(value) ~= 'number' then
        return outputError('unsupported value')
    end
    return convertArabicToRoman(value)
end

--[==[
Numeral

This function for MediaWiki converts an Arabic numeral into a Roman numeral.
It works for values between 0 and 4999999999 (includes the whole range of unsigned 32-bit integers).
Arabic numeral zero is output as 'N' (for Latin negation adverbs "nec" or "non").

Usage:
    {{#invoke:Roman|Numeral|<value>}}
    {{#invoke:Roman|Numeral}} - uses the caller's parameters

Parameters
    1: Value to convert into a Roman numeral. Must be at least 0 and less than 5,000,000.

Error Handling:
    If the input does not look like it contains a number or the number is outside of the
    supported range an error message is returned.
]==]--
function p.Numeral(frame)
    -- if no argument provided than check parent template/module args
    local args = frame.args
    if args[1] == nil then
        args = frame:getParent().args
    end
    return p._Numeral(args[1])
end

return p
Cookies help us deliver our services. By using our services, you agree to our use of cookies.