Module:Démographie

De Aknotl
Version datée du 26 août 2018 à 13:55 par Infobox>Zebulon84 (ajout font-family avoir une largeur de police à peu près constante, ce qui n'était pas le cas sur Ubuntu et dérivé avec Firefox, qui utilise par défaut DejaVu Sans, bien plus large que Arial utilisée sur Windows et Liberation Sans utilisée par Chromium)
(diff) ← Version précédente | Voir la version actuelle (diff) | Version suivante → (diff)

La documentation pour ce module peut être créée à Module:Démographie/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:Chartes"

-- liste des paramètres reconnus (valeur = nom de la variable)
p.parametres = {
  ["titre"] = "titre",
  ["charte"] = "charte",
  ["colonnes"] = "colonnes",
  ["notes"] = "notes",
  ["source"] = "source",
  ["sources"] = "sources",
  ["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