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

--[[
  Module reprenant les fonctionnalités du modèle Démographie.
--]]

local p = {} -- le module

-- le module chartes (centralisation des styles)
local data = require "Module:Chârtes"

-- liste des paramètres reconnus (valeur = nom de la variable)
p.parametros = {
  ["titro"] = "titro",
  ["charta"] = "charta",
  ["colones"] = "colones",
  ["notes"] = "notes",
  ["sorsa"] = "sorsa",
  ["sorses"] = "sorses",
  ["flottant"] = "flottant",
  ["largeur-tableau"] = "largeur_tableau",
  ["sansdoublescomptes"] = "sansdoublescomptes",
  ["enquêteannuelle"] = "enqueteannuelle",
  ["marge-interlignes"] = "marge_interlignes",
  ["taille-police"] = "taille_police",
  ["hauteur-lignes"] = "hauteur_lignes",
  ["hyperliens-années"] = "hyperliens_annees",
  ["années-fond"] = "annees_fond",
  ["population-fond"] = "population_fond",
  ["notes-fond"] = "notes_fond",
  ["style-notes"] = "style_notes",
  -- pour permettre les paramètres "depuis Lua"
  ["largeur_tableau"] = "largeur_tableau",
  ["marge_interlignes"] = "marge_interlignes",
  ["taille_police"] = "taille_police",
  ["hauteur_lignes"] = "hauteur_lignes",
  ["hyperliens_annees"] = "hyperliens_annees",
  ["hyperliens_années"] = "hyperliens_annees",
  ["années_fond"] = "annees_fond",
  ["annees_fond"] = "annees_fond",
  ["population_fond"] = "population_fond",
  ["notes_fond"] = "notes_fond",
  ["style_notes"] = "style_notes",
}


-- le nom de la catégorie d'erreur (pour simplifier les changements)
p.categorie_erreur = "Page avec une erreur d'utilisation du modèle Démographie"

-- le titre par défaut (pour simplifier les changements) → plus utilisé : il n'y a plus de titre par défaut
p.titre_par_defaut = "Évolution démographique"


--[[
  Fonction exportée reprenant le fonctionnement de {{m|Charte de couleur}}

  Fonction devenue inutile (voir Module:Chartes), maintenue pour compatibilité éventuelle
  question : est-il possible de tester un appel à une fonction précise ?
--]]
function p.charte_de_couleur(frame)
    local pframe = frame:getParent()

    -- les deux paramètres
    local nom = mw.ustring.lower(mw.text.trim(pframe.args[1] or ""))
    local code = mw.ustring.lower(mw.text.trim(pframe.args[2] or ""))

    return data.charte_m("geographie", "secondaire", code, "non")
end


--[[
  Insert une catégorie d'erreur
--]]
p.liste_erreurs = {}
p.liste_cats = {}
function p.erreur(message, cle)
    table.insert(p.liste_erreurs, message)
    table.insert(p.liste_cats, cle)
end


--[[
  Fonction de récupération d'un paramètre nommé.
--]]
function p.lit_parametre(nom, pasvide)
    if (type(nom) ~= "string") then
        return nil -- pas un paramètre nommé
    end
    local temp = p.frame.args[nom] or p.pframe.args[nom] -- du modèle, puis de l'article
    if (temp ~= nil) then
        if (pasvide) then
            temp = mw.text.trim(temp)
            if (temp == "") then
                return nil
            else
                return temp
            end
        else
            return mw.text.trim(temp)
        end
    else
        return nil
    end
end

local function get_snack_value(t, v, ...)
	if v and type(t) == 'table' then
		return get_snack_value(t[v], ...)
	elseif v then
		return nil
	else
		return t
	end
end

