Module:Noble

Depi Wikipedya, ansiklopedi lib

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

local p = {}
local Romains = require('Module:Chif women yo')

--[[
	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 erreur		(msg)		
	if string.sub(msg, 1, 5) ~= '<span' then	-- Romains.conversion() inclut déjà le <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_romain	(chaine)	
	return '<span class="romain" style="text-transform:uppercase">' .. chaine .. '</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	(infobulle, nombre, exposant)	
	return '<abbr class="abbr" title="' .. infobulle .. '" >' .. html_romain(nombre) .. exposant .. '</abbr>'
end

--[[
	Génération d'un nombre romain à afficher.
	Argument:	la chaîne avec uniquement les caractères "IVXLCDMer"
	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 romain		(chaine)	
	if chaine == "Ier" then return html_abbr('premier',  'I', '<sup>er</sup>') end
	if chaine == "Ire" then return html_abbr('première', 'I', '<sup>re</sup>') end
	if string.find(chaine, 'e', 1, true) then return nil end
	if string.find(chaine, 'r', 1, true) then return nil end
	local test, msg, infobulle = Romains.conversion(chaine)
	if not test then erreur(msg) end
	return html_abbr(infobulle, chaine, '')
end

--[[
	Formatage d'une chaîne. Une homonymie entre parenthèses est conservée.
	Argument:	chaîne à formater
	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 formatage2	(chaine)	
	local sujet = ' ' .. chaine .. ' '		-- la chaîne à formater
	local texte = ''						-- 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 debut, fin, capture = mw.ustring.find(sujet, '%W([IVXLCDM]+[er]*)%W', init)
		if not debut then					-- plus aucun nombre romain
			return texte, mw.ustring.sub(sujet, index, -2)
		end
		capture = romain(capture)			-- formate le nombre romain
		if capture then						-- accumuler avec le nombre romain
			texte = texte .. mw.ustring.sub(sujet, index, debut)
			if string.sub(texte, -1) == ' ' then				-- Louis XIV
				texte = string.sub(texte, 1, -2) .. '&nbsp;'
			elseif string.sub(texte, -2) == ' (' then			-- Louis (XIV)
				texte = string.sub(texte, 1, -3) .. '&nbsp;('
			elseif string.sub(texte, -2) == ' [' then			-- Louis [XIV]
				texte = string.sub(texte, 1, -3) .. '&nbsp;['
			end
			texte = texte .. capture
			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.
	Argument:	chaîne à formater
	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 formatage1	(chaine)	
	if string.sub(chaine, -1, -1) ~= ")" then
		return formatage2(chaine)
	end
	local paren = 0		-- position de la dernière parenthèse ouvrante
	local teste			-- dernier index testé
	repeat
		teste = string.find(chaine, "(", paren + 1, true)
		if teste then paren = teste end
	until teste == nil
	if paren == 0 then return formatage2(chaine)
	              else return formatage2(string.sub(chaine, 1, paren - 1))
	end
end

--[[
	Formatage du texte à afficher.
	Arguments:	arg1, arg2
		arg1:	premier argument reçu de l'appel du modèle
		arg2:	deuxième argument reçu de l'appel du modèle
	Résultat:	texte à afficher
	]]
local function affichage	(arg1, arg2)
	local texte, reste = "", ""
	if not arg2 then							-- second argument absent: on formate le premier argument avec le complément
		texte, reste = formatage1(arg1)
	elseif arg2 == "-" or arg2 == "" then		-- second argument "-": on formate le premier argument sans le complément
		texte = formatage1(arg1)
	elseif arg2 == "+" then						-- second argument "+": on formate le premier argument avec le complément et l'homonyùie
		texte, reste = formatage2(arg1)
	else										-- second argument présent, avec ou sans un numéro
		texte, reste = formatage2(arg2)
		if texte == "" then						-- pas de numéro: on prend le nom et le numéro du premier argument
			texte = formatage1(arg1)
		end
	end
	-- Assemblage des éléments
	texte = mw.text.trim(texte)
	reste = mw.text.trim(reste)
	if reste == "" then							-- Louis II
		-- Pas de reste, le texte est bon
	elseif texte == "" then						-- Charlemagne
		-- Il n'y avait pas de chiffres romains,
		-- il faut prendre le reste sans ajouter d'espace
		texte = reste
	else
		local debut = string.sub(reste, 1, 1)	-- premier caractère du reste
		if	debut == ')' or						-- {{noble|Louis (II)}}
			debut == ']' or						-- {{noble|Louis [II]}}
			debut == ',' or						-- {{noble|Louis II, le Jeune}}
			debut == '.'						-- {{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.
			texte = texte .. reste
		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().
			texte = texte .. " " .. reste
		end
	end
	-- Correction du bug qui empêche la gestion correcte des insécables dans un lien wiki
	texte = mw.ustring.gsub(texte, '« ', '«&nbsp;')
	texte = mw.ustring.gsub(texte, ' »', '&nbsp;»')
	return texte
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 de 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 nettoyage	(chaine)	
	if type(chaine) ~= "string" then return chaine end
	return chaine
			-- 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 valide1		(valeur)	
	if valeur == nil then erreur("L'argument 1 est requis") end
	if valeur == ""  then erreur("L'argument 1 ne peut pas être vide") end
	if valeur == "-" then erreur("L'argument 1 ne peut pas être un simple tiret") end
	return nettoyage(valeur)
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 valide2		(valeur)	
	return nettoyage(valeur)
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 traitement	(frame)		
	local args  = frame:getParent().args
	local arg1  = valide1(args[1]);
	local arg2  = valide2(args[2]);
	local texte = affichage(arg1, arg2);
	return texte, arg1
	end

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

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

return p