Module:Taxobox-fr

From Wikipedia

Documentation for this module may be created at Module:Taxobox-fr/doc

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 .. "[[Category:" .. p1 .. "]]"
            else
                ret = ret .. "[[Category:" .. p1 .. "|" .. p2 .. "]]"
            end
        else
            if (p2 == nil) then
                ret = ret .. "[[:Category:" .. p1 .. "|" .. p1 .. "]]<br/>"
            else
                ret = ret .. "[[:Category:" .. 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

    -- -- 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")
    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")
    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")
    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")
    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")
    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")
    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.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
    
    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

    -- 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.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.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.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

-- découpe selon les | et range dans une table
function z.decoupe_pipe(ligne)
    local tbl = {}
    local pos
    local courant = 1
    
    pos = string.find(ligne, "|", courant, true)
    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 = string.sub(ligne, courant, pos-1)
       table.insert(tbl, tmp)
       courant = pos + 1
       
       -- on recupere la partie suivante
       pos = string.find(ligne, "|", courant, true)
    end
    -- on récupère le dernie morceau
    local tmp = string.sub(ligne, courant, -1)
    table.insert(tbl, tmp)
    
    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 = "<pre>{{Taxobox-fr\n"
    local txt = [=[
{{Taxobox début | algue | image.png | légende | classification=AlgaeBASE }}
{{Taxobox | sous-règne | Hacrobia }}
{{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 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") then
                tx = tx .. "|rang|...\n"
            elseif (tmp == "taxobox fin") then
                tx = tx .. "|fin\n"
            elseif (tmp == "taxobox taxon") then
                tx = tx .. "|taxon|...\n"
            elseif (tmp == "taxobox taxons") then
                tx = tx .. "|taxons|...\n"
            elseif (tmp == "taxobox position") then
                tx = tx .. "|position|...\n"
            else
                return "Élément inconnu : " .. tmp .. "."
            end

        end
        -- le suivant
        cur = fin + 1
        debut, fin = z.extrait_modele(txt, cur)
    end

    tx = tx .. "}}</pre>"
    return frame:preprocess(tx)
end

-- fonction pour tester le comportement sur réception de chaînes incluant des tags
function z.tags(frame)
    local res = "Résultats :<br/>"
    local texte = mw.clone(frame.args[1])
    
    if (texte == nil or texte == "") then
        return res .. "Pas de données d'entrée.<br/>"
    end
    
    -- on insert la chaîne brute
    res = res .. "Version brute : >>" .. texte .. "<<<br/>"
    
    -- on regarde les longueurs
    res = res .. "Longueurs base + utf8 : " .. string.len(texte) .. " " .. mw.ustring.len(texte) .. "<br/>"

    local txt2 = string.gsub(texte, "tag", "XXX")
    local utxt2 = mw.ustring.gsub(texte, "tag", "XXX")
    
    res = res .. "Remplacé : >>" .. txt2 .. "<<  >>" .. utxt2 .. "<<<br/>"

    local pos = string.find(texte, "\127")
    res = res .. "ESC : " .. (pos or "<not found>") .. "<br/>"
    
    local buf = string.gsub(texte, "\127", "_")
    res = res .. "Flat : >>" .. buf .. "<< (" .. string.len(buf) .. ")<br/>"
    
    -- on tente de tout casser
    local tmp = string.gsub(texte, "\127", "_")
    local part = string.match(tmp, "(....)-nowiki")
    local v = tonumber(part, 16)
    local ret = string.format("%04x", v+8)
    local rrr = string.format("%6.4f", 3.14)
    -- je bidouille le contenu
    local tmp2 = string.gsub(tmp, part, ret)
    -- on reforme le tag
    tmp2 = string.gsub(tmp2, "_", "\127")
    res = res .. "Kill : >>" .. tmp .. "<< (" .. string.len(tmp) .. ") --- " .. part .. "," .. ret .. "<br/>"
    res = res .. "Attention : >>" .. tmp2 .. "<< <br/>"

    return res
end


return z