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

Ce module permet de créer des taxobox, infobox taxinomiques permettant de décrire un taxon existant dans une classification scientifique des espèces.

Utilisation

modifier

Fonctions exportables :

  • taxobox() – fonction unique pour la création d'une taxobox (prend tous ses paramètres dans la frame parente).
  • convertisseur() – non fonctionnel (fonction prenant en unique paramètre une taxobox au format modèles et retournant la syntaxe au format module).

Autres fonctions :

  • est_dans_liste(mot, liste) – retourne la position de mot dans liste si présent ou nil
  • lecture_parametres(ligne, depart, desc) – analyse ligne (une table de textes) à partir de l'élément depart en fonction de la description de format desc (voir plus loin la syntaxe d'un format). Retourne une table où les valeurs indiquées dans le format sont fixées (ou nil). Si une erreur dans l'analyse survient le champs erreurs contient la description de l'erreur (nil sinon) et le contenu de la table retourné est non prédictible.

Modules externes et autres éléments dont ce module a besoin pour fonctionner :

  • Module:Taxobox-outils : fonctions de traitement effectif des éléments d'une taxobox, plus quelques fonctions utilitaires
  • Module:Taxobox-data : tables et fonctions d'accès associées contenant toutes les données relatives à la génération des taxobox
  • Module:Taxobox-briques : fonction fournissant les briques de base pour construire des infobox
-- module principal (de test) pour générer des taxobox.
-- contient la fonction principale (et unique) "taxobox"
-- l'autre fonction (convertisseur) n'est pas fonctionnelle.
-- il reste des choses à faire, nettoyer…

local z = {}

-- les boîtes à outils
local data = require "Module:Taxobox-data"
local briques = require "Module:Taxobox-briques"
local tools = require "Module:Taxobox-outils"
 
 
-- preparation : table "globale" contenant les divers paramètres communs
--   contient aussi les tables et éléments pour les retours d'info des fonctions
z.configuration = {}
-- la "frame", nécessaire pour faire du preprocess si besoin
z.configuration.frame = nil  -- fram
-- paramètres qui sont des données
z.configuration.regne = nil  -- le règne
z.configuration.nv = nil     -- le(s) nom(s) vernaculaire(s) si présent
 
-- informations sur l'article
z.configuration.titre_page = nil  -- le titre réel de l'article
z.configuration.article = nil  -- le namespace de la page (true=article, false=autre)
-- titre forcé : faire comme si c'était le titre de l'article (implique NS=0)
z.configuration.force_titre = nil
 
-- informations sur la taxobox (sa structure)
z.configuration.div_nb = 0  -- compte des ouvertures / fermertures de <div>
z.configuration.table_nb = 0  -- compte des ouvertures / fermetures de <table>
z.configuration.fermeture_taxobox = nil  -- état de la taxobox (redondant avec div_nb ?)
 
-- informations calculées
z.configuration.titre_article = nil  -- titre mis en forme de l'article (pour modification affichage)
z.configuration.titre_taxobox = nil  -- titre de la taxobox (mis en forme) calculé d'après les infos
z.configuration.titre_ns = nil  -- si true → article titré avec le (un) nom scientifique, sinon nom vernaculaire
z.configuration.regne_affiche = nil  -- le nom wikifié correspondant au règne
z.configuration.regne_lien = nil  -- le lien vers lequel pointe ce règne
z.configuration.sous_titre = ""  -- le sous-titre (éventuel) à insérer
 
-- zones de stockage d'information pour les retours
z.configuration.r_categories = {}  -- stockage des catégories retournées par les fonctions
z.configuration.r_err_categories = {} -- stockage des catégories d'erreur retournées par les fonctions
z.configuration.r_erreurs = {}  -- stockage des erreurs retournées par les fonctions
 
-- fonctions d'accès pour remplir les informations (cat, err-cat, err)
z.configuration.f_categories = nil  -- fonction d'ajout d'une catégorie
z.configuration.f_err_categories = nil  -- fonction d'ajout d'une catégorie d'erreur
z.configuration.f_erreurs = nil  -- fonction d'ajout d'une erreur { type, message }
 
-- les éléments de contrôle de la sortie
z.configuration.c_titre = nil  -- la gestion du titre (oui, non, auto)
z.configuration.c_sous_titre = nil  -- la gestion du sous-titre (oui, non, auto)
z.configuration.c_categories = nil -- la gestion des catégories (en ligne, boîte, non, auto)
z.configuration.c_err_categories = nil -- la gestion des catégories d'erreurs (en ligne, boîte, non, auto)
z.configuration.c_erreurs = nil -- la gestion des messages d'erreur (oui, non, auto)
 
-- les éléments réservés aux développeurs
z.configuration.c_raw = nil  -- affichage "brut" de la taxobox
z.configuration.c_debug = nil  -- mode debug. Ne fait rien actuellement
 
 
-- l'ensemble des paramètres non-nommés, sous forme de table de tables (1 par ligne)
local descriptions = {}
local nb_lignes -- nombre de lignes dans la description
 
-- variables d'état de la taxobox : contient les états en cours
local etat_precedent = ""  -- ce qu'on a traité juste avant
local liste_taxons = {}  -- liste des taxons trouvés
local nb_taxons  -- nombre de lignes dans liste_taxons
local cible_taxon = nil  -- si présent, l'utiliser plutôt que nb_taxons pour choix du titre
local nb_debut = 0  -- pour gérer les ouvertures / fermetures
local nb_cites = 0  -- pour gérer la proximité
local nb_uicn = 0  -- pour gérer la proximité
local pdebug = ""  -- pour le mode debug
 
 
-- fonctions de manipulation des infos
function z.ajoute_r_categories(nom)
    if (nom == nil) then
        return
    end
    table.insert(z.configuration.r_categories, nom)
end
function z.ajoute_r_err_categories(nom)
    if (nom == nil) then
        return
    end
    table.insert(z.configuration.r_err_categories, nom)
end
function z.ajoute_r_erreurs(tp, desc)
    if (tp ~= nil and tp ~= "" and desc ~= nil and desc ~= "") then
        local ll = ""
        if (type(z.configuration.ligne_courante) == "string") then
            ll = z.configuration.ligne_courante
        elseif (type(z.configuration.ligne_courante) == "table") then
            ll = table.concat(z.configuration.ligne_courante, "|")
        else
            ll = "- pas d'information -"
        end
        table.insert(z.configuration.r_erreurs, {tp,desc .. "<br/>(" .. ll .. ")" })
    end
end
-- initialisation des fonctions de traitement de catégorie
z.configuration.f_categories = z.ajoute_r_categories
z.configuration.f_err_categories = z.ajoute_r_err_categories
z.configuration.f_erreurs = z.ajoute_r_erreurs
 
 
 
