La documentation pour ce module peut être créée à Module:Nôblo/doc

local p = {}
local Romens = require('Module:Chifros romens')

--[[
	Traitement d'une erreur.
	Argument:	message d'erreur en texte simple
	Résultat:	la fonction ne retourne pas à l'appelant, mais après le pcall()
	
	Lorsqu'une erreur est détectée, par exemple un argument invalide dans
	l'appel du modèle, cette fonction est appelée. Elle termine tous les appels
	de fonction, jusqu'à atteindre un appel à la fonction pcall(), qui retourne
	false et le message (s'il n'y avait pas eu d'erreur dans le traitement,
	pcall retournait true et les valeurs retournées par la fonction qu'on lui
	passe en premier argument).
	
	Cette méthode a l'intérêt de rendre le code nettement plus lisible en ne
	traînant pas partout des codes et des messages d'erreur, et accessoirement
	de gagner un brin de performance.
	
	Dans ce module, la fonction erreur() peut être appelée à peu près n'importe
	quand. Voir les fonctions p.Noble() et p.Noble_() pour l'utilisation de
	pcall().
	]]
local function fota(msg)		
	if string.sub(msg, 1, 5) ~= '<span' then
		-- Romens.conversion() rapond dedens ja lo <span>
		msg = '<span class="error">' .. msg .. '</span>'
	end
	error(msg, 0)
end

--[[
	Génération d'un <span> enrobant un nombre romain.
	Argument:	chaîne avec des chiffres romains majuscules
	Résultat:	la même chaîne enrobée dans un <span> avec le bon style
	]]
local function html_romen(chena)	
	return	'<span class="romen" style="text-transform:uppercase">'
			.. chena
			.. '</span>'
end

--[[
	Génération d'un <abbr> avec l'infobulle, le nombre romain et un exposant
	éventuel.
	Arguments:	infobulle	à afficher
				nombre		en chiffres romains majuscules uniquement
				exposant	peut être 'er', 're', '' (chaîne vide)
	Résultat:	les arguments enrobés dans un <abbr>
	]]
local function html_abbr(enfobula, nombro, exposent)	
	return	'<abbr class="abbr" title="' .. enfobula .. '" >'
			.. html_romen(nombro) .. exposent .. '</abbr>'
end

--[[
	Génération d'un nombre romain à afficher.
	Argument:	chaine, noerror
		chaine:		la chaîne avec uniquement les caractères "IVXLCDMer"
		noerror:	empeche l'affichage des erreurs
	Résultat:	le HTML pour afficher le nombre romain, ou
				nil si la chaîne doit être ignorée
	Notes:
	-	Si un argument valide comme 'Ier', 'Ire', 'XIV' est fourni, retourne le
		HTML.
	-	Si un argument invalide comme 'Le' ou 'Veere' est fourni, retourne nil.
		Dans ce case, l'analyseur doit essayer de trouver un nombre plus loin.
	-	Si un argument avec on nombre romain clairement invalide comme 'VV' est
		fourni, une erreur est causée.
	]]
local function romen(chena, noerror)	
	if chena == "Iér" then return html_abbr('premiér',  'I', '<sup>ér</sup>') end
	if chena == "Iére" then return html_abbr('premiére', 'I', '<sup>ére</sup>') end
	if string.find(chena, 'e', 1, true) then return nil end
	if string.find(chena, 'r', 1, true) then return nil end
	local test, msg, enfobula = Romens.conversion(chena)
	if not test then if noerror then return nil else fota(msg) end end
	return html_abbr(enfobula, chena, '')
end