--[[
  Fonction de récupération des données de Wikidata.
--]]
function p.valeur_wikidata(pm)
	local id = mw.wikibase.getEntityIdForCurrentPage()
	if mw.wikibase.isValidEntityId(p.pframe.args.wikidata) then
		id = p.pframe.args.wikidata
	elseif not id then
		return
	end
	local popProp = mw.wikibase.getAllStatements( id, 'P1082' )
	if not popProp or #popProp == 0 then
		return
	end
	for _, statement in ipairs( popProp ) do
		local pop = get_snack_value(statement, 'mainsnak', 'datavalue', 'value', 'amount')
		local date_table = get_snack_value(statement, 'qualifiers', 'P585', 1, 'datavalue', 'value' )
		if pop and date_table and date_table.precision > 8 and statement.rank ~= 'deprecated' then
			local an = tonumber(date_table.time:match('^%+(%d%d%d%d)'))
			if an then
				pm[an] = pop:match('%d+')
			end
		end
	end
end

--[[
  Fonction de tri de la table
--]]
function p.mysort(el1, el2)
    if (el1 == nil) then
        return true
    end
    if (el2 == nil) then
        return false
    end
    if (el1[1] < el2[1]) then
        return true
    else
        return false
    end
end

--[[
  Supprime le premier retour à la ligne (éventuel) de forme <br/>
--]]
function p.sans_nl(texte)
    if (texte == nil or texte == "" or type(texte) ~= "string") then
        return texte
    end
    return mw.ustring.gsub(texte, "[<][bB][rR][ ]*[/][>]", "", 1)
end

