Aller au contenu

Module:Chiffres romains

Cette page fait l’objet d’une mesure de semi-protection étendue.
Une page de Wikipédia, l'encyclopédie libre.
Ceci est une version archivée de cette page, en date du 15 avril 2022 à 08:24 et modifiée en dernier par Od1n (discuter | contributions) ("niveau_par_chiffre" avait un état global au lieu d'être constant, ce qui aurait provoqué des bugs par exemple si on écrivait une nouvelle fonction dans ce module faisant plusieurs fois usage de p.conversion()). Elle peut contenir des erreurs, des inexactitudes ou des contenus vandalisés non présents dans la version actuelle.

 Documentation[voir] [modifier] [historique] [purger]

Ce module est utilisé par (non exhaustif) :

Voir aussi :

local p = {}
local typeromains = "[IVXLCDM]+"
local typearabes = "[0-9]+"
local niveau_par_chiffre = {
	['I']  = 1,
	['IV'] = 4,
	['V']  = 5,
	['IX'] = 9,
	['X']  = 10,
	['XL'] = 40,
	['L']  = 50,
	['XC'] = 90,
	['C']  = 100,
	['CD'] = 400,
	['D']  = 500,
	['CM'] = 900,
	['M']  = 1000,
}

-- Restitution de la valeur d'une suite de chiffres romains identiques : XXX..., III..
-- On récupère la chaîne de départ tronquée de la séquence traitée, le chiffre romain composant cette séquence et le nombre d'occurrences
function p._niveau(romains)
	local longueur_chaine = string.len(romains)
	local valeur = 0
	local rang = string.sub(romains, 1, 1)
	local caractere = rang
	while (valeur < longueur_chaine) and (caractere == rang) do
		valeur = valeur + 1
		romains = string.sub(romains, 2)
		caractere = string.sub(romains, 1, 1)
	end
	return romains, rang, valeur
end

-- Fonction destinée à gérer les séquences particulières (IV, IX, XL, XC, CD, CM), avec test de cohérence
function p._uniques(depart, romains, chaine)
	local nb = 0
	local resultat = 0
	local test = true
	local message = ''

	romains, nb = string.gsub(romains, chaine, '')
	if nb > 1 then
		test = false
		message = '<span class="error">Nombre romain incorrect, répétition de séquence incohérente ('..nb..' '..chaine..')</span>'
	else
		if nb == 1 then
			resultat = niveau_par_chiffre[chaine]
		end
	end
	return test, message, resultat, romains
end

-- Conversion d'un nombre romain et entier du système décimal, avec tests de cohérence
-- (fonction aussi utilisée dans [[Module:Nom dynastique]])
function p.conversion(romains)
	local rangs_atteints = {}
	for k, _ in pairs(niveau_par_chiffre) do
		rangs_atteints[k] = false
	end
	local test = true
	local message = ''
	local resultat, valeur = 0, 0
	local rang = ''
	local depart = romains
	local niveau = 10000
-- Cas des valeurs obtenues par soustraction du chiffre placé à gauche du chiffre significatif, séquences qui doivent être uniques
-- A/  Unités : IV et IX
	if string.match(romains, 'IX') then
		test, message, resultat, romains = p._uniques(depart, romains, 'IX')
		if not test then return test, message, resultat end
	else
		if string.match(romains, 'IV') then
			test, message, resultat, romains = p._uniques(depart, romains, 'IV')
			if not test then return test, message, resultat end
			rangs_atteints['V'] = true
		end
	end

-- B  Dizaines : XL et XC
	if string.match(romains, 'XL') then
		test, message, valeur, romains = p._uniques(depart, romains, 'XL')
		if not test then return test, message, resultat end
		rangs_atteints['L'] = true
	else
		if string.match(romains, 'XC') then
			test, message, valeur, romains = p._uniques(depart, romains, 'XC')
			if not test then return test, message, resultat end
		end
	end
	resultat = resultat + valeur

-- C/  centaines : CD et CM
	valeur = 0
	if string.match(romains, 'CD') then
		test, message, valeur, romains = p._uniques(depart, romains, 'CD')
		if not test then return test, message, resultat end
		rangs_atteints['D'] = true
	else
		if string.match(romains, 'CM') then
			test, message, valeur, romains = p._uniques(depart, romains, 'CM')
			if not test then return test, message, resultat end
		end
	end
	resultat = resultat + valeur

-- Une fois les cas particuliers traités, la chaine ne contient plus que des "séquences" de chiffres identiques
-- Ces séquences sont limitées à 4 occurrences (écriture simplifiée), sauf pour les milliers
-- Contrôle de l'unicité de présence des chiffres V, L et D
-- Contrôle de cohérence (on ne peut pas avoir une séquence du chiffre n si une séquence de chiffres d'un niveau inférieur à n a déjà été traitée)
	valeur = 0
	test = true

	while romains ~= '' do
		romains, rang, valeur = p._niveau(romains)
		if not string.match(rang, typeromains) then
			test = false
			message = '<span class="error">Chiffre romain incorrect ('..rang..')</span>'
			break
		end
		if ((valeur > 4) and not (rang == 'M'))
		or rangs_atteints[rang]
		or (((rang == 'V') or (rang == 'L') or (rang == 'D')) and (valeur > 1)) then
			test = false
			message = '<span class="error">Nombre romain incorrect, répétition de chiffre ('..rang..')</span>'
			break
		end
		if niveau_par_chiffre[rang] > niveau then
			test = false
			message = '<span class="error">Nombre romain incorrect, séquence incohérente ('..depart..')</span>'
			break
		end
		rangs_atteints[rang] = true
		niveau = niveau_par_chiffre[rang]
		resultat = resultat + valeur*niveau
	end
	return test, message, resultat
end

-- À partir d'un nombre romain, fournit une chaine de caractères composée de ce nombre avec une infobulle donnant sa valeur en chiffres arabes
function p._RomainsInfobulle(romains)
	local test, message, nombre = p.conversion(romains)

	if not test then
		return message
	else
		return '<abbr class="abbr" title="' .. nombre .. '"><span class="romain" style="text-transform:uppercase">' .. romains .. '</span></abbr>'
	end
end

-- Décompose la chaîne transmise en nom/chiffres romains/complément
function p._chaine(chaine)
	local haystack = ' ' .. chaine .. ' '

	local offsetStart, offsetEnd, capture = haystack:find('%W(' .. typeromains .. ')%W')

	if offsetStart then
		return haystack:sub(2, offsetStart) .. p._RomainsInfobulle(capture) .. haystack:sub(offsetEnd, -2)
	else
		return chaine
	end
end

-- Fonction utilisée par le modèle {{nobr romains}}
function p.NobrRomains(frame)
	local chaine = frame.args[1]

	-- Si le paramètre passé est vide, retour
	if chaine == '' then
		return '<span class="error">Aucun paramètre</span>'
	end

	return '<span class="nowrap">' .. p._chaine(chaine) .. '</span>'
end

-- Convertit le nombre passé en paramètre en chiffres romains
function p._ChiffresRomains(chiffre)
	local u = { "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX" }
	local d = { "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC" }
	local c = { "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM" }
	local m = { "", "M", "MM", "MMM", "MMMM" }
	local ret = ""
	if (chiffre < 0) then
		ret = "-"
		chiffre = -chiffre
	end
	if (chiffre >= 5000) then
		return nil
	end
	if (chiffre >= 1000) then
		local mil = math.floor(chiffre / 1000)
		ret = ret .. m[mil + 1]
		chiffre = chiffre % 1000
	end
	if (chiffre >= 100) then
		local cen = math.floor (chiffre / 100)
		ret = ret .. c[cen + 1]
		chiffre = chiffre % 100
	end
	if (chiffre >= 10) then
		local diz = math.floor (chiffre / 10)
		ret = ret .. d[diz + 1]
		chiffre = chiffre % 10
	end
	return ret .. u[chiffre + 1]
end

-- notes :
-- * cette fonction est actuellement inutilisée
-- * il existe un modèle au rôle similaire : [[Modèle:Nombre en romain]]
function p.ChiffresRomains(frame)
	local args = frame:getParent().args
	if args[1] then
		if args[1]:match('^%-?' .. typearabes .. '$') then
			return p._ChiffresRomains(tonumber(args[1]))
		end
	end
end

return p