Documentation[créer] [purger]
--[[
  Ceci est un essai de taxobox dont la classification est gérée comme données
  « externes », afin de tester une centralisation des informations de classification
  (peut-être en attendant wikidata).
  Pour le moment la classification est restreinte aux reptiles, et partielle.
  
  Ceci n'est pas un module de prod.
--]]

local p = {}

-- données globales liées à la classification initiale
p.regne = nil -- le règne initial
p.cache_regne = nil -- faut-il masquer le règne ?
p.limite = nil -- le rang limite à traiter
p.tlimite = nil -- le type de limite
p.classification = nil -- la classification suivie

function p.erreur(texte)
	if (type(texte) ~= "string") then
		texte = "Erreur non précisée"
	end
	return '<span class="error">' .. texte .. '</span>'
end



--[[
  Fonction fille de la suivante.
  Cette fonction insert dans la table fournie (qui peut être vide)
  la liste des éléments de classification correspondant aux paramètres
  
  Modifie la table passée et retourne le quadruplet {rang, nom, classification, nb}
  correspondant à ce qui se trouve au-dessus. Si l'une des valeurs (au moins)
  vaut nil c'est qu'on est arrivé en haut de la classification.
  nb est le nombre d'entrées ajoutées
  Si une erreur se produit retourne un "string" contenant l'erreur.
--]]
function p.taxoboft_table(resu, rang, nom, classification, premier)
	-- précaution : resu doit être une table
	if (type(resu) ~= "table") then
		return { false, p.erreur("Interne : l'élément fourni n'est pas une table.") }
	end

	-- vérification éléments obligatoires
	if (rang == nil or nom == nil or classification == nil) then
		return { false, p.erreur("Rang, nom et classification obligatoires.") }
	end
	
	-- on cherche si la classification existe en chargeant le module de données
	local data = mw.loadData('Module:Taxoboft/data ' .. classification)
	if (data == nil) then
		return { false, p.erreur("La classification '" .. classification .. "' est inconnue.") }
	end
	-- si premier passage on enregistre les infos nécessaires
	if (premier) then
		p.classification = data.code_classification
		p.regne = data.regne_classification
		p.cache_regne = data.cache_regne
	end
	-- si limite indiquée dans cette classification et pas déjà précisée on la récupère
	if (data.limite_classification ~= nil and p.limite == nil) then
		p.limite = data.limite_classification
	end
	-- type de limite
	if (data.limite_classification_stricte == true and p.tlimite == nil) then
		p.tlimite = true
	end

	-- données de traitement : le rang et le nom de l'entité
	local cur_rang = rang
	local cur_nom = nom
	local sauve_rang = rang
	local sauve_nom = nom
	-- boucle de parcours à partir de l'élément courant
	local nb = 0
	while true
	do
		-- si les deux sont nil on quitte la boucle (on est arrivé en haut)
		if (cur_rang == nil or cur_nom == nil) then
			break
		end
		
		-- on cherche la table liée à ce rang
		local rtable = data.classification[cur_rang]
		if (rtable == nil) then
			-- problème
			return { false, p.erreur("Aucune entrée pour le rang '" .. cur_rang .. "' dans la classification '"
			            .. classification .. "'.") }
		end
		-- on parcours cette table pour trouver notre élément
		local i = 1
		local trouve = nil
		local nbm = 0
		while (rtable[i] ~= nil) do
			-- on cherche l'élément
			trouve = rtable[i][cur_nom]
			if (trouve ~= nil) then
				-- on note le nombre de frères
				nbm = rtable[i]["nombre"]
				break
			end
			i = i + 1
		end
		if (trouve == nil) then
			-- ce taxon n'existe pas dans ce rang de cette classification
			return { false, p.erreur("Aucune entrée pour le taxon '" .. cur_nom .. "' dans la classification '"
			            .. classification .. "' au rang '" .. cur_rang .. "'.") }
		end
		-- on a trouvé l'élément : on le stocke dans les résultats
		local tbl = { ["rang"] = cur_rang, ["nom"] = cur_nom }
		-- si seul de son niveau → monotypique
		if (nbm == 1) then
			tbl["monotypique"] = true
		end
		-- informations additionnelles (si présentes)
		if (trouve == 1) then
			-- il est noté pour catégorisation
			tbl["catégorie"] = true
		elseif (type(trouve) == "table") then
			if (trouve[1] == 1) then
				-- il est noté pour catégorisation
				tbl["catégorie"] = true
			end
			if (trouve[2] ~= nil) then
				-- nom réel de l'article
				tbl["article"] = trouve[2]
			end
		end
		-- si limite est indiqué, et que le type de limite est "strict", on n'insert pas (et on quitte)
		--  si c'est lui
		if (p.limite ~= nil and p.tlimite == true and tbl["rang"] == p.limite) then
			break -- on sort, on aurait atteint la limite
		end
		-- on insert l'élément dans les résultats (note : la table est à l'envers)
		table.insert(resu, tbl)
		nb = nb + 1 -- position du dernier
		-- si limite est indiqué (on n'est pas en type strict), on quitte après insertion
		--  si c'est lui
		if (tbl["rang"] == p.limite) then
			break -- on sort, on a atteint la limite
		end

		-- on mémorise le dernier rang
		sauve_rang = cur_rang
		sauve_nom = cur_nom
		-- on passe au parent
		cur_rang = rtable[i]["parent"][1]
		cur_nom = rtable[i]["parent"][2]
	end

	-- on est au bout. Est-ce qu'il y a une classification au-dessus ?
	if (data.classification_parente == "SOMMET") then
		-- non, on indique la fin
		return {nil, nil, nil, nb}
	end
	-- sinon on retourne le rang+nom du dernier traité, plus la classification à suivre
	return {sauve_rang, sauve_nom, data.classification_parente, nb}
end


--[[
  Fonction principale : génère la partie classification d'une taxobox (classification classique)
  à partir du genre ou du genre et de l'espèce (ou du genre, de l'espèce et de la sous-espèce)
  Paramètres :
    rang= : le rang du taxon considéré
    nom= : le nom du taxon considéré
    nom2= : si espèce, la deuxième partie du nom
    nom3= : si sous-espèce, la troisième partie du nom
    image= : le fichier image (optionnel)
    légende= : la légende associée (optionnel)
    auteur= : l'auteur (optionnel)
    classification= : la classification à suivre
--]]
function p.taxoboft_reelle(rang, nom, nom2, nom3, image, legende, classification, auteur, auteur2, auteur3, auteur4, spmono)

	-- vérification éléments obligatoires
	if (rang == nil or nom == nil or classification == nil) then
		return p.erreur("Rang, nom et classification obligatoires.")
	end
	-- table des auteurs (plus pratique)
	local auteurs = { auteur, auteur2, auteur3, auteur4 }

	-- on crée une table vide pour recevoir les résultats
	local resu = {}
	local nb = 0
	-- on traite la demande dans une boucle (pour passer aux classifications au-dessus)
	local c_rang = rang
	local c_nom = nom
	local c_classif = classification
	local premier = true
	while true do
		-- on reçoit un triplet {rang, nom, classification} ou bien une chaîne d'erreur (+ nb traité)
		local from = p.taxoboft_table(resu, c_rang, c_nom, c_classif, premier)
		-- erreur ?
		if (type(from[1]) == "false") then
			return from[2]
		end
		-- on ajoute le nombre d'éléments insérés
		nb = nb + from[4]
		-- si retour = nil on a terminé la classification
		if (from[1] == nil or from[2] == nil or from[3] == nil) then
			break
		end
		-- paramètres du rang supérieur
		c_rang = from[1]
		c_nom = from[2]
		c_classif = from[3]
		
		-- on supprime le dernier élément, qui serait dupliqué à la jointure
		table.remove(resu)
		nb = nb - 1
		if (premier) then
			premier = false
		end
	end

	-- parcours terminé : on a la classification
	local tres = ""
	local tres2 = ""
	local categorie = nil
	-- on affiche les données dans l'ordre
	-- d'abord l'entête taxobox
	local ngb = nom
	if (nom2 ~= nil) then
		ngb = ngb .. " " .. nom2
	end
	if (nom3 ~= nil) then
		ngb = ngb .. " " .. nom3
	end
	tres2 = tres2 .. "{{Taxobox début|" .. p.regne .. "|" .. ngb .. "|" .. (image or "") .. "|" .. (legende or "") .. "|classification=" .. p.classification
	if (p.cache_regne) then
		tres2 = tres2 .. "|règne=cacher}}\n"
	else
		tres2 = tres2 .. "}}\n"
	end
	tres = tres .. "taxobox début : règne='" .. p.regne .. "', classification='" ..
	   p.classification .. "', image='" .. (image or " ") .. "', légende='" .. (legende or " ") .. "', cacher-règne="
	if (p.cache_regne) then
		tres = tres .. "oui<br/>"
	else
		tres = tres .. "non<br/>"
	end
	-- s'il y a un nom d'espèce on insert une entrée pour celle-ci
	-- note : impossible de savoir si une espèce est monotypique (pas présent dans la classification)
	--  on utilise donc un paramètre dédié
	if (nom2 ~= nil) then
		local tbl = { ["rang"] = "espèce", ["nom"] = nom .. " " .. nom2 }
		if (spmono) then
			tbl["monotypique"] = true
		end
		table.insert(resu, 1, tbl)
	end
	-- s'il y a une sous-espèce on insert une entrée pour celle-ci
	-- une sous-espèce monotypique n'existe pas, c'est pratique
	if (nom3 ~= nil and nom2 ~= nil) then
		local tbl = { ["rang"] = "sous-espèce", ["nom"] = nom .. " " .. nom2 .. " " .. nom3 }
		table.insert(resu, 1, tbl)
	end
	-- on part du deuxième pour compter le nombre de monotypiques, afin d'afficher en monotypique
	--  (taxobox taxon) les N premiers si besoin
	local nbm = 1 -- inclure le premier
	for i = 2,nb do
		if (resu[i]["monotypique"]) then
			nbm = nbm + 1
		else
			-- fini
			break
		end
	end
	for i = nb,1,-1 do
		-- si présence de catégorie on le note
		if (resu[i]["catégorie"] ~= nil) then
			if (resu[i]["nom"] == nom) then
				-- article principal de la catégorie
				categorie = resu[i]["nom"] .. "|*"
			else
				categorie = resu[i]["nom"]
			end
		end
		if (i <= nbm) then
			-- le taxon décrit
			tres2 = tres2 .. "{{Taxobox taxon|" .. p.regne .. "|" .. resu[i]["rang"] .. "|" .. resu[i]["nom"] .. "|" .. (auteurs[i] or "") .. "}}\n"
			tres = tres .. "taxobox taxon : règne='" .. p.regne .. "', rang='" ..
			  resu[i]["rang"] .. "', nom='" .. resu[i]["nom"] .. "', auteur='" ..
			  (auteur or " ") .. "'"
			if (resu[i]["article"] ~= nil) then
				tres = tres .. ", lien article='" .. resu[i]["article"] .."'"
			end
			tres = tres .. "<br/>"
		else
			-- un taxon "normal"
			tres2 = tres2 .. "{{Taxobox|" .. resu[i]["rang"] .. "|"
			if (resu[i]["article"] ~= nil) then
				tres2 = tres2 .. resu[i]["article"] .. "|" .. resu[i]["nom"] .. "}}\n"
			else
				tres2 = tres2 .. resu[i]["nom"] .. "}}\n"
			end
			tres = tres .. "taxobox : rang='" .. resu[i]["rang"] .. "', nom='" ..
			       resu[i]["nom"] .. "'"
			if (resu[i]["article"] ~= nil) then
				tres = tres .. ", lien article='" .. resu[i]["article"] .."'"
			end
			tres = tres .. "<br/>"
		end
	end
	-- ajout de la catégorie
	if (categorie ~= nil) then
		-- tres2 = "[[Catégorie:" .. categorie .. "]]\n" .. tres2
		tres = tres .. "Catégorie insérée : Catégorie:" .. categorie .. "<br/>"
	end

	-- on retourne le résultat
	return tres2
