Module:Country data/Bac à sable

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

Ce module contient des informations utilisées par le système country.

--[[
This module is intended to replace the functionality of {{drapeau2}} and related
templates.  It provides several methods, including
]]
local titleSelf = mw.title.new(... or mw.getCurrentFrame():getTitle())
local sandbox = false
if titleSelf.isSubpage then
	sandbox = titleSelf.subpageText:match[[^Bac à sable]] and true
end

local p = {};

local gdata = mw.loadData ("Module:Country data/liste" .. (sandbox and "/Bac à sable" or ""))
local dates = require "Module:date complexe"
local linguistic = require "Module:Linguistique"
local Outils = require "Module:Outils"
local Drapeau = require("Module:Drapeau" .. (sandbox and "/Bac à sable" or ""))

local function _getCompetition(c,aaaa)
	local gdomain = mw.loadData ( "Module:Drapeau/Domaine" ) -- chargé seulement sur les articles où ça sert, ça prend un peu de place
	
	local symbs = {
		['cm'] = "à la Coupe du monde",
		['coupedumonde'] = "à la Coupe du monde",
		['ce'] = "au championnat d'Europe",
		['euro'] = "au championnat d'Europe",
		['chm'] = "au championnat du monde",
		['can'] = "à la Coupe des Confédérations",
		['coupedesconfederations'] = "à la Coupe des Confédérations",
		['en'] = "en",
		['jo'] = "aux Jeux olympiques",
		['jp'] = "aux Jeux paralympiques",
	}

	local str = symbs[string.lower(c or '')] or ""
	
	--edition
	if(aaaa ~= nil and aaaa ~= "") then
		if(c=="jo"  or c=="jp" ) then
			local o=gdomain.jo["_"..aaaa];
			if(o ~= nil) then
				str = str .." "..o
			end
		else
		   str = str .." "..aaaa
		end
	end

	return str
end

local function printFlag(flagfile, alt, displayformat)
	
	displayformat = displayformat or {}
	local size = displayformat.size or '20x15'
	local border = 'border|' 
	
	if displayformat.border == '-' then
		border = ''
	end
	
	if not alt then
		alt = ''
	end
	return '<span class="flagicon">' ..
			'[[Fichier:' .. flagfile .. '|' .. size ..'px|' .. border  .. alt ..'|class=noviewer]]' ..
			'</span>';
end

local function bestfordate(data, period) -- data contient une table dont les clés sont des dates au format ['2010'] = ou ['2010-05-04'] = 
	if type(data) ~= 'table' then
		return data
	end

	-- très artisanal, à améliorer
	if (not period) or (period == 'default') then
		return data.default
	end
	
	local val = data.default
	local bestdate = '-1000-01-01'
	for i, j in pairs(data) do
		if i ~= 'default' and (i:match('^-?%d+$') or i:match('^-?%d--%d--%d+$')) and dates.before(period, i) and dates.before(i, bestdate) then
			val = j
			bestdate = i
		end -- bestdate est la date la plus récente antérieure ou égale à period, val est la donnée à cette date
	end
	return val
end


local function valueAtDate(data, period, topic, forceTopic) -- topic: type de drapeau, genre de l'adjectif, etc.
	topic = topic or "default"
	if type(data) ~= 'table' and (topic == "default" or forceTopic ~= true) then
		return data
	end
	
	if topic ~= "default" and data[topic] then
		return bestfordate(data[topic], period)
	elseif topic == "default" or forceTopic ~= true then
		if type(data.default) == 'table' and data.default.default then
			return bestfordate(data.default, period)
		else
			return bestfordate(data, period)
		end
	end

	return error()
end

local function getData(datatable, typedata, topic, period, forceTopic) -- récupère la chaîne de caractère la plus appropriée dans la datatable
	-- datatable: la table de sous module par exemple [[Module:Country data/grenade]]
	-- typedata: "flag" / "name" / "adjective"
	-- period: data in ISO format
	-- topic: for instance "navy" for naval ensign
	local val = datatable[typedata]

	if val == nil then -- error handling ?
		return nil
	end

	local val = valueAtDate(val, period, topic, forceTopic)

	if val == nil then -- error handling ?
		return error(val)
	end
	return val