--[[
	Formatage d'une chaîne. Une homonymie entre parenthèses est conservée.
	Argument:	chaine, noerror
		chaine:		chaîne à formater
		noerror:	empêche l'affichage des erreurs
	Résultat:	texte, complement
		texte:		nom et numéro formatés, par exemple Louis II, avec mise en
					forme; vide si pas de numéro
		complement:	le complément de nom, par exemple de Bavière (1229-1294)
		
	La recherche de patterns en unicode est relativement coûteuse, donc on essaie
	de ne pas multiplier les recherches: on ratisse large et on trie après. Comme
	il peut y avoir plusieurs nombres en chiffres romains dans la chaîne, on
	utilise une boucle pour les trouver un par un et on accumule le texte formaté
	au fur et à mesure.
	
	La fonction utilise les variables suivantes:
	sujet:	la chaîne à formater, à laquelle on a ajouté une espace au début et
			à la fin parce que mw.ustring.find() a besoin d'un caractère avant
			et après les chiffres romains, pour détecter un nombre romain dans
			un mot isolé, et ne pas détecter le D dans Désirée.
	texte:	accumule le texte analysé jusqu'ici; soit vide, soit se terminant par
			un nombre romain formaté. Peut contenir plusieurs fois des nombres
			romains.
	index:	index dans le sujet du premier caractère qui n'a pas encore été
			copié dans la variable texte.
			Exprimé en caractères, pas en bytes.
	init:	position où débuter la recherche de pattern. Souvent identique à
			index, mais pourrait en différer. Par exemple avec le sujet " Louis
			de Veere Ier " le pattern trouvera d'abord "Veere" mais
			romain(capture) retournera nil, et donc texte restera inchangé
			(vide), index aussi (2) mais init indiquera le premier caractère
			après "Veere" (16).
			Exprimé en caractères, pas en bytes.
	
	Cette fonction est le coeur du module, à ne modifier qu'avec d'infinies
	précautions !
	]]
local function formatajo2(chena, noerror)	
	local sujet = ' ' .. chena .. ' '	-- la chaîne à formater
	local texto = ''			-- accumule le texte formaté
	local index = 2				-- premier caractère de sujet non copié dans texte
	local init  = 1				-- position où démarrer la recherche de pattern
	while true do
		local comencement, fin, captura
			  = mw.ustring.find(sujet, '%W([IVXLCDM]+[ér]*)%W', init)
		if not comencement then					-- plus aucun nombre romain
			return texto, mw.ustring.sub(sujet, index, -2)
		end
		captura = romen(captura, noerror)			-- formate le nombre romain
		if captura then						-- accumuler avec le nombre romain
			texto = texto .. mw.ustring.sub(sujet, index, comencement)
			if string.sub(texto, -1) == ' ' then				-- Louis XIV
				texto = string.sub(texto, 1, -2) .. '&nbsp;'
			elseif string.sub(texto, -2) == ' (' then			-- Louis (XIV)
				texto = string.sub(texto, 1, -3) .. '&nbsp;('
			elseif string.sub(texto, -2) == ' [' then			-- Louis [XIV]
				texto = string.sub(texto, 1, -3) .. '&nbsp;['
			end
			texto = texto .. captura
			index = fin
			init  = fin						-- .. et chercher le suivant
		else								-- fausse alerte, par exemple 'Veere'
			init = fin
		end
	end
end

--[[
	Formatage d'une chaîne. Une homonymie entre parenthèses est ignorée.
	Arguments:	chaine, noerror
		chaine:		chaîne à formater
		noerror:	empêcher l'affichage des erreurs
	Résultat:	texte, complement
		texte:		nom et numéro formatés, par exemple Louis II, avec mise en
					forme; vide si pas de numéro
		complement:	le complément de nom, par exemple de Bavière
	
	Pour l'instant, l'homonymie est défini comme le texte à partir de la dernière
	parenthèse ouvrante, pourvu que le dernier caractère de la chaîne soit une
	parenthèse fermante. Une définition plutôt rudimentaire, qui devrait
	probablement être affinée avec l'expérience.
	]]
local function formatajo1(chena, noerror)	
	if string.sub(chena, -1, -1) ~= ")" then
		return formatajo2(chena, noerror)
	end
	local paren = 0		-- position de la dernière parenthèse ouvrante
	local eprova		-- dernier index testé
	repeat
		eprova = string.find(chena, "(", paren + 1, true)
		if eprova then paren = eprova end
	until eprova == nil
	if paren == 0 then return formatajo2(chena, noerror)
	              else return formatajo2(string.sub(chena, 1, paren - 1), noerror)
	end