-- cette fonction nettoie les blancs au début et à la fin du texte. Utile pour nettoyer
-- les paramètres reçus qui peuvent contenir des blancs selon la mise en forme à l'appel 
function z.nettoie(nom)
    -- histoire de ne pas générer d'erreur
    if (nil == nom) then
        return nil
    end
    local tmp = string.gsub (nom, "^[%s]*", "")
    return string.gsub (tmp, "[%s]*$", "")
end
 
 
-- indique si un mot est dans une liste. Si oui retourne la position dans la liste
function z.est_dans_liste(mot, liste)
    local i = 1
 
    if (liste == nil) then
        return 0
    end
    while (liste[i] ~= nil) do
        if (liste[i] == mot) then
            return i
        end
        i = i + 1
    end
    return 0
end
 
 
-- lecture d'une ligne de paramètres
-- desc est la description symbolique de ce qu'on attend
-- ligne est une table (de string) contenant la ligne découpée
-- depart est l'offset dans la table où commencer
-- Retourne une table où se trouvent fixées les valeurs trouvées
-- Format de la description :
-- {
--   ["noname"] = { "key1", "key2", ... },
--   ["flags"] = { { { "f1", "f1b", ... }, "key" },   { { "f2", "f2b", ... }, "key2" }, ... },
--   |"values"] = { { { "v1", "v1b", ... }, "key" },   { { "v2", "v2b", ... }, "key2" }, ... }
-- }
-- noname : paramètres sans nom sur la ligne. La fonction associe tout paramètre qui n'est ni un
--   flag ni un value à la liste de mots-clés. Dans la table retournée on aura tab["key1"] = "le premier champs non nommé",
--   tab["key2" = "le 2ème champs non nommé"... Ceux non présents valent nil. Si trop de paramètres non nommés sont présents
--   les derniers sont ignorés et une erreur est retournée (voir la gestion des erreurs plus bas)
-- flags : paramètres ayant un nom et qui ne sont gérés que par présent/absent. La fonction compare chaque paramètre à la liste
--   des mots-clés possibles pour chaque flag (f1, f1b pour le premier, f2, f2b…). Si l'un est présent la fonction fixe
--   tab["key"] = true (nil sinon)
--   si un flag est donné plus d'une fois cela génère une erreur
-- values : identique aux flags, mais la fonction cherche une valeur associée, qui est le paramètre suivant. Cette valeur
--   est passée par tab["key"] = "valeur lue".
--   Si une value n'a pas de valeur (dernier élément de la liste) ou est donnée plusieurs fois cela génère une erreur
-- Erreurs : si tab["erreurs"] est non nil / non vide c'est qu'une erreur s'est produite. tab["erreurs"] contient un message
--   expliquant l'erreur
-----
----- TODO : remplacer z.ajoute_erreur ici pour transmettre l'erreur à celui qui appelle.
-----
function z.lecture_parametres(ligne, depart, desc)
    local i = depart
    local buf = ""
    local res = {}
    local j
    local tmpf
 
    -- on parcours les entrées
    while (ligne[i] ~= nil) do
        v = z.nettoie(ligne[i])
 
        --- on cherche si 'v' correspond à un élément des descriptions
        j = 1
        if (desc["flags"] == nil) then
            tmpf = 0 -- pas de flags
        else
            while (desc["flags"][j] ~= nil) do
                tmpf = z.est_dans_liste(v, desc["flags"][j][1])
                if (tmpf > 0) then
                    break
                end
                j = j + 1
            end
        end
        if (tmpf > 0) then
            -- on a trouvé, c'est un flag
            -- on vérifie qu'il n'est pas déjà donné
            if (res[desc["flags"][j][2]] ~= nil) then
                res["erreurs"] = "Élément ''" .. ligne[i] .. "'' en multiples exemplaires"
            end
            -- quoi qu'il en soit c'est le dernier qui gagne
            res[desc["flags"][j][2]] = true
        else
            -- pas un flag, on regarde les "values"
            j = 1
            if (desc["values"] == nil) then
                tmpf = 0
            else
                while (desc["values"][j] ~= nil) do
                    tmpf = z.est_dans_liste(v, desc["values"][j][1])
                    if (tmpf > 0) then
                        break
                    end
                    j = j + 1
                end
            end
            if (tmpf > 0) then
                -- on a trouvé, c'est un values
                -- on vérifie qu'il y a un élément en plus
                if (ligne[i+1] == nil) then
                    -- c'est une erreur
                    res.erreurs = "Valeur ''" .. v .. "'' sans contenu (case suivante)."
                else
                    -- on vérifie qu'il n'est pas déjà donné
                    if (res[desc["values"][j][2]] ~= nil) then
                        res.erreurs = "Élément ''" .. ligne[j] .. "'' en multiples exemplaires."
                    end
                    -- quoi qu'il en soit c'est le dernier qui gagne
                    res[desc["values"][j][2]] = z.nettoie(ligne[i+1])
                    -- on saute l'élément
                    i = i + 1
                end
            else
                -- c'est un paramètre non nommé
                -- on cherche le premier non nommé qui soit nil
                j = 1
                while (desc["noname"][j] ~= nil) do
                    if (res[desc["noname"][j]] == nil) then
                        break
                    end
                    j = j + 1
                end
                if (desc["noname"][j] == nil) then
                    -- donc il y a trop de paramètres -> erreur
                    res.erreurs = "Trop de paramètres"
                else
                    -- on fixe sa valeur
                    res[desc["noname"][j]] = v
                end
            end
        end
        -- entrée suivante
        i = i + 1
    end
    return res
end
 
 
 
 
 
--- fonctions de traitement des lignes de description
-- wrapper effectuant la lecture des paramètres et traitant les erreurs
function z.traite_avec_erreur(ligne, syntaxe)
 
    local params = z.lecture_parametres(ligne, 2, syntaxe)
    if (params.erreurs ~= nil) then
        return nil, "syntaxe", params.erreurs
    end
    return params, nil, nil
end
 
 
 
-- devrait disparaitre aussi : il faut revoir l'appel explicite dans la fonction principale
 
-- fin de la taxobox. Syntaxe :
-- fin
function z.traite_fin(ligne)
 
    -- si la taxobox n'est pas ouverte
    if (fermeture_taxobox == true) then
        z.ajoute_r_erreurs("syntaxe", "Fermeture de taxobox sans qu'elle soit ouverte.")
        z.ajoute_r_err_categories("Taxobox avec erreur/syntaxe")
        return ""
    end
 
    -- on indique que la taxobox est fermée
    fermeture_taxobox = true
    local tmp = tools.t_vraie_fin(z.configuration)
 
    return tmp
end
 
 
-- prend une ligne en paramètre et appelle la bonne fonction, et gère les erreurs
function z.traite_ligne(ligne)
    local prems
    local res = ""
    local courant
 
    if (ligne == nil) then
        z.ajoute_r_erreurs("interne (traite_ligne)", "La ligne courante vaut ''nil''.")
        z.ajoute_r_err_categories("Taxobox avec erreur/interne")
        return nil
    end
    prems = ligne[1]
    if (prems == nil) then
        z.ajoute_r_erreurs("interne (traite_ligne)", "Pas de premier élément dans la ligne reçue.")
        z.ajoute_r_err_categories("Taxobox avec erreur/interne")
        return nil
    end
 
    -- on récupère la ligne décrivant cet élément
    local desc = tools.d_syntaxe[prems]
    if (desc == nil) then
        z.ajoute_r_erreurs("syntaxe", "Le mot-clé " .. prems .. " est inconnu.")
        z.ajoute_r_err_categories("Taxobox avec erreur/syntaxe")
        return nil
    end
    -- desc contient dans l'ordre le niveau de criticité de la ligne (1/2/3),
    --  la description du format de la ligne, la fonction à appeler
 
    -- on évalue la ligne
    local vals, stl, msg = z.traite_avec_erreur(ligne, desc[2])
    -- erreur de syntaxe ?
    if (vals == nil) then
        -- on retourne une erreur, fatale si demandé
        z.ajoute_r_erreurs(stl, msg)
        z.ajoute_r_err_categories("Taxobox avec erreur/" .. stl)
        if (desc[1] == 2) then
            return nil -- erreur fatale
        else
            return "" -- ligne ignorée
        end
    end
 
    -- on appelle la fonction de traitement
    courant = ligne[1] -- on note l'élément en cours de traitement
    z.configuration.ligne_courante = ligne -- pour les erreurs
    local reterr, tmp = pcall( desc[3], vals, z.configuration )
    if (reterr ~= true) then -- erreur
        if (desc[1] == 2) then
            -- pas de gestion des messages d'erreur+cat : c'est fait dans les fonctions
            return nil -- erreur fatale
        else
            -- pas de gestion des messages d'erreur+cat : c'est fait dans les fonctions
            return "" -- ligne ignorée
        end
    end
    -- si tmp == nil et qu'on est là c'est qu'on ignore la ligne :
    if (tmp == nil) then
        tmp = ""
    end
 
    --- ici on insert les tests de cohérence sur la structure des éléments
 
    -- début doit être le premier. Il ne peut y en avoir qu'un
    if (courant == "début" and nb_debut > 0) then
        z.ajoute_r_erreurs("structure", "Plus de un ''début'' donné.")
        z.ajoute_r_err_categories("Taxobox avec erreur/structure")
        return nil -- c'est fatal
    end
    if (courant ~= "début" and nb_debut == 0) then
        z.ajoute_r_erreurs("structure", "Élément ''" .. courant .. "'' avant un ''début''.")
        z.ajoute_r_err_categories("Taxobox avec erreur/structure")
        return nil -- c'est fatal
    end
    if (courant == "début") then
        nb_debut = nb_debut + 1
    end
 
    -- si on sort d'un "rang" on doit fermer la table
    if ( (etat_precedent == "rang" or etat_precedent == "conflit") and (courant ~= "rang" and courant ~= "conflit") ) then
        res = res .. "</table>\n"
        z.configuration.table_nb = z.configuration.table_nb - 1
    end
 
    -- si on rentre dans un "rang" il faut qu'on soit dans une table.
    -- ici on accepte mais on génère une erreur
    if ( (courant == "rang" or courant == "conflit") and z.configuration.table_nb == 0) then
        z.ajoute_r_erreurs("structure", "Élément ''" .. courant .. "'' en déhors d'une zone de classification.")
        z.ajoute_r_err_categories("Taxobox avec erreur/structure")
        -- on ouvre une table (pas propre) pour traiter
        res = res .. "<table>"
    end    
 
    -- rang/conflit : un rang ne peut apparaître que :
    --  - après un autre rang (on est dans la classification)
    --  - après "début" (sauf si phylo est indiqué)
    --  - après "bandeau phylo" (non implémenté encore)
    -- dans les autres cas : erreur, ignore ou corrige (<table[/]>) selon option
 
    -- légende/répartition légende : une légende ne peut que suivre une légende ou une image
 
    -- taxon : les taxons devraient être donnés à la suite, pas séparés
    if (courant == "taxon" and (etat_precedent ~= "taxon" and etat_precedent ~= "rang" and etat_precedent ~= "conflit") ) then
        z.ajoute_r_erreurs("structure", "Élément ''taxon'' à une position incorrecte.")
        z.ajoute_r_err_categories("Taxobox avec erreur/structure")
    end -- mais ce n'est pas fatal
 
    -- UICN / CITES : devraient être groupés s'il y en a plusieurs ?
    if (courant == "cites") then
        if (nb_cites > 0 and etat_precedent ~= "cites") then
            z.ajoute_r_erreurs("structure", "Élément ''CITES'' multiples mais non consécutifs.")
            z.ajoute_r_err_categories("Taxobox avec erreur/structure")
        end
        nb_cites = nb_cites + 1
    end
    if (courant == "uicn") then
        if (nb_uicn > 0 and etat_precedent ~= "uicn") then
            z.ajoute_r_erreurs("structure", "Élément ''UICN'' multiples mais non consécutifs.")
            z.ajoute_r_err_categories("Taxobox avec erreur/structure")
        end
        nb_uicn = nb_uicn + 1
    end
 
 
    -- synonymes / taxons : devrait être au plus près du dernier taxon
 
    -- ...
 
 
    -- on ajoute l'élément
    res = res .. tmp
 
    -- bascule courant / précédent
    if (courant ~= nil) then  -- on ne touche pas si erreur car on n'a rien fait
        etat_precedent = courant
    end
 
    -- on retourne le résultat
    return res
end
 
 
 
-- cette fonction traite tous les paramètres non-nommés pour créer la table globale associée
-- retourne le nombre total
function z.traite_parametres(pframe)
    local k, v
    local ligne = {}
    local pos = 1
    local pd = 1
 
    for k,v in pframe:argumentPairs() do
        if (type(k) == "number") then
            local temp = string.sub(v, -1)
 
            local txt = z.nettoie(v)
            if (pos == 1) then
                -- le premier est passé en minuscules
                if (txt ~= nil) then
                    ligne[pos] = string.lower(txt)
                else
                    ligne[pos] = txt
                end
            else
                ligne[pos] = txt
            end
            pos = pos + 1
            if (temp == "\n") then
                descriptions[pd] = ligne
                pd = pd + 1
                ligne = {}
                pos = 1
            end
        end
    end
    return pd - 1
end
 
-- retourne vrai si le mot-clé "titre" ou "en titre" est présent
-- dans la table, sinon faux (ainsi qu'en cas d'erreur)
function z.presence_titre(lst)
    if (lst == nil or type(lst) ~= "table") then
        return false
    end
    local i = 1
    while (lst[i] ~= nil) do
        if (type(lst[i]) == "string") then
            if (lst[i] == "titre" or lst[i] == "en titre") then
                return true
            end
        end
        i = i + 1
    end
    return false
end
 
 
-- cette fonction cherche toutes les occurrences de "taxon" pour déterminer s'il y en a et combien
-- génère la liste des taxons qui est une table { rang, nom } et retourne le nombre trouvé
-- ils sont classés dans l'ordre
-- si une ligne taxon est invalide elle est ignorée (ici)
-- si le mot-clé "titre" ou "en titre" est présent le 3ème champs est mis à vrai
function z.cherche_taxons()
    local nb = 0
    local pos = 1
 
    -- tant qu'il y a des entrées
    while (descriptions[pos] ~= nil) do
        -- si c'est "taxon"
        if (descriptions[pos][1] == "taxon") then
            local rang = descriptions[pos][2]
            local nom = descriptions[pos][3]
            --  gestion des erreurs
            if (rang ~= nil and nom ~= nil) then
                -- présence de "titre" ?
                local ttl = false
                if (z.presence_titre(descriptions[pos])) then
                    ttl = true
                    cible_taxon = nb + 1
                end
                -- on l'ajoute
                nb = nb + 1
                liste_taxons[nb] = { rang, nom, ttl }
            end
        end
        pos = pos + 1
    end
    return nb
end
 
 
 
-- utilise liste_taxons pour trouver les titres
function z.cherche_titres(frame)
    local pos = 1
 
    -- en premier lieu on cherche le taxon le plus "bas" pour le titre de la taxobox
    local ligne
    if (cible_taxon ~= nil) then
        ligne = liste_taxons[cible_taxon]
        if (ligne == nil) then
            -- protection
            ligne = liste_taxons[nb_taxons]
            z.ajoute_r_erreurs("interne/cible_taxon", "cible_taxon=" .. cible_taxon .. " alors que nb_taxons=" .. nb_taxons .. ", retour à ''nil''.")
            z.ajoute_r_err_categories("Taxobox avec erreur/interne")
        end
    else
        ligne = liste_taxons[nb_taxons]
    end
    z.configuration.titre_taxobox = tools.formate_ns(z.configuration.regne, ligne[1], ligne[2], false, false)
 
    -- on découpe le titre de l'article pour extraire la partie homonymie
    local tmp = tools.decoupe_homonymie(z.configuration.titre_page)
    -- on cherche dans la liste des taxons si l'un d'entre-eux correspond au nom (sans partie homonynie)
    local trouve = 0
    for i = 1, nb_taxons do
        if (tmp[1] == liste_taxons[i][2]) then
            trouve = i
            -- si celui qui correspond au titre n'est pas le dernier on insert une erreur+cat
            -- sauf si il est explicitement indiqué comme titre
            if (i ~= nb_taxons) then
                if (liste_taxons[i][3] == false) then
                    z.ajoute_r_erreurs("structure/taxons", "Taxobox gigogne mais article non titré avec le taxon le plus bas")
                    z.ajoute_r_err_categories("Taxobox avec erreur/structure")
                end
            end
            break
        end
    end
 
    -- si pas trouvé : probablement titré en nom vernaculaire
    if (trouve == 0) then
        -- on ne touche pas au titre
        z.configuration.titre_article = nil
        -- on met en sous-titre la liste des taxons
        for i = 1, nb_taxons do
            -- on met en forme le nom scientifique
            local ttr = tools.formate_ns(z.configuration.regne, liste_taxons[i][1], liste_taxons[i][2], false, false)
            if (i < nb_taxons) then
                z.configuration.sous_titre = z.configuration.sous_titre .. ttr .. ", "
            else
                z.configuration.sous_titre = z.configuration.sous_titre .. ttr
            end
        end
    else
        -- la mise en forme du titre
        z.configuration.titre_article = tools.formate_ns(z.configuration.regne, liste_taxons[trouve][1], tmp[1], true, false)
        if (tmp[2] ~= nil) then
            z.configuration.titre_article = z.configuration.titre_article .. " " .. tmp[2]
        end
        -- puisque NS on ajoute les NV en sous-titre si présents
        if (z.configuration.nv == nil or z.configuration.nv == "") then
            z.configuration.sous_titre = nil
        else
            z.configuration.sous_titre = z.configuration.nv
        end
    end
 
end
 
function z.essai(frame)
    tools.t_test(nil, z.configuration)
 
    if (z.configuration.r_erreurs ~= nil) then
        return z.configuration.r_erreurs[1][1] .. " : " .. z.configuration.r_erreurs[1][2] .. "<br/>"
    else
        return "Aucune retour.<br/>"
    end
 
 
end
 
-- génère une box lisant les erreurs (selon le mode)
function z.box_erreurs(mode)
    local tx = ""
    local pos = 1
 
    if (mode == "non" or z.configuration.r_erreurs == nil or z.configuration.r_erreurs[1] == nil) then
        return "" -- rien (rien à faire ou mode "pas d'erreurs")
    end
 
    -- si mode auto et article → on ne fait rien non plus
    if (mode == "auto" and z.configuration.article == true) then
        return ""
    end
 
    -- donc là on affiche (soit mode="oui" soit mode="auto" + article)
    tx = tx .. briques.t_debut("error", "Erreurs détectées")
    while (z.configuration.r_erreurs[pos] ~= nil) do
        tx = tx .. "<small>" .. z.configuration.r_erreurs[pos][1] .." : " .. z.configuration.r_erreurs[pos][2] .. "</small>"
        pos = pos + 1
    end
 
    tx = tx .. briques.t_end()
 
    return tx
end
 
 
-- "pousse" la table de catégories selon le bon mode
function z.mef_categories(liste, mode, msg1, msg2)
    local ret = ""
 
    -- vide, ou bien mode = "non"
    if (mode == "non" or liste == nil or liste[1] == nil) then
        return ""
    end
    local t -- 1=inline, 2=box
 
    if (mode == "en ligne") then
        t = 1
    elseif (mode == "boîte") then
        t = 2
    else -- si mode "auto" on regarde dans quel espace on est
        if (z.configuration.article == true) then
            t = 1 -- dans les articles mode inline
        else
            t = 2 -- sinon mode box
        end
    end
 
    -- mode boite : entête
    if (t == 2) then
        ret = ret .. briques.t_debut(msg1, msg2)
    end
 
    local pos = 1
    while (liste[pos] ~= nil) do -- on parcours
        local p1 = nil  -- catégorie
        local p2 = nil  -- clé éventuelle
        if (type(liste[pos]) == "string") then
            p1 = liste[pos]
            p2 = nil
        elseif (type(liste[pos]) == "table") then
            p1 = liste[pos][1] or "Taxobox avec erreur/interne"
            p2 = liste[pos][2]
        else
            p1 = "Taxobox avec erreur/interne"
            p2 = nil
        end
        if (t == 1) then
            if (p2 == nil) then
                ret = ret .. "[[Catégorie:" .. p1 .. "]]"
            else
                ret = ret .. "[[Catégorie:" .. p1 .. "|" .. p2 .. "]]"
            end
        else
            if (p2 == nil) then
                ret = ret .. "[[:Catégorie:" .. p1 .. "|" .. p1 .. "]]<br/>"
            else
                ret = ret .. "[[:Catégorie:" .. p1 .. "|" .. p1 .. " (" .. p2 .. ")]]<br/>"
            end
        end
        pos = pos + 1
    end
 
    -- mode boite : la fin
    if (t == 2) then
        ret = ret .. briques.t_end()
    end
 
    return ret
end
 
 
-- génère une box listant les catégories ou les incluant (selon le mode)
function z.box_categories()
    local tx = ""
    local pos = 1
 
    -- categories "normales"
    tx = tx .. z.mef_categories(z.configuration.r_categories, z.configuration.c_categories, "", "Catégories non incluses")
 
    -- catégories d'erreur
    tx = tx .. z.mef_categories(z.configuration.r_err_categories, z.configuration.c_err_categories, "error", "Catégories d'erreur non incluses")
 
    return tx
end
 
 
-- fonction principale
function z.taxobox(frame)
    local pframe = frame:getParent()
    local args = pframe.args
    local tx = ""
    local tmp
 
    -- on conserve la frame
    z.configuration.frame = frame

    -- on récupère le NAMESPACE tout de suite, pour que les briques erreur puissent catégoriser correctement
    z.configuration.article = frame:preprocess("{{NAMESPACE}}")  -- le namespace de la page
    if (z.configuration.article == "") then
        -- c'est un article
        z.configuration.article = true
    else
        z.configuration.article = false
    end

    -- -- gestion des paramètres nommés (debug, options, éléments obligatoires)
    -- - paramètres éditoriaux
    -- on récupère le règne
    z.configuration.regne = z.nettoie(args["règne"]) or nil
    -- pas de règne, erreur tout de suite
    if (z.configuration.regne == nil) then
        return briques.t_erreur("Le paramètre nommé ''règne'' est obligatoire.", "règne", z.configuration)
    end
    -- on recupère les noms vernaculaires
    z.configuration.nv = z.nettoie(args["nv"] or args["nom vernaculaire"] or args["noms vernaculaires"]) or nil
 
    -- - paramètres modifiant le comportement (note : passer ça en minuscules)
    -- titre : oui, non, auto → contrôle la modification (éventuelle) du titre
    z.configuration.c_titre = z.nettoie(args["titre"]) or nil
    if (z.configuration.c_titre == nil) then
        z.configuration.c_titre = data.defauts.titre -- mode par défaut
    end
    -- validation du paramètre
    if (z.configuration.c_titre == "boite") then
        z.configuration.c_titre = "boîte"
    end
    if (z.configuration.c_titre ~= "oui" and z.configuration.c_titre ~= "non" and z.configuration.c_titre ~= "boîte" and z.configuration.c_titre ~= "auto") then
        return briques.t_erreur("Le paramètre nommé ''titre'' ne peut prendre que les valeurs ''oui'', ''non'' ou ''auto''.", "titre", z.configuration)
    end
    -- sous-titre : oui, non, auto → contrôle la modification (éventuelle) du sous-titre
    z.configuration.c_sous_titre = z.nettoie(args["sous-titre"]) or nil
    if (z.configuration.c_sous_titre == nil) then
        z.configuration.c_sous_titre = data.defauts.sous_titre -- mode par défaut
    end
    -- validation du paramètre
    if (z.configuration.c_sous_titre == "boite") then
        z.configuration.c_sous_titre = "boîte"
    end
    if (z.configuration.c_sous_titre ~= "oui" and z.configuration.c_sous_titre ~= "non"  and z.configuration.c_sous_titre ~= "boîte" and z.configuration.c_sous_titre ~= "auto") then
        return briques.t_erreur("Le paramètre nommé ''sous-titre'' ne peut prendre que les valeurs ''oui'', ''non'' ou ''auto''.", "sous-titre", z.configuration)
    end
    -- erreurs : oui, non, auto → contrôle l'insertion des messages d'erreur
    z.configuration.c_erreurs = z.nettoie(args["erreurs"]) or nil
    if (z.configuration.c_erreurs == nil) then
        z.configuration.c_erreurs = data.defauts.erreurs -- mode par défaut
    end
    -- validation du paramètre
    if (z.configuration.c_erreurs ~= "oui" and z.configuration.c_erreurs ~= "non" and z.configuration.c_erreurs ~= "auto") then
        return briques.t_erreur("Le paramètre nommé ''erreurs'' ne peut prendre que les valeurs ''oui'', ''non'' ou ''auto''.", "erreurs", z.configuration)
    end
    -- catégories : en ligne, boite, non, auto → contrôle l'insertion des catégories "normales"
    z.configuration.c_categories = z.nettoie(args["catégories"] or args["catégorie"] or args["categories"] or args["categorie"]) or nil
    if (z.configuration.c_categories == nil) then
        z.configuration.c_categories = data.defauts.categories -- mode par défaut
    end
    -- validation du paramètre
    if (z.configuration.c_categories == "boite") then
        z.configuration.c_categories = "boîte"
    end
    if (z.configuration.c_categories ~= "en ligne" and z.configuration.c_categories ~= "boîte" and z.configuration.c_categories ~= "non" and z.configuration.c_categories ~= "auto") then
        return briques.t_erreur("Le paramètre nommé ''catégories'' ne peut prendre que les valeurs ''en ligne'', ''boîte'', ''non'' ou ''auto''.", "catégories", z.configuration)
    end
    -- catégories d'erreurs : en ligne, boite, non, auto → contrôle l'insertion des catégories d'erreurs
    z.configuration.c_err_categories = z.nettoie(args["catégories erreurs"] or args["catégorie erreurs"] or args["categories erreurs"] or args["categorie erreurs"]) or nil
    if (z.configuration.c_err_categories == nil) then
        z.configuration.c_err_categories = data.defauts.err_categories -- mode par défaut
    end
    -- validation du paramètre
    if (z.configuration.c_err_categories == "boite") then
        z.configuration.c_err_categories = "boîte"
    end
    if (z.configuration.c_err_categories ~= "en ligne" and z.configuration.c_err_categories ~= "boîte" and z.configuration.c_err_categories ~= "non" and z.configuration.c_err_categories ~= "auto") then
        return briques.t_erreur("Le paramètre nommé ''catégories erreurs'' ne peut prendre que les valeurs ''en ligne'', ''boîte'', ''non'' ou ''auto''.", "catégories erreurs", z.configuration)
    end
 
    -- - paramètres de debug (réservés aux développeurs)
    -- paramètre "raw" : sortie brute, non interprétée, du résultat (debug)
    z.configuration.c_raw = z.nettoie(args["raw"]) or nil
    if (z.configuration.c_raw == nil) then
        z.configuration.c_raw = data.defauts.raw
    end
    if (z.configuration.c_raw == "oui") then
        tx = "<nowiki>"
    end
    -- paramètre "debug" : ne fait presque rien actuellement
    z.configuration.c_debug = z.nettoie(args["debug"]) or nil
    if (z.configuration.c_debug == nil) then
        z.configuration.c_debug = data.defauts.debug
    end
    -- paramètre "force titre" : fait comme si c'était un article dont le titre est indiqué
    z.configuration.force_titre = z.nettoie(args["force titre"]) or nil
 
 
    -- on analyse le règne indiqué, pour validation/préparation
    z.configuration.regne_affiche = data.premier_regne(z.configuration.regne) -- le nom wikif du règne
    if ( nil == z.configuration.regne_affiche or "" == z.configuration.regne_affiche ) then
        -- ce n'est pas un règne valide
        return briques.t_erreur("Le paramètre nommé ''règne'' n'est pas valide (" .. z.configuration.regne .. ").", "règne", z.configuration) .. z.box_erreurs("oui") -- on force l'erreur
    end
    local tmp2 = data.est_rang("règne")
    z.configuration.regne_lien = tmp2[2][1]
 
 
    -- on récupère les informations externes sur l'article en cours
    if (z.configuration.force_titre ~= nil) then
        -- dans ce cas on utilise ce titre-là
        z.configuration.titre_page = z.configuration.force_titre
    else
        z.configuration.titre_page = frame:preprocess("{{PAGENAME}}") -- titre de la page
    end
 
    -- on génère la table qui décrit la taxobox
    nb_lignes = z.traite_parametres(pframe)
    if (nb_lignes == nil or nb_lignes <= 0) then
        -- une taxobox vide !
        return briques.t_erreur("La taxobox est vide", "syntaxe", z.configuration) .. z.box_erreurs("oui") -- on force l'erreur
    end
 
 
    -- on extrait les taxons (nécessaire pour savoir de qui on parle)
    nb_taxons = z.cherche_taxons()
    if (nb_taxons <= 0) then
        return briques.t_erreur("Aucune entrée ''taxon'' fournie", "syntaxe", z.configuration) .. z.box_erreurs("oui") -- on force l'erreur
    end
 
    -- on détermine les titres et autres
    z.cherche_titres(frame)
 
    -- on parcours les lignes de structuration pour générer la sortie
    for i = 1, nb_lignes do
        tmp = z.traite_ligne(descriptions[i])
        if (tmp == nil) then
            -- erreur qui ne peut être ignorée
            return briques.t_erreur("Erreur de ligne ''début'' ou ''phylogénie bandeau", "syntaxe", z.configuration) .. z.box_erreurs("oui") -- on force l'erreur
        end
        tx = tx .. tmp
    end
    -- si la taxobox n'est pas fermée on la ferme
    if (fermeture_taxobox == false) then
        z.traite_fin() -- fermeture implicite, rendant donc "fin" optionnel
    end
 
    -- tout c'est passé correctement, on modifie le titre/sous-titre
 
    -- si présent, et si article, on traite le sous-titre
    if (z.configuration.sous_titre == nil) then
        pdebug = pdebug .. "sous_titre = <nil><br/>"
    else
        pdebug = pdebug .. "sous_titre = " .. z.configuration.sous_titre .. "<br/>"
    end
 
    local boite_titres = ""
    if (z.configuration.sous_titre ~= nil and z.configuration.sous_titre ~= "") then
        -- on détermine si la gestion du sous-titre est active
        local t = false
        local b = false
        if (z.configuration.c_sous_titre == "non") then
            t = false
        elseif (z.configuration.c_sous_titre == "oui") then
            t = true
        else
            if (z.configuration.article == true) then
                t = true
            else
                t = false
                b = true -- boîte
            end
        end
        if (z.configuration.c_sous_titre == "boîte" or b == true) then
            boite_titres = boite_titres .. "Sous-titre :<br/>" .. z.configuration.sous_titre .. "<br/>"
        else
            if (t == true) then
                tx = tx .. briques.t_sous_titre(z.configuration.sous_titre)
            end
        end
    else
        if (z.configuration.c_sous_titre == "boîte" or (z.configuration.c_sous_titre == "auto" and z.configuration.article == false)) then
            boite_titres = boite_titres .. "Sous-titre :<br/>&nbsp;&nbsp;''aucun''<br/>"
        end
    end
 
    if (z.configuration.titre_article == nil) then
        pdebug = pdebug .. "titre_article = <nil><br/>"
    else
        pdebug = pdebug .. "titre_article = " .. z.configuration.titre_article .. "<br/>"
    end
 
    -- si présent, et si article, on traite la modification du titre
    if (z.configuration.titre_article ~= nil and z.configuration.titre_article ~= "") then
        -- est-ce qu'on doit modifier ?
        local t = false
        local b = false
        if (z.configuration.c_titre == "oui") then
            t = true
        elseif (z.configuration.c_titre == "non") then
            t = false
        else -- auto
            if (z.configuration.article == true) then
                t = true
            else
                t = false
                b = true -- mode boîte si auto
            end
        end
        if (z.configuration.c_titre == "boîte" or b == true) then
            boite_titres = boite_titres .. "Titre :<br/>" .. z.configuration.titre_article .. "<br/>"
        else
            if (t == true) then
                local modif = "{{DISPLAYTITLE:" .. z.configuration.titre_article .. "}}"
                tx = tx .. frame:preprocess(modif)
            end
        end
    else
        if (z.configuration.c_titre == "boîte" or (z.configuration.c_titre == "auto" and z.configuration.article == false)) then
            boite_titres = boite_titres .. "Titre :<br/>&nbsp;&nbsp;''aucun''<br/>"
        end
    end
 
    -- on insert les catégories et les erreurs
    tx = tx .. z.box_categories()
    tx = tx .. z.box_erreurs(z.configuration.c_erreurs)
    -- on insert la boîte des titres (à faire en fonction)
    if (boite_titres ~= "") then
        tx = tx .. briques.t_debut("", "Titre/sous-titre")
        tx = tx .. "<small>" .. boite_titres .. "</small>"
        tx = tx .. briques.t_end()
    end
 
 
    -- spécial : le mode "raw" nécessite quelques ajustements
    if (z.configuration.c_raw == "oui") then
        tx = tx .. "</nowiki>"
        return frame:preprocess(tx)
    end
 
    if (z.configuration.c_debug == "oui") then
        return data.message .. tx .. pdebug -- cette partie seulement si demandée
    else
        return data.message .. tx
    end
end
 
 
local sup = ""
 
-- recupère le contenu du modèle suivant complet
function z.extrait_modele(texte, depart)
 
    local cnt = 0
    local pos = depart
    local npos
    local debut = nil
    local fin = nil
    local t
 
    if (depart > string.len(texte)) then
        return nil, nil
    end
 
    debut = string.find(texte, "{{", pos, true)
    if (debut == nil) then
        return nil, nil
    end
    cnt = 1
    while (true) do
        local p1 = string.find(texte, "{{", pos+2, true)
        local p2 = string.find(texte, "}}", pos+2, true)
        if (p2 == nil) then
            return nil, nil
        end
        if (p1 == nil) then
            cnt = cnt - 1
            npos = p2+2
            if (cnt == 0) then
                return debut, p2+1
            end
        elseif (p1 < p2) then
            cnt = cnt + 1
            npos = p1+2
        else
            cnt = cnt - 1
            npos = p2+2
            if (cnt == 0) then
                return debut, p2+1
            end
        end
        pos = npos
    end
end

-- cherche le | suivant, en ignorant ceux dans des modèles ou wikiliens
-- retourne une position (utf8)
function z.pipe_suivant(ligne, posr)
    local pos = posr
    local cnt = 0
    while (true) do
        local c = mw.ustring.sub(ligne, pos, pos)
        if (c == nil or c == "") then
            if (pos <= posr) then
                return nil
            else
                return pos
            end
        end
        -- on comptabilise les entrées/sorties de modèle/lien
        if (c == "[" or c == "{") then
            cnt = cnt + 1
        elseif (c == "]" or c == "}") then
            cnt = cnt - 1
        elseif (c == "|") then
            -- si | on retourne la position que si cnt = 0
            if (cnt == 0) then
                return pos
            end
        end
        pos = pos + 1
    end
    -- on ne vient pas là
    return nil
end

-- découpe selon les | et range dans une table
function z.decoupe_pipe(ligne)
    local tbl = {}
    local pos
    local courant = 1
 
    pos = z.pipe_suivant(ligne, 1)
    if (pos == nil) then
        table.insert(tbl, ligne) -- un seul élément
        return tbl
    end

    while (pos ~= nil) do
        -- on recupere de "courant" à pos
       local tmp = mw.ustring.sub(ligne, courant, pos-1)
       table.insert(tbl, tmp)
       courant = pos + 1

       -- on recupere la partie suivante
       pos = z.pipe_suivant(ligne, courant)
    end
 
    return tbl
end
 
-- cette fonction reçoit (non processé) une taxobox et retourne la même chose
-- avec la syntaxe actuelle
function z.convertisseur(frame)
    local tx = "{{user:Hexasoft/Scribunto/Taxobox\n"
    local txt = [=[
{{Taxobox début | algue | une_image.jpg | légende | classification=AlgaeBASE }}
{{Taxobox | sous-règne | Hacrobia }}
{{Taxobox | sous-règne | Hacrobia (truc) | Hacrobia}}
{{Taxobox | sous-règne | Hacrobia | ancien=oui }}
{{Taxobox taxon | algue | embranchement | Haptophyta | <auteurs>, <date> }}
{{Taxobox taxon | algue | embranchement | Haptophyta | <auteurs>, <date> }}
{{Taxobox taxon | algue | embranchement | Haptophyta | <auteurs>, <date> }}
{{Taxobox taxons |
* classe ''[[Pavlovophyceae]]''
** ordre ''[[Pavlovales]]''
* classe ''[[Prymnesiophyceae]]'' Hibberd, 1976
** ordre ''[[Prymnesiales]]''
** ordre ''[[Phaeocystales]]''
** ordre ''[[coccolithophore|Isochrysidales]]''
** ordre ''[[coccolithophore|Coccolithales]]''
}}
{{taxobox synonymes |
* ''Turlu tutu'' <small>Chapeau Pointu</small> }}
{{Taxobox UICN | LC | 1Ab12-coulé | chez moi }}
{{Taxobox UICN | VU | 1Ab12-coulé | ailleurs }}
{{Taxobox position | {{Phylogénie Eukaryota}} | Cryptophyta }}
{{Taxobox fin}}
]=]
    local ligne
    local tbl = {}
 
    debut, fin = z.extrait_modele(txt, 1)
    if (debut == nil) then
        return "''Syntaxe probablement erronée.''<br/>" .. sup
    end
    while (debut ~= nil) do
        -- on récupère la ligne
        ligne = string.sub(txt, debut+2, fin-2)
        -- on découpe selon les | présents
        tbl = z.decoupe_pipe(ligne)
        if (tbl[1] ~= nil) then
 
            -- on analyse le premier
            local tmp = z.nettoie(string.lower(tbl[1]))
            if (tmp == "taxobox début") then
                -- le règne
                regne = z.nettoie(tbl[2])
                tx = tx .. "|règne=" .. regne .. "\n"
                -- les autres parametres
                tx = tx .. "|début "
                local i = 3
                while (tbl[i] ~= nil) do
                    local rr = string.find(tbl[i], "=")
                    if (rr == nil) then
                        tx = tx .. "|" .. z.nettoie(tbl[i]) .. " "
                    else
                        -- on remplace le = par un |
                        tx = tx .. "|" .. z.nettoie(string.gsub(tbl[i], "=", "| ")) .. " "
                    end
                    i = i + 1
                end
                tx = tx .. "\n"
            elseif (tmp == "taxobox phylogénie bandeau") then
                tx = tx .. "|phylogénie bandeau "
                local i = 2
                while (tbl[i] ~= nil) do
                    local rr = string.find(tbl[i], "=")
                    if (rr == nil) then
                        tx = tx .. "|" .. z.nettoie(tbl[i]) .. " "
                    else
                        -- on remplace le = par un |
                        tx = tx .. "|" .. z.nettoie(string.gsub(tbl[i], "=", "| ")) .. " "
                    end
                    i = i + 1
                end
                tx = tx .. "\n"
            elseif (tmp == "taxobox") then
                tx = tx .. "|rang "
                local i = 2
                while (tbl[i] ~= nil) do
                    local rr = string.find(tbl[i], "=")
                    if (rr == nil) then
                        tx = tx .. "|" .. z.nettoie(tbl[i]) .. " "
                    else
                        local p1 = mw.ustring.match(tbl[i], "^%s*(%a*)%s*=")
                        if (p1 == "ancien") then
                            tx = tx .. "|ancien "
                        else
                            -- on remplace le = par un |
                            tx = tx .. "|" .. z.nettoie(string.gsub(tbl[i], "=", "| ")) .. " "
                        end
                    end
                    i = i + 1
                end
                tx = tx .. "\n"
            elseif (tmp == "taxobox fin") then
                tx = tx .. "|fin\n"
            elseif (tmp == "taxobox taxon") then
                tx = tx .. "|taxon "
                local i = 3
                while (tbl[i] ~= nil) do
                    local rr = string.find(tbl[i], "=")
                    if (rr == nil) then
                        tx = tx .. "|" .. z.nettoie(tbl[i]) .. " "
                    else
                        -- obsolète ou nouveau ?
                        local p1 = mw.ustring.match(tbl[i], "%s*(%a*)%s*=")
                        if (p1 == "obsolète") then
                            tx = tx .. "| obsolète "
                        else
                            -- on remplace le = par un |
                            tx = tx .. "|" .. z.nettoie(string.gsub(tbl[i], "=", "| ")) .. " "
                        end
                    end
                    i = i + 1
                end
                tx = tx .. "\n"
            elseif (tmp == "taxobox image") then
                tx = tx .. "|image "
                local i = 2
                while (tbl[i] ~= nil) do
                    local rr = string.find(tbl[i], "=")
                    if (rr == nil) then
                        tx = tx .. "|" .. z.nettoie(tbl[i]) .. " "
                    else
                        -- obsolète ou nouveau ?
                        local p1 = mw.ustring.match(tbl[i], "%s*(%a*)%s*=")
                        if (p1 == "séparateur") then
                            tx = tx .. "| séparateur "
                        else
                            -- on remplace le = par un |
                            tx = tx .. "|" .. z.nettoie(string.gsub(tbl[i], "=", "| ")) .. " "
                        end
                    end
                    i = i + 1
                end
                tx = tx .. "\n"
            elseif (tmp == "taxobox conflit") then
                tx = tx .. "|conflit "
                local i = 2
                while (tbl[i] ~= nil) do
                    local rr = string.find(tbl[i], "=")
                    if (rr == nil) then
                        tx = tx .. "|" .. z.nettoie(tbl[i]) .. " "
                    else
                        -- on remplace le = par un |
                        tx = tx .. "|" .. z.nettoie(string.gsub(tbl[i], "=", "| ")) .. " "
                    end
                    i = i + 1
                end
                tx = tx .. "\n"
            elseif (tmp == "taxobox répartition début") then
                tx = tx -- rien à faire car inutile
            elseif (tmp == "taxobox répartition fin") then
                tx = tx -- rien à faire car inutile
            elseif (tmp == "taxobox répartition légende") then
                tx = tx .. "|répartition légende "
                local i = 2
                while (tbl[i] ~= nil) do
                    local rr = string.find(tbl[i], "=")
                    if (rr == nil) then
                        tx = tx .. "|" .. z.nettoie(tbl[i]) .. " "
                    else
                        -- on remplace le = par un |
                        tx = tx .. "|" .. z.nettoie(string.gsub(tbl[i], "=", "| ")) .. " "
                    end
                    i = i + 1
                end
                tx = tx .. "\n"
            elseif (tmp == "taxobox uicn") then
                tx = tx .. "|uicn "
                local i = 2
                while (tbl[i] ~= nil) do
                    tx = tx .. "|" .. z.nettoie(tbl[i]) .. " "
                    i = i + 1
                end
                tx = tx .. "\n"
            elseif (tmp == "taxobox légende") then
                tx = tx .. "|légende "
                local i = 2
                while (tbl[i] ~= nil) do
                    tx = tx .. "|" .. z.nettoie(tbl[i]) .. " "
                    i = i + 1
                end
                tx = tx .. "\n"
            elseif (tmp == "taxobox parents") then
                tx = tx .. "|parents "
                local i = 2
                while (tbl[i] ~= nil) do
                    tx = tx .. "|" .. z.nettoie(tbl[i]) .. " "
                    i = i + 1
                end
                tx = tx .. "\n"
            elseif (tmp == "taxobox cites") then
                tx = tx .. "|cites "
                local i = 2
                while (tbl[i] ~= nil) do
                    tx = tx .. "|" .. z.nettoie(tbl[i]) .. " "
                    i = i + 1
                end
                tx = tx .. "\n"
            elseif (tmp == "taxobox taxons") then
                tx = tx .. "|taxons "
                if (tbl[2] ~= nil) then
                    tx = tx .. "|" .. tbl[2] .. " "
                end
                if (tbl[3] ~= nil) then
                    tx = tx .. "|" .. tbl[3] .. " "
                end
                tx = tx .. "\n"
            elseif (tmp == "taxobox synonymes") then
                tx = tx .. "|synonymes "
                if (tbl[2] ~= nil) then
                    tx = tx .. "|" .. tbl[2] .. " "
                end
                tx = tx .. "\n"
            elseif (tmp == "taxobox position") then
                tx = tx .. "|position "
                if (tbl[2] ~= nil) then
                    tx = tx .. "|" .. tbl[2] .. " "
                end
                if (tbl[3] ~= nil) then
                    tx = tx .. "|" .. tbl[3] .. " "
                end
                tx = tx .. "\n"
            else
                tx = tx .. "|Élément inconnu (" .. tmp .. ")\n"
            end

        end
        -- le suivant
        cur = fin + 1
        debut, fin = z.extrait_modele(txt, cur)
    end
 
    tx = tx .. "}}"
    
    tx = "<pre>" .. txt .. "</pre>\n\n" .. "<pre>" .. tx .. "</pre>\n\n" .. tx .. "\n"
-- reste à faire : phylo arbre, phylo inexistant, répartition, répartition image
    return frame:preprocess(tx)
end

-- cette fonction génère une erreur d'exécution
function z.fonction_en_erreur(frame)
    local t = string.find(nil, nil)
end

function z.mw_title(frame)
    res = ""
    -- afficher le contenu de mw.title
    for k, v in pairs( mw.title ) do
        res = res .. "mw.title: " .. k .. " → " .. type(v) .. "<br/>\n"
    end

    return res
end


return z