end

local function getAdjective(data, gender, number)
	if not gender then
		gender = 'm'
	end
	if not number then
		number = 's'
	end
	if (gender ~= 'm' and gender ~= 'f') then
		return error('gender devrait être m ou f mais est ' .. gender)
	end
	if (number ~= 's' and number ~= 'p') then
		return error('number devrait être s ou p mais est ' .. number)
	end
	return getData(data, 'adjective', (gender .. number))
end

local function getDemonym (data, gender, number)
	if data.demonym == nil then
		return linguistic.ucfirst( getAdjective(data, gender, number) )
	end
	gender = gender or 'm'
	number = number or 's'
	if (gender ~= 'm' and gender ~= 'f') then
		return error('gender devrait être m ou f mais est ' .. gender)
	end
	if (number ~= 's' and number ~= 'p') then
		return error('number devrait être s ou p mais est ' .. number)
	end
	return getData(data, 'demonym', (gender .. number))
end

local function getLabel(data, topic, period, form)
	local label
	if (not form) or form == 'short' then
		label = getData(data, 'shortname', topic, period)
	end
	if not label then
		label = getData(data, 'name')
	end
	if (not label) and data.item then
		label = mw.wikibase.label(getData(data, 'item'))
	end
	return label
end

local function getLink(data, topic, period)
	local link = getData(data, 'link', topic, period)
	if (not link) and data.item then
		link =  mw.wikibase.label(getData(data, 'item'))
	end
	return link
end

local function applyregex(str, areadata)
	local cio = 'code CIO (en attente)' --require('Module:Wikidata')._formatStatements({entity= areadata.item, property= 'P984'}) or '??'

	local label = getData(areadata, 'name')
	local of = linguistic.of(label, areadata.genre)
	
	str = mw.ustring.gsub(str, '$de$label', of)
	str = mw.ustring.gsub(str, '$label', label)
	str = mw.ustring.gsub(str, '$cio', 'cio') 

	if string.find(str, '$gentile') then
		local function get(genre) return getData(areadata, 'adjective', genre) end
		local gentileMS, gentileFS, gentileMP, gentileFP = get('ms'), get('fs'), get('mp'), get('fp')
		str = mw.ustring.gsub(str, '$gentileMS', gentileMS)
		str = mw.ustring.gsub(str, '$gentileFS', gentileFS)
		str = mw.ustring.gsub(str, '$gentileMP', gentileMP)
		str = mw.ustring.gsub(str, '$gentileFP', gentileFP)
	end
	return str
end

local function getDatatable(zone)
	zoneLower = mw.ustring.lower(zone)
	zoneUpper = mw.ustring.upper(zone)
	zone = gdata.wikidata[zoneLower] or gdata.alpha3[zoneUpper] or gdata.redirects[zoneLower] or zoneLower
	return require('Module:Country data/' .. zone)
end


local function flagIcon(data, flagtype, period, displayformat)
	local flagimage = getData(data, 'flag', flagtype, period)
	if flagimage then
		return printFlag(flagimage, '', displayformat)
	end
end

local function getcontents(frame,country,params)
  return frame:expandTemplate({title="Country data "..country;args=params})
end

function p.gettable(frame,country,params)
--Returns the parameters of a country data template as a Lua table
  --If not a valid data template, return empty table
  local bool, s = pcall(getcontents,frame,country,params or {})
  if bool and (string.find(s,"^%{%{ *%{%{%{1") or string.find(s,"^%{%{safesubst: *%{%{%{1"))
  then
    --Replace parameter delimiters with arbitrary control characters
    --to avoid clashes if param values contain equals/pipe signs
    s = string.gsub(s,"|([^|=]-)=","\1\1%1\2")
    s = string.gsub(s,"}}%s*$","\1")
    --Loop over string and add params to table
    local part = {}
    for par in string.gmatch(s,"\1[^\1\2]-\2[^\1\2]-\1") do
      local k = string.match(par,"\1%s*(.-)%s*\2")
      local v = string.match(par,"\2%s*(.-)%s*\1")
      if v and not (v=="" and string.find(k,"^flag alias")) then
        part[k] = v
      end
    end
    return part
  else
  	return {}
  end