end

--[[
	Formatage du texte à afficher.
	Arguments:	arg1, arg2, arg3, noerror
		arg1:		premier argument reçu de l'appel du modèle
		arg2:		deuxième argument reçu de l'appel du modèle
		arg3:		troisième argument reçu de l'appel du modèle
		noerror:	empêche l'affichage des erreurs
	Résultat:	texte à afficher
	]]
local function afichajo(arg1, arg2, arg3, noerror)
	local texto, resto = "", ""
	if not arg2 or arg2 == "*" then
		-- second argument absent: on formate le premier argument
		-- avec le complément
		texto, resto = formatajo1(arg1, noerror)
	elseif arg2 == "-" or arg2 == "" then
		-- second argument "-": on formate le premier argument
		-- sans le complément
		texto = formatajo1(arg1, noerror)
	elseif arg2 == "+" then
		-- second argument "+": on formate le premier argument
		-- avec le complément et l'homonyùie
		texto, resto = formatajo2(arg1, noerror)
	else
		-- second argument présent, avec ou sans un numéro
		texto, resto = formatajo2(arg2, noerror)
		if texto == "" then
			-- pas de numéro: on prend le nom et le numéro du premier argument
			texto = formatajo1(arg1, noerror)
		end
	end
	-- Assemblage des éléments
	texto = mw.text.trim(texto)
	resto = mw.text.trim(resto)
	if resto == "" then							-- Louis II
		-- Pas de reste, le texte est bon
	elseif texto == "" then						-- Charlemagne
		-- Il n'y avait pas de chiffres romains,
		-- il faut prendre le reste sans ajouter d'espace
		texto = resto
	else
		local comencement = string.sub(resto, 1, 1)	-- premier caractère du reste
		if	comencement == ')' or						-- {{noble|Louis (II)}}
			comencement == ']' or						-- {{noble|Louis [II]}}
			comencement == ',' or						-- {{noble|Louis II, le Jeune}}
			comencement == '.'						-- {{noble|Karl I. der Große}}
		then
			-- Le reste commence par un caractère particulier,
			-- il ne faut pas insérer d'espace avant le reste.
			texto = texto .. resto
		else									-- {{noble|Louis II le Jeune}}
			-- Le reste ne commence pas par un caractère particulier,
			-- il faut insérer une espace avant le reste, puisque des
			-- espaces au début avaient été supprimées par trim().
			texto = texto .. " " .. resto
		end
	end
	if arg3 then
		-- troisième argument présent
		local supl, resto = formatajo2(arg3, noerror)
		local comencement
		comencement = string.sub(supl, 1, 1)		-- premier caractère du supplément
		if	comencement == ')' or					-- {{noble|Louis (II)}}
			comencement == ']' or					-- {{noble|Louis [II]}}
			comencement == ',' or					-- {{noble|Louis II, le Jeune}}
			comencement == '.'					-- {{noble|Karl I. der Große}}
		then
			-- Le supplément commence par un caractère particulier,
			-- il ne faut pas insérer d'espace avant le supplément.
			texto = texto .. supl
		else								-- {{noble|Louis II le Jeune}}
			-- Le supplément ne commence pas par un caractère particulier,
			-- il faut insérer une espace avant le supplément, puisque des
			-- espaces au début avaient été supprimées par trim().
			texto = texto .. " " .. supl
		end
		comencement = string.sub(resto, 1, 1)		-- premier caractère du reste
		if	comencement == ')' or					-- {{noble|Louis (II)}}
			comencement == ']' or					-- {{noble|Louis [II]}}
			comencement == ',' or					-- {{noble|Louis II, le Jeune}}
			comencement == '.'					-- {{noble|Karl I. der Große}}
		then
			-- Le reste commence par un caractère particulier,
			-- il ne faut pas insérer d'espace avant le reste.
			texto = texto .. resto
		else									-- {{noble|Louis II le Jeune}}
			-- Le supplément ne commence pas par un caractère particulier,
			-- il faut insérer une espace avant le reste, puisque des
			-- espaces au début avaient été supprimées par trim().
			texto = texto .. " " .. resto
		end
	end
	-- Correction du bug qui empêche la gestion correcte des insécables
	-- dans un lien wiki
	texto = mw.ustring.gsub(texto, '« ', '«&nbsp;')
	texto = mw.ustring.gsub(texto, ' »', '&nbsp;»')
	return texto
