เอกสารการใช้งานสำหรับมอดูลนี้อาจสร้างขึ้นที่ มอดูล:Roman/doc

require('strict')

local getArgs = require('Module:Arguments').getArgs
local yesno = require('Module:Yesno')
local p = {}

local function error_message(message)
	message = message or 'Error in [[Module:Roman]]'
	
	local cat
	local page = mw.title.getCurrentTitle()
	local demoPages = {
		['Roman'] = true,
		['Saferoman'] = true,
		['Roman-to-arabic'] = true
	}
	if (page.nsText == 'Template' or page.nsText == 'Module') and demoPages[page.rootText] then
		cat = ''
	else
		cat = '[[Category:Errors reported by Module Roman]]'
	end
	
	return require('Module:Error')['error']({['message'] = message .. cat})
end

-- Roman-to-Arabic

local function _toArabic(roman)
	-- strip non-roman numerals and convert to uppercase for parsing
	roman = string.gsub(roman, '[^IVXLCDMivxlcdm]', ''):upper()
	local numHash = { ["M"] = 1000,
		["D"] = 500, ["C"] = 100,
		["L"] = 50, ["X"] = 10,
		["V"] = 5, ["I"] = 1 }
	local total = 0
	
	local i = 1
	local strlen = roman:len()
	-- Get last char separately. If i >= strlen, this loop's i+1 is out of bounds.
	while i < strlen do
		local thisChar = numHash[string.sub(roman, i, i)]
		local nextChar = numHash[string.sub(roman, i + 1, i + 1)]
		-- e.g. IX is 10 minus 1, and XL is 50 minus 10	
		if thisChar < nextChar then
			total = total + ( nextChar - thisChar )
			i = i + 2 -- consumed 2 (this + next)
		else
			total = total + thisChar
			i = i + 1 -- consumed 1
		end
	end
	
	-- leftover from i, i+1 loop above
	if i <= strlen then
		total = total + numHash[string.sub(roman, i, i)]
	end
	
	return total
end

function p._toArabic(args)
	local numeral = args[1]
	if numeral == nil then
		return error_message('Error in [[Module:Roman]]: first parameter is nil')
	else
		return _toArabic(numeral)
	end
end

function p.toArabic(frame)
	return p._toArabic(getArgs(frame))
end

-- Arabic-to-Roman

local function _toRoman(arabic, lc, usej)
	local arabic_digits = {
		[3] = math.floor(arabic/1000),
		[2] = math.floor((arabic % 1000)/100),
		[1] = math.floor((arabic % 100)/10),
		[0] = math.floor(arabic % 10)
	}
	
	local roman_units = {
		[1] = 'I',
		[5] = 'V',
		[10] = 'X',
		[50] = 'L',
		[100] = 'C',
		[500] = 'D',
		[1000] = 'M',
		[5000] = 'V',
		[10000] = 'X'
	}
	local function roman_digit(digit, place)
		local digit_values = {
			[0] = '',
			[1] = roman_units[place],
			[2] = roman_units[place] .. roman_units[place],
			[3] = roman_units[place] .. roman_units[place] .. roman_units[place],
			[4] = roman_units[place] .. roman_units[5 * place],
			[5] = roman_units[5 * place],
			[6] = roman_units[5 * place] .. roman_units[place],
			[7] = roman_units[5 * place] .. roman_units[place] .. roman_units[place],
			[8] = roman_units[5 * place] .. roman_units[place] .. roman_units[place] .. roman_units[place],
			[9] = roman_units[place] .. roman_units[10 * place]
		}
		return digit_values[digit]
	end
	
	local roman_digits = {
		roman_digit(arabic_digits[3], 10^3),
		roman_digit(arabic_digits[2], 10^2),
		roman_digit(arabic_digits[1], 10^1),
		roman_digit(arabic_digits[0], 10^0)
	}
	local roman = table.concat(roman_digits)
	
	if lc then
		roman = string.lower(roman)
		if usej and string.sub(roman, -1) == 'i' then
			roman = string.sub(roman, 1, -2) .. 'j'
		end
	end
	return roman
end

function p._toRoman(args)
	local numeral = tonumber(args[1])
	
	if args[1] == nil then
		return error_message('Error in [[Module:Roman]]: first parameter is nil')
	elseif numeral == nil then
		return error_message('Error in [[Module:Roman]]: ' .. args[1] .. ' is not a number')
	elseif numeral <= 0 or numeral >= 4000 or numeral ~= math.floor(numeral) then
		return error_message('Error in [[Module:Roman]]: ' .. numeral .. ' is not an integer in the range 1–3999')
	end
	
	local lc = yesno(args.lc) or false
	local usej = yesno(args.usej) or false
	
	return _toRoman(numeral, lc, usej)
end

function p.toRoman(frame)
	return p._toRoman(getArgs(frame))
end

-- Saferoman

local function substring_is_integer(str, i, j)
	i = tonumber(i)
	j = tonumber(j)
	if not str or not i or not j or i ~= math.floor(i) or j ~= math.floor(j) then
		return nil
	end
	
	local n = tonumber(string.sub(str, i, j))
	return n and n == math.floor(n)
end

function p._saferoman(args)
	if args == nil or args[1] == nil then
		return ''
	end
	
	local lc = args.lc
	local usej = args.usej
	
	local numstr = args[1]
	local i = 1
	local num
	local vin
	local ret = ''
	
	while (i <= string.len(numstr)) do
		-- If char is a number, get substring that's a number and convert to roman. Otherwise, move on.
		if substring_is_integer(numstr, i, i) then
			local j = i
			while j <= string.len(numstr) and substring_is_integer(numstr, i, j) do
				j = j + 1
			end
			num = tonumber(string.sub(numstr, i, j - 1))
			
			if num == 0 then
				ret = ret .. 'N'
			elseif num > 3999 then
				ret = ret .. '<span style="text-decoration:overline;">'
				
				vin = math.floor(num / 1000)
				num = (num - (vin * 1000))
				while (vin > 3000) do
					ret = ret .. 'M'
					vin = vin - 1000
				end
				ret = ret .. _toRoman(vin, lc, usej)
				ret = ret .. '</span>'
			end
			
			if num > 0 then
				ret = ret .. _toRoman(num, lc, usej)
			end
			
			i = j
		else
			ret = ret .. string.sub(numstr, i, i)
			i = i + 1
		end
	end
	return ret
end

function p.saferoman(frame)
	return p._saferoman(getArgs(frame))
end

return p