end

function p.gettemplate(frame)
--For testing, recreates the country data from the created Lua table
  --Get data table
  local data = p.gettable(frame,frame.args[1])
  --Concatenate fields into a template-like string
  local out = "{{ {{{1}}}"
  for k,v in pairs(data) do
    out = out.."\n| "..k.." = "..v
  end
  return out.."\n}}"
end

local function convertTemplateToModuleFormat(data)
	if not data.alias or not data['flag alias'] then
		return false, nil
	end
	local newData = {
		name = data['shortname alias'] or data.alias,
		--gender = {},
		link = {default = {default = data.alias}},
		flag = {default = {default = data['flag alias']}},
		--flagsize = {},
		--flagborder = {},
		--flagalt = {},
		--sortkey = {},
	}
	if data.size and data.size ~= '' then
		newData.size = {default = {default = data.size}}
	end

	local keys = {
		gender = 'gender',
		link = 'link',
		flag = 'flag',
		['size flag'] = 'flagsize',
		border = 'flagborder',
		['alt attribute'] = 'flagalt',
		sortkey = 'sortkey',
	}
	local topics = {
		naval = 'naval',
		['air force'] = 'airforce',
		army = 'army',
		marines = 'marines',
		['coast guard'] = 'coastguard',
		military = 'military',
	}
	for k, v in pairs(data) do
		local foundKey, foundTopic, foundPeriod
		for key, newKey in pairs(keys) do
			for topic, newTopic in pairs(topics) do
				if k == key .. ' alias-' .. topic then
					foundPeriod = 'default'
				else
					foundPeriod = k:match('^' .. key .. ' alias%-' .. topic .. '%-(%d+)$')
				end
				if foundPeriod then
					foundTopic = newTopic
					break
				end
			end
			if not foundTopic then
				if k == key then
					foundPeriod = 'default'
				else
					foundPeriod = k:match('^' .. key .. ' alias%-(%d+)$')
				end
				if foundPeriod then
					foundTopic = 'default'
				end
			end
			if not foundTopic then
				foundPeriod = 'default'
				foundTopic = k:match('^' .. key .. ' alias%-(.+)')
			end
			if foundTopic then
				foundKey = newKey
				break
			end
		end
		if foundKey and foundTopic and foundPeriod then
			newData[foundKey] = newData[foundKey] or {}
			newData[foundKey][foundTopic] = newData[foundKey][foundTopic] or {}
			if key == 'link' and v:find('|') then
				newData['name'] = newData['name'] or {}
				newData['name'][foundTopic] = newData['name'][foundTopic] or {}
				local vlink, vname = v:match('^(.-)|(.-)$')
				newData[foundKey][foundTopic][foundPeriod] = vlink
				newData['name'][foundTopic][foundPeriod] = vname
			else
				newData[foundKey][foundTopic][foundPeriod] = v
			end
		end
	end
	return true, newData
end

-- Retrouver topic et period utilisés par le format modules de données à partir de la variant utilisée par les modèles
local function getTopicAndPeriodFromVariant(variant)
	if variant == nil then
		return nil, nil
	end
	-- TODO déplacer un niveau au-dessus pour éviter la répétition ?
	local topics = {
		naval = 'naval',
		['air force'] = 'airforce',
		army = 'army',
		marines = 'marines',
		['coast guard'] = 'coastguard',
		military = 'military',
	}
	for topic, newTopic in pairs(topics) do
		if variant == topic then
			return newTopic, nil
		end
		local period = variant:match('^' .. topic .. '%-(%d+)$')
		if period then
			return newTopic, period
		end
	end
	if variant:match('^(%d+)$') then
		return nil, variant
	end
	return variant, nil