--[[
  Fonction principale
  reçoit une table des paramètres (pm) issus de l'appel
--]]
function p.demographie_m(pm)
    -- titre par défaut
    --[[
    if (pm.titre == "off" or pm.titre == "non") then
        pm.titre = nil
    elseif (pm.titre == nil) then
        pm.titre = p.titre_par_defaut
    end
    --]]
    -- modification : maintenant le titre est réellement optionnel
    if (pm.titre == "") then
        pm.titre = nil
    end

    -- valeur marge interlignes
    if (pm.marge_interlignes == nil) then
        pm.marge_interlignes = "5px"
    else
        -- les valeurs trop petites
        if (pm.marge_interlignes == "0" or pm.marge_interlignes == "0em" or pm.marge_interlignes == "0.1em" or pm.marge_interlignes == "0px" or
             pm.marge_interlignes == "1px" or pm.marge_interlignes == "2px" or pm.marge_interlignes == "3px" or pm.marge_interlignes == "4px") then
            pm.marge_interlignes = "5px"
        end
    end

    -- valeur effective du flottant
    local vflottant = 'margin: 0 auto' -- valeur par défaut
    if (mw.ustring.lower(pm.flottant or "") == "gauche") then
        vflottant = 'float:left; margin: 0 1em 1em 0'
    elseif (mw.ustring.lower(pm.flottant or "") == "droite") then
        vflottant = 'float:right; margin: 0 0 1em 1em'
    end

    if (pm.hauteur_lignes == nil) then
        pm.hauteur_lignes = ""
    else
        pm.hauteur_lignes = "line-height:" .. pm.hauteur_lignes .. ";"
    end
    pm.taille_police = (pm.taille_police or "100%") -- valeur par défaut taille police
    if (pm.notes_fond ~= nil) then
        pm.notes_fond = "background: " .. pm.notes_fond .. ";"
    else
        pm.notes_fond = ""
    end
    local parenthese = false
    if (pm.style_notes == "gauche") then
        pm.style_notes = 'border: 1px solid #aaa; text-align:left;'
    else
        pm.style_notes = 'border: 0; border-width: 0;'
        parenthese = true
    end

    -- valeur par défaut lien
    if (pm.hyperliens_annees == nil) then
        pm.hyperliens_annees = false
    else
       -- validation valeur
        if (pm.hyperliens_annees == "on" or pm.hyperliens_annees == "oui") then
            pm.hyperliens_annees = true
        else
            pm.hyperliens_annees = false -- toute valeur autre que "on" = "off"
        end
    end
    -- valeurs par défaut des colonnes
    local colonnes_par_defaut
    if (pm.colonnes == nil) then
        colonnes_par_defaut = true
        pm.colonnes = 9
    else
        pm.colonnes = tonumber(pm.colonnes) -- pour que ce soit un nombre
    end
    -- on valide les colonnes
    if (type(pm.colonnes) ~= "number" or pm.colonnes < 1) then
        -- colonne erronée : erreur
        p.erreur("La valeur du paramètre ''colonnes'' (" .. (pm.colonnes or "<pas un nombre>") .. ") n'est pas valide", "nombre de colonnes")
        pm.colonnes = 9
    end
    -- largeur par défaut : 5.4em * colonnes
    local largeur_tableau_par_defaut
    if (pm.largeur_tableau == nil) then
        largeur_tableau_par_defaut = true
        pm.largeur_tableau = pm.colonnes*5.4 .. "em"
    end
    if (pm.charte == nil) then
        pm.charte = "défaut"
    else
        -- on valide la charte
        pm.charte = mw.ustring.lower(pm.charte)
    end
    -- on récupère les couleurs de la charte sauf si indiquées
    local coul_annees = (pm.annees_fond or data.charte_m("geographie", "secondaire", pm.charte , "oui"))
    local coul_valeurs = (pm.population_fond or nil) -- valeur par défaut = rien

    if (coul_valeurs == nil) then
        coul_valeurs = ""
    else
        coul_valeurs = 'style="background:' .. coul_valeurs .. ';"'
    end

    -- extraction des éléments de la table, rangés dans une "vraie" table pour les trier
    local tbl = {}
    for annee, valeur in pairs(pm) do
        -- il y a aussi les paramètres nommés dans cette table, qu'on laisse
        if (type(annee) == "number") then
            table.insert(tbl, {annee,valeur})
        else
            -- on profite de cette boucle pour vérifier les paramètres qui n'existent pas
            local tst = p.parametres[annee]
            if (tst == nil) then
                -- cas particulier : les paramètres sous la forme "XXXX notes" et "XXXX unité" sont acceptés
                local tst = mw.ustring.match(annee, "^[0-9]+ notes$") or mw.ustring.match(annee, "^[0-9]+ unité$") or mw.ustring.match(annee, "^[0-9]+ affichage$") or mw.ustring.match(annee, "^_")
                -- autre : on ignore également les paramètres vides (pb de transmission d'autres modèles)
                local temp = mw.text.trim(annee)
                if (tst == nil and temp ~= "") then -- pas un paramètre connu ni XXXX notes → erreur
                    p.erreur("Le paramètre ''>>" .. annee .. "<<'' (" .. mw.ustring.len(annee) .. "/" .. mw.ustring.len(temp) .. ") est inconnu", "paramètre inconnu")
                    -- on ignore simplement ce champs
                end
            end
        end
    end
    -- tri de la table
    table.sort(tbl, p.mysort)

    -- cette fois on parcours la structure des infos
    local ret = ""
    local odebug = "" -- sortie de debug

    -- on parcours les données (années) pour générer la table structurée
    local col = 1
    local ligne = 1
    local struct = {}
    table.insert(struct, {}) -- on crée la première ligne
    local total = 0 -- compte du nombre total
    local pos = 1
    while (tbl[pos] ~= nil) do
        annee = tbl[pos][1]
        valeur = tbl[pos][2]
        pos = pos + 1
        -- il y a aussi les paramètres nommés dans cette table, qu'on laisse
        if (type(annee) == "number" and not (annee == 1 and (valeur == nil or valeur == ""))) then -- protection : un paramètre non nommé vaudra "1"
            -- nettoyage de la valeur
            local v = mw.text.trim(valeur or "")
            -- on insert dans la ligne en cours
            table.insert(struct[ligne], { annee, v })

            -- suivant
            total = total + 1
            col = col + 1
            -- fin de la ligne ?
            if (col > pm.colonnes) then
                col = 1
                ligne = ligne + 1
                table.insert(struct, {})
            end
        end
    end
    -- aucune entrée ? erreur.
    if (total == 0) then
        p.erreur("Aucune année fournie au modèle", "absence d'années")
        -- on insert une donnée fictive
        tbl[1] = { 1970, 0 }
    end
    -- cas particulier : si les données arrivent pile à la dernière colonne on a alors
    -- une nouvelle ligne vide, pour rien. On l'enlève si c'est le cas
    if (struct[ligne] ~= nil and struct[ligne][1] == nil) then
        struct[ligne] = nil
        ligne = ligne - 1
    end
    -- on traite la largeur
    if (colonnes_par_defaut == true and ligne == 1 and total < pm.colonnes) then
        pm.colonnes = total  -- restriction du nombre de colonnes au nombre réel d'éléments
        -- il faut aussi recalculer la largeur totale : on fait le rapport entre 9 (ancien) et le nouveau nombre de colonnes
        if (largeur_tableau_par_defaut == true) then
            -- uniquement si l'utilisateur n'a pas fixé la taille
            pm.largeur_tableau = pm.colonnes*5.4 .. "em"
        end
    end

    -- on récupère le "langage" courant pour utiliser formatnum
    local lang = mw.language.getContentLanguage()

    -- on récupère le namespace
    -- local ttl = mw.title.getCurrentTitle().namespace
    -- on force le namespace à "non encyclopédique" pour avoir systématiquement les messages d'erreur
    local ttl = 1  -- 1 ou autre chose, mais pas pas 0. Il faudrait si on conserve ça virer le code qui teste cette valeur (plus propre)


    -- création du div principal
    ret = ret .. '<div style="overflow:hidden; width: ' .. pm.largeur_tableau .. '; ' .. vflottant .. '; padding:0 1px; text-align:center; font-family: Liberation Sans, Arial, sans-serif;">'

    ligne = 1
    -- boucle sur les lignes
    while (struct[ligne] ~= nil) do
        -- une ligne à faire, on crée le tableau
        if (ligne == 1) then
            ret = ret .. '<table class="wikitable" style="table-layout:fixed;width:100%;text-align:center;margin-top: 1px; ' .. pm.hauteur_lignes .. 'margin-bottom: 0;font-size:' .. pm.taille_police .. ';">\n'
        else
            ret = ret .. '<table class="wikitable" style="table-layout:fixed;width:100%;text-align:center;margin-top:' .. pm.marge_interlignes .. '; ' .. pm.hauteur_lignes .. 'margin-bottom: 0;font-size:' .. pm.taille_police .. ';">\n'
        end
        -- si titre présent on l'ajoute : visible si 1ère ligne, caché sinon
        if (pm.titre ~= nil and pm.titre ~= "") then
            if (ligne == 1) then
                ret = ret .. '<caption style="margin-bottom:' .. pm.marge_interlignes .. ';">' .. pm.titre .. '</caption>'
            else
                ret = ret .. '<caption class="hidden">' .. pm.titre .. ", suite (" .. ligne-1 .. ')</caption>'
            end
        else
            -- titre par défaut, caché
            ret = ret .. '<caption class="hidden">' .. p.titre_par_defaut .. ' (ligne ' .. ligne .. ')</caption>'
        end
        -- parcours des colonnes pour insérer les années
        col = 1
        ret = ret .. "<tr>\n"
        while (struct[ligne][col] ~= nil) do
            -- présence de AAAA affichage ?
            local temp = pm[struct[ligne][col][1] .. " affichage"]
            if (temp ~= nil) then
                -- on affiche l'élément indiqué à la place
                ret = ret .. '<th scope="col" style="background-color: ' .. coul_annees .. ';">' .. temp .. '</th>\n'
            else
                if (pm.hyperliens_annees) then
                    ret = ret .. '<th scope="col" style="background-color: ' .. coul_annees .. ';">[[' .. struct[ligne][col][1] .. ']]</th>\n'
                else
                    ret = ret .. '<th scope="col" style="background-color: ' .. coul_annees .. ';">' .. struct[ligne][col][1] .. '</th>\n'
                end
            end
            col = col + 1
        end
        -- si on n'a pas terminé les colonnes on termine avec du vide
        if (col <= pm.colonnes) then
            while (col <= pm.colonnes) do
                ret = ret .. '<th scope="col" style="background-color: ' .. coul_annees .. ';">-</th>\n'
                col = col + 1
            end
        end
        ret = ret .. "</tr>\n"
        -- parcours des colonnes pour insérer les valeurs
        col = 1
        ret = ret .. "<tr>\n"
        while (struct[ligne][col] ~= nil) do
            if (struct[ligne][col][2] == "" or struct[ligne][col][2] == nil) then
                ret = ret .. '<td ' .. coul_valeurs .. '>-</td>'
            else
                -- on récupère la partie numérique au début
                local pdeb = mw.ustring.match(struct[ligne][col][2], "^[0-9]*")
                local pfin = mw.ustring.match(struct[ligne][col][2], "^[0-9]*(.*)$")
                local tmp = ""
                -- si le début est présent il passe par formatnum
                if (pdeb ~= nil and pdeb ~= "") then
                    tmp = tmp .. lang:formatNum(tonumber(pdeb))
                end
                -- on ajoute la suite (éventuelle)
                if (pfin ~= nil and pfin ~= "") then
                    tmp = tmp .. pfin
                end
                -- si un paramètre "XXXX unité" existe on l'insert
                local unite = pm[struct[ligne][col][1] .. " unité"]
                if (unite ~= nil) then
                    tmp = tmp .. " " .. unite
                end
                -- si un paramètre "XXXX notes" existe on l'insert en tant que note
                local note = pm[struct[ligne][col][1] .. " notes"]
                if (note ~= nil) then
                    -- test : on regarde si la note est déjà une ref (pour insérer une espace ou pas avant)
                    local estref = mw.text.unstrip(note)
                    -- si 'estref' == '' la note ne contient qu'un strip marker (ref, pre, gallery). Probablement une ref.
                    if (estref ~= '') then
                        tmp = tmp .. " "
                    end
                    tmp = tmp .. note
                end
                ret = ret .. '<td ' .. coul_valeurs .. '>' .. tmp .. '</td>'
            end

            col = col + 1
        end
        -- si on n'a pas terminé les colonnes on termine avec du vide
        if (col <= pm.colonnes) then
            while (col <= pm.colonnes) do
                ret = ret .. '<td ' .. coul_valeurs .. '>-</td>'
                col = col + 1
            end
        end
        ret = ret .. "</tr>\n"
        -- fermeture table
        ret = ret .. "</table>\n"
        ligne = ligne + 1
    end

    -- si pas encyclo + présence d'erreur on l'ajoute aux notes
    local erreurs = nil
    if (ttl ~= 0 and p.liste_erreurs[1] ~= nil) then
        erreurs = "<span class=\"error\" style=\"font-size: 0.9em;\">Liste des erreurs :<br/>"
        local i = 1
        while (p.liste_erreurs[i] ~= nil) do
            erreurs = erreurs .. "• " .. p.liste_erreurs[i]
            if (p.liste_erreurs[i+1] ~= nil) then
                erreurs = erreurs .. "<br/>"
            end
            i = i + 1
        end
        erreurs = erreurs .. "</span>"
    end


    -- gestion des notes et sources
    if (pm.notes ~= nil or pm.source ~= nil or pm.sources ~= nil or pm.sansdoublescomptes ~= nil or pm.enqueteannuelle ~= nil or erreurs ~= nil) then
        local pred = false
        ret = ret .. '<div style="padding: 0.3em; margin: 6px 0; line-height: 150%; font-size: 0.9em; ' .. pm.notes_fond .. ' ' .. pm.style_notes .. '">'
        -- le double-compte si présent
        if (pm.sansdoublescomptes ~= nil) then
            -- si présent on retire le saut de ligne
            pm.sansdoublescomptes = p.sans_nl(pm.sansdoublescomptes)
           ret = ret .. "Nombre retenu à partir de [[" .. pm.sansdoublescomptes .. "]] : [[Chiffres de population de la France|population sans doubles comptes]]."
           pred = true
        end
        if (pm.enqueteannuelle ~= nil) then
            -- si présent on retire le saut de ligne
            pm.enqueteannuelle = p.sans_nl(pm.enqueteannuelle)
            if (pred) then
                ret = ret .. "<br/>"
            end
            ret = ret .. "[[" .. pm.enqueteannuelle .. "]] : Population provisoire (enquête annuelle)."
            pred = true
        end
        -- on ajoute les notes si présentes
        if (pm.notes ~= nil) then
            if (pred) then
                ret = ret .. "<br/>"
            end
            -- si présent on retire le saut de ligne
            pm.notes = p.sans_nl(pm.notes)
            ret = ret .. pm.notes
            pred = true
        end
        -- sources si présentes
        if (pm.source ~= nil or pm.sources ~= nil) then
            if (pred) then
                ret = ret .. "<br/>"
            end
            pred = true
            -- si on a source et sources on met tout dans sources
            if (pm.source ~= nil and pm.sources ~= nil) then
                pm.sources = pm.source .. " " .. pm.sources
                pm.source = nil
            end
            if (pm.sources ~= nil) then
                -- si présent on retire le saut de ligne
                pm.sources = p.sans_nl(pm.sources)
            end
            if (pm.source ~= nil) then
                -- si présent on retire le saut de ligne
                pm.source = p.sans_nl(pm.source)
            end
            local tmp
            if (pm.sources ~= nil) then
                tmp = "Sources : " .. pm.sources
            else
                tmp = "Source : " .. pm.source
            end
            if (parenthese) then
                ret = ret .. "(" .. tmp .. ")"
            else
                ret = ret .. tmp
            end
        end

        -- on ajoute les erreurs si présentes
        if (erreurs ~= nil) then
            if (pred) then
                ret = ret .. "<br/>"
            end
            ret = ret .. erreurs
        end
        -- on ferme la div des notes
        ret = ret .. '</div>'
    end

    -- on ferme la div principale
    ret = ret .. "</div>"

    -- si namespace encyclo (ttl = 0) on insert les catégories d'erreur
    if (ttl == 0) then
        local i = 1
        while (p.liste_cats[i] ~= nil) do
            ret = ret .. "[[Catégorie:" .. p.categorie_erreur .. "|" .. p.liste_cats[i] .. "]]"
            i = i + 1
        end
    end

    -- on retourne le résultat
    return ret
end

--[[
  Fonction appelable depuis un modèle. Se contente d'appeler demographie_m() qui fait le
    traitement et est également appelable depuis un autre module
--]]
function p.demographie(frame)
    -- pour simplifier on stocke la frame et la pframe
    p.frame = frame
    p.pframe = frame:getParent()

    -- pm est la table des parametres → on lit tous les paramètres référencés
    local pm = {}
    for k, v in pairs(p.parametres) do
        pm[v] = p.lit_parametre(k, true)
    end
    -- les paramètres numériques maintenant (les années)
    if require('Module:Yesno')(p.pframe.args.wikidata, p.pframe.args.wikidata) then
    	-- obtient les données de wikidata, mais les données locales auront priorité
    	p.valeur_wikidata(pm)
    	mw.logObject( pm, 'pm' )
    end
    for k, v in pairs(p.pframe.args) do
        if (type(k) == "number") then
            pm[k] = mw.text.trim(v)
        end
    end
    for k, v in pairs(p.frame.args) do
        if (type(k) == "number") then
            pm[k] = mw.text.trim(v)
        end
    end

    -- on appelle (et on retourne) la fonction principale
    return p.demographie_m(pm)
end


return p -- on retourne le module