end

--[[
	Nettoyage d'une chaîne.
	Argument:	valeur reçue de l'appel du modèle
	Résultat:	l'argument, avec les diverses espaces HTML remplacés par des
				espaces normales
	
	Ce code est utile, par exemple avec arg2 = "Louis&nbsp;XIV". Si on ne le
	fait pas, les résultats ne seront pas les résultats attendus.
	]]
local function neteyajo(chena)	
	if type(chena) ~= "string" then return chena end
	return chena
			-- nbsp
			:gsub( '\194\160',		' ' )
			:gsub( '&#160;',		' ' )
			:gsub( '&nbsp;',		' ' )
			-- narrow nbsp
			:gsub( '\226\128\175',	' ' )
			:gsub( '&#8239;',		' ' )
			-- thin space
			:gsub( '\226\128\137',	' ' )
			:gsub( '&#8201;',		' ' )
			:gsub( '&thinsp;',		' ' )
			-- simple space
			:gsub( '&#32;',			' ' )
end

--[[
	Validation du premier argument, qui doit être présent, non vide et différent
	de "-".
	Argument:	valeur reçue de l'appel du modèle
	Résultat:	l'argument, éventuellement modifié 
	]]
local function valido1(valor)	
	if valor == nil then
		fota("L’argument 1 est nècèssèro")
		end
	if valor == ""  then
		fota("L’argument 1 pôt pas étre vouedo")
		end
	if valor == "-" then
		fota("L’argument 1 pôt pas étre un simplo terèt")
		end
	return neteyajo(valor)
end

--[[
	Validation du deuxième argument, qui peut être présent, vide, ou  "-".
	Argument:	valeur reçue de l'appel du modèle
	Résultat:	l'argument, éventuellement modifié
	]]
local function valido2(valor)	
	return neteyajo(valor)
end

--[[
	Validation du troisième argument optionnel.
	Argument:	valeur reçue de l'appel du modèle
	Résultat:	l'argument, éventuellement modifié
	]]
local function valido3(valor)	
	return neteyajo(valor)
end

--[[
	Traitement de l'appel.
	Argument:	frame reçu du modèle
	Résultat:	le texte à retourner au modèle, et
				le premier argument (pour la construction du lien)
	]]
local function tretament(frame)		
	local args  = frame:getParent().args
	local arg1  = valido1(args[1]);
	local arg2  = valido2(args[2]);
	local arg3  = valido3(args[3]);
	local texto = afichajo(arg1, arg2, arg3);
	return texto, arg1
	end

--[[
	Formatage d'un nom dynastique, avec lien: modèle Noble.
	Note: Le <nowiki /> est nécessaire pour supporter [{{Noble:Louis XIV}}] sans
	obtenir des triple crochets dans l'affichage.
	]]
function p.Noblo(frame)		
	local ok, texto, arg1 = pcall(tretament, frame)
	if ok then	return mw.getCurrentFrame():callParserFunction('#tag', 'nowiki') .. '[[' .. arg1 .. '|' .. texto .. ']]'
		  else	return texto
	end
end

--[[
	Formatage d'un nom dynastique, sans lien: modèle Noble-.
	]]
function p.Noblo_(frame)		
	local ok, texto = pcall(tretament, frame)
	return texto
end

function p.labelEnfocajon(label)
	return afichajo(neteyajo(label), '+', nil, true)
end

return p