end

local function getDatatableFromTemplate(country)
	local data = p.gettable(mw.getCurrentFrame(), country)
	if next(data) == nil then
		return false, nil
	end
	return convertTemplateToModuleFormat(data)
end

local function getDatatableFallback(zone)
	local success, data = pcall(getDatatable, zone)
	if not success then
		success, data = getDatatableFromTemplate(zone)
	end
	return success, data
end

local function getAnyDatatable(zone, usedata)
	if usedata == 'template' then
		return getDatatableFromTemplate(zone)
	elseif usedata == 'module' then
		return pcall(getDatatable, zone)
	else
		return getDatatableFallback(zone)
	end
end

function p.standarddisplay(zone, args)
	if not zone then
		return nil
	end
	-- nettoyage des paramètres
	if not args then
		args = {}
	end
	for i, j in pairs(args) do
		args[i] = mw.text.trim(j) -- remove whitespaces
		if args[i] == '' then args[i] = nil end
	end

   -- ajout des valeurs par défaut
	local size = args.flagsize or '20x15'
	local flagtype = args.type

	local align = args.align or 'left'
	local link = args.link
	local period = args.date
	local competition = args.competition
	local edition = args.edition
	local extra = ''
	
	local success, data = pcall(getDatatable, zone)
	if not success then
		success, data = getDatatableFromTemplate(zone)
	end
	if not success then
		if args.strict then
			return error('lieu non reconnu')
		end
		return nil
	end
	
	-- image
	local flag = flagIcon(data, flagtype, period)
	if (args.label == '-') then
		return flag, true
	end

	-- text
	local text
	local label = getLabel(data, flagtype)
	local link = getLink(data, flagtype)

	if competition then 
		competition = _getCompetition(linguistic.toascii(competition),args["édition"])
	end
	if link and competition then
		link = link .. ' ' .. competition
	end

	if link then
		text = '[[' .. link .. '|' .. label .. ']]'
	end

	-- si les données sont extraites d'un formulaire standard comme module:Drapeau/domain, appliquer des regex
	if string.match(text, '%$') then
		text = applyregex(text, data)
	end
	
	local val
	if align == 'right' then
		val = text .. '&nbsp;' .. flag
	else
		val = flag .. '&nbsp;' .. text
	end
	
	return val, true -- true indique le succès
end

-- fonction appelable avec #invoke
function p.standarddisplay2(frame)
	result, success = p.standarddisplay(frame.args['pays'],frame.args)
	return result
end

-- fonction utilitaire pour récupérer les données du drapeau
function p.getFlagData(country, topic, period, usedata)
	local success, datatable = getAnyDatatable(country, usedata)
	if not success then
		return error('Lieu non reconnu') -- FIXME comment faudrait-il gérer l'erreur ?
	end
	
	local name = getData(datatable, 'name', topic, period)
	local sortkey = getData(datatable, 'sortkey', topic, period)
	if not sortkey then
		sortkey = name
	end
	
	local flag = getData(datatable, 'flag', topic, period)
	local border = getData(datatable, 'flagborder', topic, period)
	border = border ~= '' and border ~= false
	local alt = getData(datatable, 'flagalt', topic, period)
	if not alt then
		alt = name
	end
	local size = getData(datatable, 'flagsize', topic, period)
	local link = getData(datatable, 'link', topic, period)
	if not link then
		link = name
	end
	local gender = getData(datatable, 'gender', topic, period)
	return {
		flag = flag,
		name = name,
		sortkey = sortkey,
		border = border,
		alt = alt,
		size = size,
		link = link,
		gender = gender,
	}
end

