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

Utilisation modifier

Le module Titulaires est constitué de différentes parties. Ses fonctions sont réutilisées par les modèles {{Titulaires}} et {{Liste des dirigeants successifs}}.

--[[
Module permettant l'affichage d'un tableau de titulaires d'une fonction, ou d'un
tableau de dirigeants (chefs d'État, commandants, etc.) à partir de Wikidata.
]]

local countryData = require 'Module:Country data'
local duration = require 'Module:Durée'
local formatDate = require 'Module:Date complexe'
local langue = require 'Module:Langue'
local linguistic = require 'Module:Linguistique'
local roman = require 'Module:Romain'
local tools = require 'Module:Outils'
local wd = require 'Module:Wikidata'
local yn = require 'Module:Yesno'

local p = {}

-- Propriétés pour les dirigeants
local leaderWikidataProperties = {
	['chef d\'État'] = {
		fonction = 'P1906',
		plurielFonction = 'Chefs d\'État',
		listeIndividus = 'P35',
	},
	['chef de l\'exécutif'] = {
		fonction = 'P1313',
		plurielFonction = 'Chefs de l\'exécutif',
		listeIndividus = 'P6',
	},
	['dirigeant'] = {
		plurielFonction = 'Dirigeants',
		listeIndividus = 'P1037'
	},
	['président'] = {
		plurielFonction = 'Présidents',
		listeIndividus = 'P488'
	},
	['secrétaire général'] = {
		plurielFonction = 'Secrétaires généraux',
		listeIndividus = 'P3975'
	},
	['directeur général'] = {
		plurielFonction = 'Directeurs généraux',
		listeIndividus = 'P169'
	},
	['membre du conseil d\'administration'] = {
		plurielFonction = 'Membres du conseil d\'administration',
		listeIndividus = 'P3320'
	},
	['commandant'] = {
		plurielFonction = 'Commandants',
		listeIndividus = 'P4791'
	},
	['colonel en chef'] = {
		plurielFonction = 'Colonels en chef',
		listeIndividus = 'P3460'
	},
	['responsable éditorial'] = {
		plurielFonction = 'Responsables éditoriaux',
		listeIndividus = 'P5769'
	}
}

-- Propriété pour les titulaires
local officeHolderWProps = {
	fonction = 'entity',
	plurielFonction = 'Titulaires de la fonction',
	listeIndividus = 'P1308'
}

local charterToClass = {
	['localité'] = 'localites',
	['commune'] = 'communes',
	['canton'] = 'cantons',
	['arrondissement'] = 'arrondissements',
	['intercommunalité'] = 'intercommunalites',
	['département'] = 'departements',
	['région'] = 'regions'
}

-- Association élément → Charte
local charterMap = {
	Q3455524 = 'région',
	Q6465 = 'département',
	Q702842 = 'arrondissement',
	Q2311958 = 'canton',
	Q3266850 = 'commune',  -- commune
	Q15284 = 'commune', -- municipalité
	Q1763214 = 'commune', -- municipio
	Q515 = 'commune', -- ville
	Q2785216 = 'localité' -- section de commune
}

-- Éléments étant sous-classe de Q4676846 (intérim)
-- À l'heure actuelle, il y en très peu; les lister ici est plus économe que
-- d'effectuer une requête Wikidata récursive pour chaque fonction. La liste
-- ci-dessous peut être mise à jour en utilisant la requête SPARQL suivante :
-- https://w.wiki/3SE
local actingSubclasses = {
	'Q4676846', 'Q3153784', 'Q4676862', 'Q4676866', 'Q4676868', 'Q6046657',
	'Q55831', 'Q2123628', 'Q3911042', 'Q4127082', 'Q4676854', 'Q6046654',
	'Q22971123', 'Q62920598', 'Q13436054', 'Q71125958', 'Q104900631'
}

local function getArgumentValue(params, argName)
	return tools.validTextArg(params.args, argName)
end

local function getArgumentValueWithSuffix(params, argName)
	return tools.validTextArg(params.args, argName,
		argName .. ' ' .. params.argSuffix)
end

local function getBooleanArgumentValue(params, argName, default)
	return yn(params:arg(argName) or '', default)
end

local function getCharterRecursively(entity, maxDepth) -- fonction permettant d'aller chercher la charte
	local candidates = {entity}
	for i = 0, maxDepth do
		local newCandidates = {}
		if i == 0 then
			recursiveProperty = 'P31' -- nature de l'élément
		else
			recursiveProperty = 'P279' -- sous-classe
		end
		for _, candidate in pairs(candidates) do
			local superclasses = wd.getIds(candidate, {removedupes = true,
				property = recursiveProperty, rank = 'valid'})
			
			newCandidates = wd.addNewValues(newCandidates, superclasses)
		end
		
		if #newCandidates == 0 then
			return nil
		end
		
		for _, candidate in pairs(newCandidates) do
			local charte = charterMap[candidate]
			if charte then
				return charte
			end
		end
		
		candidates = newCandidates
	end
end

local function getOfficesRecursively(entity, officeProp, maxDepth)
	if not officeProp then
		return nil
	end
	
	if officeProp == 'entity' then
		return wd.formatEntity(entity)
	end

	local candidates = {entity}
	for i = 0, maxDepth do
		local officesTable = {}
		for _, candidate in pairs(candidates) do
			local claims = wd.getClaims({entity = candidate,
				property = officeProp, rank = 'valid'})
			officesTable = wd.addNewValues(officesTable, claims)
		end
		
		if #officesTable > 0 then
			stringTable, _ = wd.stringTable({
				claims = wd.chronoSort(officesTable), removedupes = true,
				--[[Si une page correspondant à la fonction n'est pas disponible
				 sur Wikipédia en français, essayer d'obtenir la page de liste
				 correspondante (e.g. Liste des maires de Paris) et, si non
				 disponible, la superclasse
				 ]]
				defaultlinkquery = {property = {'P2354', 'P279'}},
				--Afficher les dates de validité de la fonction
				showdate = true
			})
			local formattedOffices = linguistic.conj(stringTable, 'or')
			if i == 0 then
				return formattedOffices
			else
				local locationName = wd.getLabel(entity)
				return formattedOffices .. ' ' .. linguistic.of(locationName)
			end
		end

		local newCandidates = {}
		if i == 0 then
			recursiveProperty = 'P31' -- nature de l'élément
		else
			recursiveProperty = 'P279' -- sous-classe
		end
		for _, candidate in pairs(candidates) do
			local superclasses = wd.getIds(candidate, {removedupes = true,
				property = recursiveProperty, rank = 'valid'})
			newCandidates = wd.addNewValues(newCandidates, superclasses)
		end
		
		if #newCandidates == 0 then
			return nil
		end
		
		candidates = newCandidates
	end
end

local function getDefaultTitle(entity, wProps)
	local formattedOffices = getOfficesRecursively(entity, wProps.fonction, 5)
	
	if formattedOffices then
		return 'Titulaires de la fonction de ' .. formattedOffices
	end

	return wProps.plurielFonction
end

local function getDeterminationMethod(statement) -- fonction permettant d'aller chercher la méthode de détermination, comme une élection
	local params = {conjtype = 'new line', removedupes = true,
		displayFormat = 'raw'}
	return wd.getFormattedQualifiers(statement, 'P459', params)
end

local function getDateIfAvailable(qid)
	local claims = wd.getClaims{entity = qid, property = 'P585', numval = 1}
	if not claims then
		return mw.wikibase.label(qid)
	end
	return wd.getDataValue(claims[1].mainsnak, {linktopic='-'})
end

local function getGenderedValue(entity, valueF, valueM, valueDefault)
	local gender = wd.getgender(entity)
	if gender == 'f' then
		return valueF
	elseif gender == 'm' then
		return valueM
	else
		return valueDefault
	end
end

local function getElectedIn(statement) -- fonction permettant d'aller chercher l'élection
	local qualifiers = wd.getQualifiers(statement, 'P2715')
	local entity = wd.getMainId(statement)
	if not qualifiers then
		return nil
	end
	for i, qualifier in ipairs(qualifiers) do
		qualifiers[i] = wd.formatSnak(qualifier,
			{labelformat = getDateIfAvailable,
			 novaluelabel = getGenderedValue(entity, 'non élue', 'non élu', 'non élu(e)')})
	end
	return linguistic.conj(qualifiers, 'new line')
end

local function getNominatedBy(statement) --  fonction permettant d'aller chercher qui a nommé la personne à ce poste
	local params = {conjtype = 'new line', removedupes = true,
		displayFormat = 'raw'}
	return wd.getFormattedQualifiers(statement, 'P4353', params)
end

local function getEndCause(statement) -- fonction permettant d'aller chercher le motif de fin, comme une mort en cours de mandat ou une démission
	local params = {conjtype = 'comma', removedupes = true,
		displayFormat = 'raw'}
	return wd.getFormattedQualifiers(statement, 'P1534', params)
end

local function dateObject(orig)
	--[[ transforme un snak en un nouvel objet utilisable par Module:Date complexe
		{type = 'dateobject', timestamp = str, era = '+' ou '-', year = number, month = number, day = number, calendar = calendar}
		Inspiré de Module:Wikidata
	]]-- 
	local newobj = formatDate.splitDate(orig.time, orig.calendarmodel)
	
	newobj.precision = orig.precision
	newobj.type = 'dateobject'
	return newobj
end

local function getDateObjectFromQualif(statement, qualif)
	if (not statement.qualifiers) or not (statement.qualifiers[qualif]) then
		return nil
	end
	local v = statement.qualifiers[qualif][1]
	if v.snaktype == 'value' then
		return dateObject(v.datavalue.value)
	end
end

local function getDateObjectFromProperty(entity, property, maxPrecision)
	local claim = wd.getClaims{entity = entity, property = property, numval = 1}
	if not claim then
		return
	end
	local snak = claim[1].mainsnak
	if snak.snaktype == 'value' then
		local dateObj = dateObject(snak.datavalue.value)
		if maxPrecision and dateObj.precision > maxPrecision then
			dateObj.precision = maxPrecision
		end
		return dateObj
	end
end

local function getDateWithPrefix(dateObject)
	local formattedDate = formatDate.simplestring(dateObject)
	local prefix
	if dateObject.precision <= 7 then  -- siècle, millénaire, ...
		prefix = 'au '
	elseif dateObject.precison == 8 then  -- décennie
		prefix = 'dans les '
	elseif dateObject.precision <= 10 then  -- mois, année
		prefix = 'en '
	else  -- jour et moins
		prefix = 'le '
	end
	return prefix .. formattedDate
end

local function getBirthDeathDates(statement, params)
	local entity = wd.getMainId(statement)
	
	local maxPrecision
	if not params:yesno('dates de vie complètes', false) then
		-- Seulement afficher les années de naissance / mort
		maxPrecision = 9
	end
	
	local dateOfBirth = getDateObjectFromProperty(entity, 'P569', maxPrecision)
	local dateOfDeath = getDateObjectFromProperty(entity, 'P570', maxPrecision)
	
	if dateOfBirth and dateOfDeath then
		return formatDate.simplestring(dateOfBirth)
		.. '&nbsp;- ' 
		.. formatDate.simplestring(dateOfDeath)
	elseif dateOfBirth then
		return getGenderedValue(entity, 'née ', 'né ', 'né(e) ')
			.. getDateWithPrefix(dateOfBirth)
	elseif dateOfDeath then
		return getGenderedValue(entity, 'morte ', 'mort ', 'mort(e) ')
			.. getDateWithPrefix(dateOfDeath)
	end
end

local langDirCache = {}  -- cache pour la direction d'une langue

local function getNameInOtherLanguage(statement, lang)
	local entity = wd.getMainId(statement)
	local label = wd.getLabel(entity, lang)
	if label then
		local dir = langDirCache[lang]
		if not dir then
			dir = langue.directionLangue({lang})
			langDirCache[lang] = dir  -- stocker la direction dans le cache
		end
		return langue.indicationDeLangue({'', lang, texte = label, dir = dir})
	end
end

local function shouldShowActing(statement, params)
	return 
		-- Rôle de l'élément désigné = intérim
		wd.hasQualifier(statement, 'P3831', {'Q4676846'})
		or (
			-- Pas de colonne "fonction", sinon l'indication est inutile
			not params:yesno('fonction', false)
			-- Fonction = intérim ou président par intérim ou ...
			and wd.hasQualifier(statement, 'P39', actingSubclasses)
		)
end

local actingText = '<small>[[Intérim (droit constitutionnel)|Par intérim]] :</small><br />'

local function textWithSortValue(text, sortValue)
	if not sortValue then
		return text
	end
	return '<span data-sort-value="' .. sortValue .. '">' .. text .. '</span>'
end

local noIdentity = textWithSortValue('Vacant', '~~~~~~')

local function getIdentity(statement, params)
	local nameParams = {showsource = params:yesno('sources', true),
		novaluelabel = noIdentity}
	local name = wd.formatStatement(statement, nameParams) or ''
	
	local showNameInOtherLanguage = params:arg('nom dans la langue')
	if showNameInOtherLanguage then
		local labelInOtherLanguage = getNameInOtherLanguage(statement, 
			showNameInOtherLanguage)
		if labelInOtherLanguage then
			name = name .. '<br /><small>' .. labelInOtherLanguage .. '</small>'
		end
	end
	
	if params:yesno('dates de vie', true) then
		local dates = getBirthDeathDates(statement, params)
		if dates then
			name = name .. '<br /><small>(' .. dates .. ')</small>'
		end
	end
	
	if params:yesno('intérim', true)
			and shouldShowActing(statement, params) then
		local qid = wd.getMainId(statement)
		return actingText .. name, wd.getLabel(qid)
	end
	
	return name
end

local function getCitizenship(statement)
	local entity = wd.getMainId(statement)
	return countryData.getNationality({item = entity, mindate = '-',
		conjtype = 'new line'})
end

local function formatCountry(snak)
	local qid = wd.getId(snak)
	return countryData.standarddisplay(qid) or wd.formatEntity(qid)
end

local function getCountry(statement)
	local params = {conjtype = 'new line', removedupes = true,
		displayformat = formatCountry}
	return wd.getFormattedQualifiers(statement, 'P17', params)
end

local function getSeriesOrdinal(statement)
	local params = {conjtype = 'comma', removedupes = true}
	return wd.getFormattedQualifiers(statement, 'P1545', params)
end

--[[ Si date de début et date de fin ne sont pas disponibles, utiliser "date",
mais ce n'est pas recommandé]]
local function getSimpleDate(statement)
	if not wd.hasQualifier(statement, {'P580', 'P582'}) then
		return getDateObjectFromQualif(statement, 'P585')
	end
end

local function getStartDate(statement)
	local startDate = getDateObjectFromQualif(statement, 'P580')
	if startDate then
		return formatDate.simplestring(startDate)
	end
	
	local simpleDate = getSimpleDate(statement)
	if simpleDate then
		return formatDate.simplestring(simpleDate)
	end
end

local function isDateInFuture(date)
	return os.difftime(os.time(date), os.time(os.date('!*t'))) > 0
end

local function getEndDate(statement, params)
	local endDateObj = getDateObjectFromQualif(statement, 'P582')
	if endDateObj then
		local endDate = formatDate.simplestring(endDateObj)
		local endCause = getEndCause(statement)
		if endCause then
			return endDate .. '<br /><small>(' .. endCause .. ')</small>'
		end
		return endDate
	end
	
	local simpleDate = getSimpleDate(statement)
	if simpleDate then
		return formatDate.simplestring(simpleDate)
	end
	
	local startDate = getDateObjectFromQualif(statement, 'P580')
	if params.latest and statement.rank == 'preferred'
			and not isDateInFuture(startDate) then
		-- Valeur "en cours", avec clé de tri maximale, affichée grâce à un rang
		-- privilégié
		return 'En cours', '999999-12-12'
	end
end

local function getDuration(statement, params)
	local startDate = getDateObjectFromQualif(statement, 'P580')
	if not startDate then
		return
	end
	local precision = startDate.precision
	local endDate = getDateObjectFromQualif(statement, 'P582')
	if endDate then
		precision = math.min(precision, endDate.precision)
	elseif not (params.latest and statement.rank == 'preferred')
			or isDateInFuture(startDate) then
		-- Mandat non considéré comme "en cours"
		return
	else
		endDate = {}
	end
	if precision < 9 then
		-- No year
		return
	end
	if precision < 11 then
		-- No day
		startDate.day = nil
		endDate.day = nil
		if precision < 10 then
			-- No month
			startDate.month = nil
			endDate.month = nil
		end
	end
	return duration._duree({startDate.day, startDate.month, startDate.year,
		endDate.day, endDate.month, endDate.year})
end
	

local function getPartyColoredSquare(qid) -- Permet d'aller chercher la couleur politique d'un parti ; celle-ci est définie dans son élément via la propriété P465 couleur triplet hexadécimal sRGB
	local partyColor = wd.formatStatements{entity = qid,
		property = 'P465', numval = 1}
	if partyColor then
		return mw.getCurrentFrame():expandTemplate{
			title = 'Carré couleur', args = {'#' .. partyColor }
		}
	end
end

local function getShortNameIfAvailable(qid)
	local shortName = wd.formatStatements{entity = qid,
		property = 'P1813', numval = 1, isinlang = 'locallang'}
	return shortName or mw.wikibase.label(qid)
end

local function getPartyOrParliamentaryGroup(statement, prop, params, abbreviate)
	local qualifiers = wd.getQualifiers(statement, prop)
	if not qualifiers then
		return nil
	end
	
	local showPoliticalColors = params:yesno('couleur politique', false)

	for i, qualifier in ipairs(qualifiers) do
		local partyFormatParams = {}
		if abbreviate then
			partyFormatParams.labelformat = getShortNameIfAvailable
		end
		local formattedSnak = wd.formatSnak(qualifier, partyFormatParams)
		if showPoliticalColors then
			local qid = wd.getId(qualifier)
			local coloredSquare = getPartyColoredSquare(qid)
			if coloredSquare then
				formattedSnak = textWithSortValue(
					coloredSquare .. ' ' .. formattedSnak,
					wd.getLabel(qid))
			end
		end
		qualifiers[i] = formattedSnak
	end
	return linguistic.conj(qualifiers, 'new line')
end

local function getParty(statement, params)
	return getPartyOrParliamentaryGroup(statement, 'P102', params,
		 params:yesno('abréger étiquette', false))
end

local function getParliamentaryGroup(statement, params)
	return getPartyOrParliamentaryGroup(statement, 'P4100', params,
		 params:yesno('abréger groupe parlementaire', false))
end

local function getPositionsHeld(statement)
	local params = {conjtype = 'new line', removedupes = true}
	return wd.getFormattedQualifiers(statement, 'P39', params)
end

local function getParliamentaryOrdinal(entity)
	-- Recherche de nature de l'élément = Q15238777
	local legislativeTermInfos = wd.getClaims({entity = entity,
		property = 'P31', rank = 'valid', targetvalue = 'Q15238777',
	})
	if not legislativeTermInfos then
		return nil
	end
	
	for _, legislativeTermInfo in ipairs(legislativeTermInfos) do
		if legislativeTermInfo.qualifiers then
			local parliamentaryTermOf = legislativeTermInfo.qualifiers['P642']
			local seriesOrdinal = legislativeTermInfo.qualifiers['P1545']
			if parliamentaryTermOf and #parliamentaryTermOf == 1
					and seriesOrdinal and #seriesOrdinal == 1
					and seriesOrdinal[1].snaktype == 'value' then
				return tonumber(seriesOrdinal[1].datavalue.value)
			end
		end
	end
end

local function getParliamentaryTermLabel(ordinal)
	local exposant
	if ordinal == 1 then exposant = 're' else exposant = 'e' end

	return (roman.toRoman(ordinal) or ordinal) 
		.. '<sup>' .. exposant .. '</sup>'
end

local function getParliamentaryTerm(statement, params)
	if not statement.qualifiers then
		return nil
	end

	local qualifiers = statement.qualifiers['P2937'] -- Législature
	if not qualifiers or #qualifiers == 0 then
		return nil
	end
	
	local ordinalLabels = {}
	local smallestOrdinal
	
	if params:yesno('abréger législature', false) then
		local countOrdinals = 0
		for _, qualifier in ipairs(qualifiers) do
			local qualifierId = wd.getId(qualifier)
			local ordinal = getParliamentaryOrdinal(qualifierId)
			if ordinal then
				local qualifierLabel = mw.wikibase.label(qualifierId) or ''
				local termLabel = '<span title="' .. qualifierLabel .. '">'
					.. getParliamentaryTermLabel(ordinal)
					.. '</span>'
				ordinalLabels[qualifierId] = termLabel
				qualifier._parliamentOrdinal = ordinal
				countOrdinals = countOrdinals + 1
			end
		end
		
		if countOrdinals == #qualifiers then
			-- Autant d'ordinaux que de qualifiers, on peut trier
			table.sort(qualifiers,
				function(a, b)
					return a._parliamentOrdinal < b._parliamentOrdinal
				end)
			smallestOrdinal = qualifiers[1]._parliamentOrdinal
		end
	end
	
	local args = {defaultlinkquery = 'P2354',
		labelformat = function(qid)
			return ordinalLabels[qid] or mw.wikibase.label(qid)
		end
	}
	for i, qualifier in ipairs(qualifiers) do
		qualifiers[i] = wd.formatSnak(qualifier, args)
	end
	
	return linguistic.conj(qualifiers, 'and'), smallestOrdinal
end

local function getPicture(statement)
	if statement.mainsnak.snaktype == 'novalue' then
		-- Poste vacant
		return 
	end
	local entity = wd.getMainId(statement)
	local claims = wd.getClaims({entity = entity, rank = 'best',
		property = 'P18', numval = 1})
	local caption
	local mediaName
	
	if claims then
		local claim = claims[1]
	
		mediaName = wd.formatStatement(claim)
		caption = wd.getFormattedQualifiers(claim, {'P2096'},
			{isinlang = 'fr', conjtype = 'comma'})

		if not caption or caption == '' then
			local personName = mw.wikibase.label(entity)
			if personName then
				caption = personName
				local pictureDate = wd.getFormattedQualifiers(claim,
					{'P585'}, {conjtype = 'comma'})
				if pictureDate then
					caption = caption .. ', ' .. pictureDate .. '.'
				end
			end
		end
	else
		-- Pas de portrait
		mediaName = getGenderedValue(entity,
			'Gray - replace this image female.svg',
			'Gray - replace this image male.svg',
			'Sin foto.svg')
		local personName = mw.wikibase.label(entity)
		if personName then
			caption = 'Pas de portrait sous licence libre disponible pour '
				.. personName .. '.'
		else
			caption = 'Pas de portrait sous licence libre disponible.'
		end
	end
	
	return '[[Fichier:' .. mediaName .. '|80px|' .. caption .. ']]'
end

local numero = '<abbr class="abbr" title="Numéro">N<sup>o</sup></abbr>'

local cellKindProperties = {
	['ordre'] = {fn = getSeriesOrdinal, dataSortType = 'unsortable',
		columnName = numero},
	['portrait'] = {fn = getPicture, dataSortType = 'unsortable',
		style = {padding = '0', ['vertical-align'] = 'middle'}},
	['identité'] = {fn = getIdentity, dataSortType = 'text'},
	['nationalité'] = {fn = getCitizenship, dataSortType = 'text'},
	['pays'] = {fn = getCountry, dataSortType = 'text'},
	['début'] = {fn = getStartDate},
	['fin'] = {fn = getEndDate},
	['durée'] = {fn = getDuration},
	['étiquette'] = {fn = getParty, dataSortType = 'text'},
	['groupe parlementaire'] = {fn = getParliamentaryGroup, dataSortType = 'text'},
	['fonction'] = {fn = getPositionsHeld, dataSortType = 'text'},
	['législature'] = {fn = getParliamentaryTerm, dataSortType = 'number'},
	['méthode de détermination'] = {fn = getDeterminationMethod,
		dataSortType = 'text', columnName = 'Déterminé par'},
	['élection'] = {fn = getElectedIn, dataSortType = 'text',
		columnName = 'Élection'},
	['nommé par'] = {fn = getNominatedBy,
		dataSortType = 'text', columnName = 'Nommé par'},
}

local multiColumns = {
	['période'] = {'début', 'fin'}
}

local orderedColumnsWithDefaultVisibility = {
	{name = 'ordre', defaultVisibility = false},
	{name = 'portrait', defaultVisibility = false},
	{name = 'identité', defaultVisibility = 'always'},
	{name = 'nationalité', defaultVisibility = false},
	{name = 'pays', defaultVisibility = false},
	{name = 'période', defaultVisibility = true},
	{name = 'durée', defaultVisibility = true},
	{name = 'étiquette', defaultVisibility = false},
	{name = 'groupe parlementaire', defaultVisibility = false},
	{name = 'fonction', defaultVisibility = false},
	{name = 'législature', defaultVisibility = false},
	{name = 'méthode de détermination', defaultVisibility = false},
	{name = 'élection', defaultVisibility = false},
	{name = 'nommé par', defaultVisibility = false},
}

local function getColumnsAndCellKinds(params)
	local headerColumns = {}
	local cellKinds = {}
	
	for _, column in ipairs(orderedColumnsWithDefaultVisibility) do
		local shouldShowColumn = column.defaultVisibility == 'always'
			or params:yesno(column.name, column.defaultVisibility)
		if shouldShowColumn then
			table.insert(headerColumns, column.name)
			local columnCellKinds = multiColumns[column.name] or {column.name}
			for _, cellKind in ipairs(columnCellKinds) do
				table.insert(cellKinds, cellKind)
			end
		end
	end
	
	return headerColumns, cellKinds
end

local function getHeaderBaseCell(column)
	columnProperties = cellKindProperties[column] or {}

	local columnName = columnProperties.columnName or linguistic.ucfirst(column)
	local baseCell = mw.html.create('th')
		:attr('scope', 'col')
		:wikitext(columnName)
		
	local dataSortType = cellKindProperties[column] 
		and cellKindProperties[column].dataSortType
	if dataSortType then
		if dataSortType == 'unsortable' then
			baseCell = baseCell:addClass('unsortable')
		else
			baseCell = baseCell:attr('data-sort-type', dataSortType)
		end
	end

	return baseCell
end

local function getHeader(columns)
	local headerFirstRow = mw.html.create('tr')
	local headerSecondRow = mw.html.create('tr')
		
	for _, column in pairs(columns) do
		local cell = getHeaderBaseCell(column)
			
		local subcolumns = multiColumns[column]
		if subcolumns then
			cell = cell:attr('colspan', #subcolumns)
			for _, subColum in ipairs(subcolumns) do
				headerSecondRow:node(getHeaderBaseCell(subColum):done())
			end
		else
			cell = cell:attr('rowspan', 2)
		end
		
		headerFirstRow = headerFirstRow:node(cell:done())
	end
	
	return headerFirstRow:done(), headerSecondRow:done()
end

local function getLeaderRow(cellKinds, statement, params)
	local row = mw.html.create('tr')
	
	if statement.mainsnak.snaktype == 'novalue' then
		-- Poste vacant
		row = row:css('font-style', 'italic')
			:css('background-color', '#00000009')
	end
		
	for _, cellKind in pairs(cellKinds) do
		local ckp = cellKindProperties[cellKind]
		local success, result, sortValue = pcall(ckp.fn, statement, params)
		if not success then
			result = tostring(mw.html.create('span')
				:wikitext('Erreur')
				:attr('title', result)
				:css('font-style', 'italic')
				:css('font-size', 'small')
				:done())
		end
		local td = mw.html.create('td'):wikitext(result or '')
		if sortValue then
			td = td:attr('data-sort-value', sortValue)
		end
		if ckp.style then
			td = td:css(ckp.style)
		end
		row = row:node(td:done())
	end
	
	return row:done()
end

local function getTitle(params, wProps)
	local title = params:arg('titre') or getDefaultTitle(params.entity, wProps)
	
	-- Bouton d'édition Wikidata
	title = wd.addLinkBack(title, params.entity, wProps.listeIndividus)
	
	-- Formattage du titre
	return mw.html.create('caption')
		:wikitext(title)
		:done()
end

local function officeHolders(params, wProps)
	local elus = wd.getClaims({entity = params.entity,
		property = wProps.listeIndividus, sorttype = 'chronological',
		rank = 'valid'})
	
	if not elus then
		return
	end
	
	local title = getTitle(params, wProps)
	
	local headerColumns, cellKinds = getColumnsAndCellKinds(params)
	
	local firstHeadRow, secondHeaderRow = getHeader(headerColumns)
	local maxWidth = params:arg('largeur maximale') or '1000px'
	
	local tab = mw.html.create('table')
		:addClass('wikitable')
		:addClass('centre')
		:addClass('sortable')
		:addClass(charterToClass[params.charter])
		:css('max-width', maxWidth)  -- largeur maximale du tableau
		:css('vertical-align', 'top')  -- alignement vertical en haut
		:node(title)  -- titre
		:node(firstHeadRow)  -- première ligne
		:node(secondHeaderRow)  -- deuxième ligne
		
	params.latest = 0
	-- Ajout des lignes des dirigeants
	for i, statement in ipairs(elus) do
		if i == #elus then
			params.latest = 1
		end
		local leaderRow = getLeaderRow(cellKinds, statement, params)
		tab = tab:node(leaderRow)
	end

	return tostring(tab:done())
end

local function prepareParams(frame, argumentValueGetter)
	local args = tools.extractArgs(frame)
	
	-- Entité Wikidata
	local entity = wd.getEntity(args.wikidata)
	
	if not entity then
		error('Pas d\'entité Wikidata pour l\'élément.')
	end
	
	return {
		entity = entity, args = args,
		charter = args.charte or getCharterRecursively(entity, 2),
		yesno = getBooleanArgumentValue,
		arg = argumentValueGetter or getArgumentValue
	}
end

function p.tableauDesTitulaires(frame)
	return officeHolders(prepareParams(frame), officeHolderWProps)
end

function p.tableauDesDirigeants(frame)
	local params = prepareParams(frame, getArgumentValueWithSuffix)

	local wPropsList
	local types = tools.validTextArg(params.args, 1, 'types')
	if types then
		wPropsList = {}
		for leaderType in mw.text.gsplit(types, '%s*,%s*') do
			wPropsList[leaderType] = leaderWikidataProperties[leaderType]
		end
	else
		wPropsList = leaderWikidataProperties
	end

	local tables = {}
	
	for leaderType, wProps in pairs(wPropsList) do
		params.argSuffix = leaderType
		local table_ = officeHolders(params, wProps)
		if table_ then
			table.insert(tables, table_)
		end
	end
	
	return table.concat(tables, '\n')
end

return p