end

--[[
  Fonction réelle (exportée).
  Juste un wrapper pour celle du dessus

  Paramètres :
    rang= : le rang du taxon considéré
    nom= : le nom du taxon considéré
    nom2= : si espèce, la deuxième partie du nom
    nom3= : si sous-espèce, la troisième partie du nom
    image= : le fichier image (optionnel)
    légende= : la légende associée (optionnel)
    auteur= : l'auteur (optionnel)
    classification= : la classification à suivre
--]]
function p.taxoboft(frame)
	local args = frame.args
	local pargs = frame:getParent().args
	-- on récupère les paramètres
	local rang = pargs["rang"] or args["rang"]
	local nom = pargs["nom"] or args["nom"]
	local nom2 = pargs["nom2"] or args["nom2"]
	local nom3 = pargs["nom3"] or args["nom3"]
	local image = pargs["image"] or args["image"]
	local legende = pargs["légende"] or args["légende"]
	local classification = pargs["classification"] or args["classification"]
	local auteur = pargs["auteur"] or args["auteur"]
	local auteur2 = pargs["auteur2"] or args["auteur2"]
	local auteur3 = pargs["auteur3"] or args["auteur3"]
	local auteur4 = pargs["auteur4"] or args["auteur4"]
	local spmono = pargs["espèce monotypique"] or args["espèce monotypique"]
	if (spmono ~= nil) then
		spmono = true
	else
		spmono = false
	end
	
	-- on ne gère pas les rangs en dessous du genre. Conversion :
	if (rang == "espèce") then
		rang = "genre"
	elseif (rang == "sous-espèce") then
		rang = "genre"
	end
	return frame:preprocess(p.taxoboft_reelle(rang, nom, nom2, nom3, image, legende, classification, auteur, auteur2, auteur3, auteur4, spmono))
end

-- on retourne le module
return p