-- modèle {{pays}}
function p.pays(frame)
	local args = Outils.extractArgs(frame)
	local templatename = Outils.validTextArg(args, 1)
	local name = Outils.validTextArg(args, 'name', 'nom', 1)
	local variant = Outils.validTextArg(args, 'variant', 2)
	local size = Outils.validTextArg(args, 'size', 'taille')
	if not size then
		size = '20x18'
	end
	size = size .. 'px'
	
	-- FIXME La rétrocompatibilité parfaite avec {{pays}} implique de mettre à jour les sous-modules de données
	-- par exemple ajouter border="" dans le sous-module Module:Country data/népal
	
	local topic, period = getTopicAndPeriodFromVariant(variant)

	-- TODO args.usedata ne devrait pas avoir lieu d'être à terme, mais il est utile pour tester
	local flagData = p.getFlagData(templatename, topic, period, args.usedata)
	
	local alt = flagData.alt
	if alt == nil then
		alt = flagData.name
	end
	
	return Drapeau.displayFlag(flagData.flag, {
		border = flagData.border,
		size = size,
		alt = alt,
		sortkey = flagData.sortkey,
		text = '[[' .. flagData.link .. '|' .. name .. ']]'
	})
end

-- modèle {{pays/lien seul}}
function p.pays_lien_seul(frame)
	local args = Outils.extractArgs(frame)
	local templatename = Outils.validTextArg(args, 1)
	local variant = Outils.validTextArg(args, 2)
	
	-- TODO args.usedata ne devrait pas avoir lieu d'être à terme, mais il est utile pour tester
	local success, datatable = getAnyDatatable(templatename, args.usedata)
	if not success then
		return error('Lieu non reconnu') -- FIXME comment faudrait-il gérer l'erreur ?
	end
	
	local topic, period = getTopicAndPeriodFromVariant(variant)
	
	local name = getData(datatable, 'name', topic, period)
	local link = getData(datatable, 'link', topic, period)
	if not link then
		link = name
	end
	
	return '[[' .. link .. '|' .. name .. ']]'
end

