Modiwl:Roman

Oddi ar Wicipedia

Documentation for this module may be created at Modiwl:Roman/doc

--[[  
 
This module converts Arabic numerals into Roman numerals.  It currently works for any 
whole number between 0 and 4999999.
 
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
 
]]

local p = {}
 
-- =======================================
-- === Public Functions ==================
-- =======================================

--[[
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.
 
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


--[[
_Numeral

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.

Parameters
   input: 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(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


--[[
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 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 s and (s ~= '') and (p.toArabic(s) ~= 0)
end


--[[
toArabic

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

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 zero.
]]
function p.toArabic(roman)
	local result = 0

	if roman and (type(roman)=='string') and (roman ~= '') then
		roman = mw.ustring.lower(mw.text.trim(roman))
		result = convertRomanHTMLToArabic(roman)
	end

	return result
end


-- =======================================
-- === Private Functions =================
-- =======================================

local overline_start = '<span style="text-decoration:overline;">'
local overline_end = '</span>'

--[[
This function returns a string containing the input value formatted as a Roman numeral.  It works for values between 
0 and 4999999. The result string may contain HTML tags.
]]
function convertArabicToRomanHTML(value)
	local result = ''
	
	if (value < 5000) then
		result = convertArabicToRoman(value)
	else
		local low_value
		if (math.floor(value) % 5000) >= 4000 then
			low_value = math.floor(value) % 1000;
		else
			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


--[[
This function returns a string containing the input value formatted as a Roman numeral.  It works for values between 
0 and 4999. The result string will be a simple alphabetic string.
]]
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


--[[
This function converts a string containing a Roman numeral to an integer.  It works for values between 
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


--[[
This function converts a string containing a Roman numeral to an integer.  It works for values between 
0 and 4999.
]]
function convertRomanToArabic(roman)
	local romanDecimals = {m = 1000, d = 500, c = 100, l = 50, x = 10, v = 5, i = 1}
	local prevRomanDecimal = 0
	local result = 0
	
	for i = mw.ustring.len(roman), 1, -1 do
		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

return p