-- Selon la valeur de topic, implémente les modèles
-- naval : {{marine de guerre}}
-- army : {{armée de terre}}
-- airforce : {{armée de l'air}}
local function topicTemplate(frame, topic, prefix)
	local args = Outils.extractArgs(frame)
	local templatename = Outils.validTextArg(args, 1)
	local period = Outils.validTextArg(args, 'variant', 2)
	local size = Outils.validTextArg(args, 'size')
	if not size then
		size = '22x20px'
	end
	local name = Outils.validTextArg(args, 'name')
	
	-- TODO args.usedata ne devrait pas avoir lieu d'être à terme, mais il est utile pour tester
	local success, datatable = getAnyDatatable(templatename, args.usedata)
	if not success then
		return error('Lieu non reconnu') -- FIXME comment faudrait-il gérer l'erreur ?
	end
	
	local flag = getData(datatable, 'flag', topic, period)
	local border = getData(datatable, 'flagborder', topic, period)
	border = border ~= '' and border ~= false

	local linkSuccess, link = pcall(getData, datatable, 'link', topic, period, true)
	if not linkSuccess or not link then
		link = prefix .. getData(datatable, 'name', topic, period)
	end
	local linkNameSuccess, linkName = pcall(getData, datatable, 'name', topic, period, true)
	if not linkNameSuccess or not linkName then
		linkName = name
	end
	
	return Drapeau.displayFlag(flag, {
		border = border,
		size = size,
		text = '[[' .. link .. (linkName and ('|' .. linkName) or '') .. ']]'
	})
end

-- modèle {{marine de guerre}}
function p.marine_de_guerre(frame)
	return topicTemplate(frame, 'naval', 'Marine de ')
end

-- modèle {{armée de terre}}
function p.armee_de_terre(frame)
	return topicTemplate(frame, 'army', 'Armée de ')
end

-- modèle {{armée de l'air}}
function p.armee_de_l_air(frame)
	return topicTemplate(frame, 'airforce', 'Force aérienne de ')
end

-- modèle {{drapeau}}
function p.drapeau(frame)
	local args = Outils.extractArgs(frame)
	local templatename = Outils.validTextArg(args, 1)
	local variant = Outils.validTextArg(args, 'variant', 2)
	local size = Outils.validTextArg(args, 'size', 'taille')
	if not size then
		size = '20x18'
	end
	size = size .. 'px'
	
	local topic, period = getTopicAndPeriodFromVariant(variant)
	
	-- TODO args.usedata ne devrait pas avoir lieu d'être à terme, mais il est utile pour tester
	local success, flagData = pcall(p.getFlagData, templatename, topic, period, args.usedata)
	if not success then
		flagData = {}
	end
	
	local alt = flagData.alt
	if alt == nil and flagData.name then
		alt = 'Drapeau : ' .. flagData.name
	end
	
	return Drapeau.displayFlag(flagData.flag, {
		border = flagData.border,
		size = size,
		alt = alt
	})
end

-- modèle {{getalias}}
function p.getalias(frame)
	local args = Outils.extractArgs(frame)
	local templatename = Outils.validTextArg(args, 1)
	local name = Outils.validTextArg(args, 2)
	local variant = Outils.validTextArg(args, 'variant', 3)
	
	-- TODO args.usedata ne devrait pas avoir lieu d'être à terme, mais il est utile pour tester
	local success, datatable = getAnyDatatable(templatename, args.usedata)
	if not success then
		return error('Lieu non reconnu') -- FIXME comment faudrait-il gérer l'erreur ?
	end
	
	local topic, period = getTopicAndPeriodFromVariant(variant)
	
	-- Seuls "link" (par défaut), "shortname" et "flag" sont documentés.
	if name == 'flag' then
		return getData(datatable, 'flag', topic, period)
	end
	if name == 'shortname' then
		return getData(datatable, 'name', topic, period)
	end
	return getData(datatable, 'link', topic, period)
end

function p.nationality(zone, gender, number, topic, period)
	local success, data = pcall(getDatatable, zone)
	if not success then return
		zone
	end
	
	local str = getAdjective(data, gender, number)
	if not str then
		return zone
	end
	local link = getLink(data, topic, period) 
	if link then
		str = '[[' .. link .. '|' .. str .. ']]'
	end
	return str, true-- true indique le succès
end

function p.getNationality(args) -- pour obtenir la nationalité d'une personne sur Wikidata sous forme d'adjectifs
	if not args then
		return nil
	end
	local wikidata = require "Module:Wikidata"
	local complexdate = require "Module:Date complexe"
	
	if type(args) == 'string' then -- si un seul argument, c'est l'entité à utiliser
		args = {item = args}
	end
	if args.args then -- si vient de frame
		args = args.args
	end
	local item = args.item or args[1]
	
	-- établit la variable gender pour l'élément
	local vals = {
		['Q6581072'] = 'f',
		['Q6581097'] = 'm',
		default	  = '?'
	}
	local gender = args.gender
	if not gender then
		gender = wikidata.formatStatements{entity = item, property = 'P21', displayformat = 'raw'}
		gender = vals[gender] or vals.default
	end
	-- désactivation si date de naissance avant l'Ère contemporaine : trop d'imprécisions et d'anachronismes
	local mindate = args.mindate or '1789'
	if mindate ~= "-" then
		local birthdate = wikidata.formatStatements{entity = item, property = 'P569', displayformat = 'raw', numval = 1}
                local deathdate = wikidata.formatStatements{entity = item, property = 'P570', displayformat = 'raw', numval = 1}
		if ((not birthdate) or complexdate.before(mindate, birthdate)) and ((not deathdate) or complexdate.before(mindate, deathdate)) then
			return nil
		end
	end
	return wikidata.formatStatements{
			property = 'P27',
			showdate = true,
			entity = item,
			conjtype = args.conjtype or 'and',
			removedupes = true,
			linktopic = '-',
			displayformat = 
				function(snak)
					local g = gender -- genre de la personne, pour affichage du gentilé
					if g == '?' then -- si inconnu, au masculin
						g = 'm'
					end
					local val, success = p.nationality(wikidata.getId(snak), g)
					if not success then
						val = wikidata.formatSnak(snak)
					end
					return val
				end
	}
end

return p