« Module:Wikidata » : différence entre les versions

De kookipedia
Aller à la navigation Aller à la recherche
(Page créée avec « -- vim: set noexpandtab ft=lua ts=4 sw=4: require('strict') local p = {} local debug = false ------------------------------------------------------------------------------ -- module local variables and functions local wiki = { langcode = mw.language.getContentLanguage().code } -- internationalisation local i18n = { ["errors"] = { ["property-not-found"] = "Property not found.", ["entity-not-found"] = "Wikidata entity not found.", ["unknown-claim-type... »)
 
Aucun résumé des modifications
 
Ligne 1 : Ligne 1 :
-- vim: set noexpandtab ft=lua ts=4 sw=4:
--script that retrieves basic data stored in Wikidata, for the datamodel, see https://www.mediawiki.org/wiki/Extension:Wikibase_Client/Lua
require('strict')


local p = {}
local wd = {}
local debug = false


-- creation of a subobject to store comparison funtions, used for sorting claims
-- to be able to build more complex sorts like topological sorts


------------------------------------------------------------------------------
wd.compare = {}
-- module local variables and functions


local wiki =
local databases = { }
{
local modules = { }
langcode = mw.language.getContentLanguage().code
local databasesNames = { -- modules de données statiques pouvant être appelés avec mw.loadData(), ne nécessitant pas require()
i18n = 'Module:Wikidata/I18n',
globes = 'Module:Wikidata/Globes',
langhierarchy = 'Module:Wikidata/Hiérarchie des langues',
langcodes = 'Module:Dictionnaire Wikidata/Codes langue', -- big, infrequently useda
invertedlangcodes = 'Module:Dictionnaire Wikidata/Codes langue/inversé'
}
}
 
local modulesNames = {
-- internationalisation
reference = 'Module:Wikidata/Références',
local i18n =
linguistic = 'Module:Linguistique',
{
datemodule = 'Module:Date',
["errors"] =
formatDate = 'Module:Date complexe',
{
formatNum = 'Module:Conversion',
["property-not-found"] = "Property not found.",
langmodule = 'Module:Langue',
["entity-not-found"] = "Wikidata entity not found.",
cite = 'Module:Biblio',
["unknown-claim-type"] = "Unknown claim type.",
weblink = 'Module:Weblink'
["unknown-entity-type"] = "Unknown entity type.",
["qualifier-not-found"] = "Qualifier not found.",
["site-not-found"] = "Wikimedia project not found.",
["unknown-datetime-format"] = "Unknown datetime format.",
["local-article-not-found"] = "Article is not yet available in this wiki."
},
["datetime"] =
{
-- $1 is a placeholder for the actual number
[0] = "$1 billion years", -- precision: billion years
[1] = "$100 million years", -- precision: hundred million years
[2] = "$10 million years", -- precision: ten million years
[3] = "$1 million years", -- precision: million years
[4] = "$100,000 years", -- precision: hundred thousand years
[5] = "$10,000 years", -- precision: ten thousand years
[6] = "$1 millennium", -- precision: millennium
[7] = "$1 century", -- precision: century
[8] = "$1s", -- precision: decade
-- the following use the format of #time parser function
[9]  = "Y", -- precision: year,
[10] = "F Y", -- precision: month
[11] = "F j, Y", -- precision: day
[12] = "F j, Y ga", -- precision: hour
[13] = "F j, Y g:ia", -- precision: minute
[14] = "F j, Y g:i:sa", -- precision: second
["beforenow"] = "$1 BCE", -- how to format negative numbers for precisions 0 to 5
["afternow"] = "$1 CE", -- how to format positive numbers for precisions 0 to 5
["bc"] = '$1 "BCE"', -- how print negative years
["ad"] = "$1", -- how print positive years
-- the following are for function getDateValue() and getQualifierDateValue()
["default-format"] = "dmy", -- default value of the #3 (getDateValue) or
-- #4 (getQualifierDateValue) argument
["default-addon"] = "BC", -- default value of the #4 (getDateValue) or
-- #5 (getQualifierDateValue) argument
["prefix-addon"] = false, -- set to true for languages put "BC" in front of the
-- datetime string; or the addon will be suffixed
["addon-sep"] = " ", -- separator between datetime string and addon (or inverse)
["format"] = -- options of the 3rd argument
{
["mdy"] = "F j, Y",
["my"] = "F Y",
["y"] = "Y",
["dmy"] = "j F Y",
["ymd"] = "Y-m-d",
["ym"] = "Y-m"
}
},
["monolingualtext"] = '<span lang="%language">%text</span>',
["warnDump"] = "[[Category:Called function 'Dump' from module Wikidata]]",
["ordinal"] =
{
[1] = "st",
[2] = "nd",
[3] = "rd",
["default"] = "th"
}
}
}


if wiki.langcode ~= "en" then
local function loadDatabase( t, key )
--require("Module:i18n").loadI18n("Module:Wikidata/i18n", i18n)
if databasesNames[key] then
-- got idea from [[:w:Module:Wd]]
local m = mw.loadData( databasesNames[key] )
local module_title; if ... == nil then
t[key] = m
module_title = mw.getCurrentFrame():getTitle()
return m
else
end
module_title = ...
end
local function loadModule( t, key )
if modulesNames[key] then
local m = require( modulesNames[key] )
t[key] = m
return m
end
end
require('Module:i18n').loadI18n(module_title..'/i18n', i18n)
end
end
setmetatable( databases, { __index = loadDatabase } )
setmetatable( modules, { __index = loadModule } ) -- ainsi le require() sera opéré seulement si nécessaire par modules.(nom du module)
local datequalifiers = {'P585', 'P571', 'P580', 'P582', 'P1319', 'P1326'}
-- === I18n ===
local defaultlang = mw.getContentLanguage():getCode()


-- this function needs to be internationalised along with the above:
function wd.translate(str, rep1, rep2)
-- takes cardinal numer as a numeric and returns the ordinal as a string
str = databases.i18n[str] or str
-- we need three exceptions in English for 1st, 2nd, 3rd, 21st, .. 31st, etc.
if rep1 and (type (rep1) == 'string') then
local function makeOrdinal (cardinal)
str = str:gsub('$1', rep1)
local ordsuffix = i18n.ordinal.default
end
if cardinal % 10 == 1 then
if rep2 and (type (rep2) == 'string')then
ordsuffix = i18n.ordinal[1]
str = str:gsub('$2', rep2)
elseif cardinal % 10 == 2 then
ordsuffix = i18n.ordinal[2]
elseif cardinal % 10 == 3 then
ordsuffix = i18n.ordinal[3]
end
end
-- In English, 1, 21, 31, etc. use 'st', but 11, 111, etc. use 'th'
return str
-- similarly for 12 and 13, etc.
end
if (cardinal % 100 == 11) or (cardinal % 100 == 12) or (cardinal % 100 == 13) then
 
ordsuffix = i18n.ordinal.default
local function addCat(cat, sortkey)
if sortkey then
return  '[[Category:' .. cat .. '|' .. sortkey .. ']]'
end
end
return tostring(cardinal) .. ordsuffix
return '[[Category:' .. cat  .. ']]'
end
end


local function printError(code)
local function formatError( key , category, debug)
return '<span class="error">' .. (i18n.errors[code] or code) .. '</span>'
    if debug then
        return error(databases.i18n[key] or key)
    end
    if category then
        return addCat(category, key)
    else
        return addCat('cat-unsorted-issue', key)
    end
end
end
local function parseDateFormat(f, timestamp, addon, prefix_addon, addon_sep)
 
local year_suffix
--
local tstr = ""
 
local lang_obj = mw.language.new(wiki.langcode)
function wd.isSpecial(snak)
local f_parts = mw.text.split(f, 'Y', true)
return (snak.snaktype ~= 'value')
for idx, f_part in pairs(f_parts) do
end
year_suffix = ''
 
if string.match(f_part, "x[mijkot]$") then
function wd.getId(snak)
-- for non-Gregorian year
if (snak.snaktype == 'value') then
f_part = f_part .. 'Y'
return 'Q' .. snak.datavalue.value['numeric-id']
elseif idx < #f_parts then
-- supress leading zeros in year
year_suffix = lang_obj:formatDate('Y', timestamp)
year_suffix = string.gsub(year_suffix, '^0+', '', 1)
end
tstr = tstr .. lang_obj:formatDate(f_part, timestamp) .. year_suffix
end
end
if addon ~= "" and prefix_addon then
end
return addon .. addon_sep .. tstr
 
elseif addon ~= "" then
function wd.getNumericId(snak)
return tstr .. addon_sep .. addon
if (snak.snaktype == 'value') then
else
return snak.datavalue.value['numeric-id']
return tstr
end
end
end
end
local function parseDateValue(timestamp, date_format, date_addon)
local prefix_addon = i18n["datetime"]["prefix-addon"]
local addon_sep = i18n["datetime"]["addon-sep"]
local addon = ""


-- check for negative date
function wd.getMainId(claim)
if string.sub(timestamp, 1, 1) == '-' then
return wd.getId(claim.mainsnak)
timestamp = '+' .. string.sub(timestamp, 2)
end
addon = date_addon
 
end
function wd.entityId(entity)
local _date_format = i18n["datetime"]["format"][date_format]
if type(entity) == 'string' then
if _date_format ~= nil then
return entity
return parseDateFormat(_date_format, timestamp, addon, prefix_addon, addon_sep)
elseif type(entity) == 'table' then
else
return entity.id
return printError("unknown-datetime-format")
end
end
end
end


-- This local function combines the year/month/day/BC/BCE handling of parseDateValue{}
function wd.getEntityIdForCurrentPage()
-- with the millennium/century/decade handling of formatDate()
return mw.wikibase.getEntityIdForCurrentPage()
local function parseDateFull(timestamp, precision, date_format, date_addon)
end
local prefix_addon = i18n["datetime"]["prefix-addon"]
 
local addon_sep = i18n["datetime"]["addon-sep"]
-- function that returns true if the "qid" parameter is the qid
local addon = ""
-- of the item that is linked to the calling page
function wd.isPageOfQId(qid)  
local self_id = mw.wikibase.getEntityIdForCurrentPage()
return self_id ~= nil and qid == self_id
end


-- check for negative date
function wd.getEntity( val )
if string.sub(timestamp, 1, 1) == '-' then
if type(val) == 'table' then
timestamp = '+' .. string.sub(timestamp, 2)
return val
addon = date_addon
end
end
if val == '-' then
return nil
end
if val == '' then
val = nil
end
return mw.wikibase.getEntity(val)
end


-- get the next four characters after the + (should be the year now in all cases)
function wd.splitStr(val) -- transforme en table les chaînes venant du Wikitexte qui utilisent des virgules de séparation
-- ok, so this is dirty, but let's get it working first
if type(val) == 'string' then
local intyear = tonumber(string.sub(timestamp, 2, 5))
val = mw.text.split(val, ",")
if intyear == 0 and precision <= 9 then
return ""
end
end
return val
end


-- precision is 10000 years or more
function wd.isHere(searchset, val, matchfunction)
if precision <= 5 then
for i, j in pairs(searchset) do
local factor = 10 ^ ((5 - precision) + 4)
if matchfunction then
local y2 = math.ceil(math.abs(intyear) / factor)
if matchfunction(val,j) then
local relative = mw.ustring.gsub(i18n.datetime[precision], "$1", tostring(y2))
return true
if addon ~= "" then
end
-- negative date
relative = mw.ustring.gsub(i18n.datetime.beforenow, "$1", relative)
else
else
relative = mw.ustring.gsub(i18n.datetime.afternow, "$1", relative)
if val == j then
return true
end
end
end
return relative
end
end
return false
end


-- precision is decades (8), centuries (7) and millennia (6)
 
local era, card
local function wikidataLink(entity)
if precision == 6 then
local name =':d:'
card = math.floor((intyear - 1) / 1000) + 1
era = mw.ustring.gsub(i18n.datetime[6], "$1", makeOrdinal(card))
if type(entity) == 'string' then
if entity:match("P[0-9]+") then
entity = "Property:" .. entity
end
return name .. entity
elseif type(entity) == 'table' then
if entity["type"] == "property" then
name = ":d:Property:"
end
return name .. entity.id
elseif type(entity) == nil then
return formatError('entity-not-found')
end
end
if precision == 7 then
end
card = math.floor((intyear - 1) / 100) + 1
 
era = mw.ustring.gsub(i18n.datetime[7], "$1", makeOrdinal(card))
function wd.siteLink(entity, project, lang)
-- returns 3 values: a sitelink (with the relevant prefix) a project name and a language
lang = lang or defaultlang
if (type(project) ~= 'string') then
project = 'wiki'
end
end
if precision == 8 then
project = project:lower()
era = mw.ustring.gsub(i18n.datetime[8], "$1", tostring(math.floor(math.abs(intyear) / 10) * 10))
if project == 'wikipedia' then
project = 'wiki'
end
end
if era then
if type(entity) == 'string' and (project == 'wiki') and ( (not lang or lang == defaultlang) ) then -- évite de charger l'élément entier
if addon ~= "" then
local link = mw.wikibase.getSitelink(entity)
era = mw.ustring.gsub(mw.ustring.gsub(i18n.datetime.bc, '"', ""), "$1", era)
if link then
else
local test_redirect = mw.title.new(link) -- remplacement des redirections (retirer si trop coûteux)
era = mw.ustring.gsub(mw.ustring.gsub(i18n.datetime.ad, '"', ""), "$1", era)
if test_redirect.isRedirect and test_redirect.redirectTarget then
link = test_redirect.redirectTarget.fullText
end
end
end
return era
return link, 'wiki', defaultlang
end
if project == 'wikidata' then
return wikidataLink(entity), 'wikidata'
end
end
local projects = {
-- nom = {préfixe sur Wikidata, préfix pour les liens sur Wikipédia, ajouter préfixe de langue}
wiki = {'wiki', nil, true}, -- wikipedia
commons = {'commonswiki', 'commons', false},
commonswiki = {'commonswiki', 'commons', false},
wikiquote = {'wikiquote', 'q', true},
wikivoyage = {'wikivoyage', 'voy', true},
wikibooks = {'wikibooks', 'b', true},
wikinews = {'wikinews', 'n', true},
wikiversity = {'wikiversity', 'v', true},
wikisource = {'wikisource', 's', true},
wiktionary = {'wiktionary', 'wikt', true},
specieswiki = {'specieswiki', 'species', false},
metawiki = {'metawiki', 'm', false},
incubator = {'incubator', 'incubator', false},
outreach = {'outreach', 'outreach', false},
mediawiki = {'mediawiki', 'mw', false}
}
local entityid = entity.id or entity


local _date_format = i18n["datetime"]["format"][date_format]
local projectdata = projects[project:lower()]
if _date_format ~= nil then
if not projectdata then -- defaultlink might be in the form "dewiki" rather than "project: 'wiki', lang: 'de' "
-- check for precision is year and override supplied date_format
for k, v in pairs(projects) do
if precision == 9 then
if project:match( k .. '$' )
_date_format = i18n["datetime"][9]
and mw.language.isKnownLanguageTag(project:sub(1, #project-#k))
then
lang = project:sub(1, #project-#k)
project = project:sub(#lang + 1, #project)
projectdata = projects[project]
break
end
end
end
return parseDateFormat(_date_format, timestamp, addon, prefix_addon, addon_sep)
if not mw.language.isKnownLanguageTag(lang) then
return --formatError('invalid-project-code', projet or 'nil')
end
end
if not projectdata then
return -- formatError('invalid-project-code', projet or 'nil')
end
local linkcode = projectdata[1]
local prefix = projectdata[2]
local multiversion = projectdata[3]
if multiversion then
linkcode = lang .. linkcode
end
local link = mw.wikibase.getSitelink(entityid, linkcode)
if not link then
return nil
end
if prefix then
link = prefix .. ':' .. link
end
if multiversion then
link = ':' .. lang .. ':' .. link
end
return link, project, lang
end
 
-- add new values to a list, avoiding duplicates
function wd.addNewValues(olditems, newitems, maxnum, stopval)
if not newitems then
return olditems
end
for _, qid in pairs(newitems) do
if stopval and (qid == stopval) then
table.insert(olditems, qid)
return olditems
end
if maxnum and (#olditems >= maxnum) then
return olditems
end
if not wd.isHere(olditems, qid) then
table.insert(olditems, qid)
end
end
return olditems
end
 
--=== FILTER CLAIMS ACCORDING TO VARIOUS CRITERIA : FUNCTION GETCLAIMS et alii ===
 
local function notSpecial(claim)
local type
if claim.mainsnak ~= nil then
type = claim.mainsnak.snaktype
else
else
return printError("unknown-datetime-format")
-- condition respectée quand showonlyqualifier est un paramètre renseigné
-- dans ce cas, claim n'est pas une déclaration entière, mais UNE snak qualifiée du main snak
type = claim.snaktype
end
end
return type == 'value'
end
local function hasTargetValue(claim, targets) -- retourne true si la valeur est dans la liste des target, ou si c'est une valeur spéciale filtrée séparément par excludespecial
local id = wd.getMainId(claim)
local targets = wd.splitStr(targets)
return wd.isHere(targets, id) or wd.isSpecial(claim.mainsnak)
end
local function excludeValues(claim, values) -- true si la valeur n'est pas dans la liste, ou si c'est une valeur spéciale (filtrée à part par excludespecial)
return wd.isSpecial(claim.mainsnak) or not ( hasTargetValue(claim, values) )
end
local function hasTargetClass(claim, targets, maxdepth) -- retourne true si la valeur est une instance d'une classe dans la liste des target, ou si c'est une valeur spéciale filtrée séparément par excludespecial
local id = wd.getMainId(claim)
local targets = wd.splitStr(targets)
local maxdepth = maxdepth or 10
local matchfunction = function(value, target) return wd.isInstance(target, value, maxdepth) end
return wd.isHere(targets, id, matchfunction) or wd.isSpecial(claim.mainsnak)
end
end


-- the "qualifiers" and "snaks" field have a respective "qualifiers-order" and "snaks-order" field
local function excludeClasses(claim, classes) -- true si la valeur n'est pas une instance d'une classe dans la liste, ou si c'est une valeur spéciale (filtrée à part par excludespecial)
-- use these as the second parameter and this function instead of the built-in "pairs" function
return wd.isSpecial(claim.mainsnak) or not ( hasTargetClass(claim, classes, maxdepth) )
-- to iterate over all qualifiers and snaks in the intended order.
end
local function orderedpairs(array, order)
if not order then return pairs(array) end


-- return iterator function
local function hasTargetSuperclass(claim, targets, maxdepth) -- retourne true si la valeur est une sous-classe d'une classe dans la liste des target, ou si c'est une valeur spéciale filtrée séparément par excludespecial
local i = 0
local id = wd.getMainId(claim)
return function()
local targets = wd.splitStr(targets)
i = i + 1
local maxdepth = maxdepth or 10
if order[i] then
local matchfunction = function(value, target) return wd.isSubclass(target, value, maxdepth) end
return order[i], array[order[i]]
return wd.isHere(targets, id, matchfunction) or wd.isSpecial(claim.mainsnak)
end
 
local function excludeSuperclasses(claim, classes) -- true si la valeur n'est pas une sous-classe d'une classe dans la liste, ou si c'est une valeur spéciale (filtrée à part par excludespecial)
return wd.isSpecial(claim.mainsnak) or not ( hasTargetSuperclass(claim, classes, maxdepth) )
end
 
 
local function bestRanked(claims)
if not claims then
return nil
end
local preferred, normal = {}, {}
for i, j in pairs(claims) do
if j.rank == 'preferred' then
table.insert(preferred, j)
elseif j.rank == 'normal' then
table.insert(normal, j)
end
end
end
if #preferred > 0 then
return preferred
else
return normal
end
end
end
end


-- precision: 0 - billion years, 1 - hundred million years, ..., 6 - millennia, 7 - century, 8 - decade, 9 - year, 10 - month, 11 - day, 12 - hour, 13 - minute, 14 - second
local function withRank(claims, target)
local function normalizeDate(date)
if target == 'best' then
date = mw.text.trim(date, "+")
return bestRanked(claims)
-- extract year
end
local yearstr = mw.ustring.match(date, "^\-?%d+")
local newclaims = {}
local year = tonumber(yearstr)
for pos, claim in pairs(claims)  do
-- remove leading zeros of year
if target == 'valid' then
return year .. mw.ustring.sub(date, #yearstr + 1), year
if claim.rank ~= 'deprecated' then
table.insert(newclaims, claim)
end
elseif claim.rank == target then
table.insert(newclaims, claim)
end
end
return newclaims
end
end


local function formatDate(date, precision, timezone)
function wd.hasQualifier(claim, acceptedqualifs, acceptedvals, excludequalifiervalues)
precision = precision or 11
local claimqualifs = claim.qualifiers
local date, year = normalizeDate(date)
if year == 0 and precision <= 9 then return "" end
if (not claimqualifs) then
return false
end
 
acceptedqualifs = wd.splitStr(acceptedqualifs)
acceptedvals = wd.splitStr( acceptedvals)
 


-- precision is 10000 years or more
local function ok(qualif) -- vérification pour un qualificatif individuel
if precision <= 5 then
if not claimqualifs[qualif] then
local factor = 10 ^ ((5 - precision) + 4)
return false
local y2 = math.ceil(math.abs(year) / factor)
end
local relative = mw.ustring.gsub(i18n.datetime[precision], "$1", tostring(y2))
if not (acceptedvals) then  -- si aucune valeur spécifique n'est demandée, OK
if year < 0 then
return true
relative = mw.ustring.gsub(i18n.datetime.beforenow, "$1", relative)
end
else
for i, wanted in pairs(acceptedvals) do
relative = mw.ustring.gsub(i18n.datetime.afternow, "$1", relative)
for j, actual in pairs(claimqualifs[qualif]) do
if wd.getId(actual) == wanted then
return true
end
end
end
end
return relative
end
end


-- precision is decades, centuries and millennia
for i, qualif in pairs(acceptedqualifs) do
local era
if ok(qualif) then
if precision == 6 then era = mw.ustring.gsub(i18n.datetime[6], "$1", tostring(math.floor((math.abs(year) - 1) / 1000) + 1)) end
return true
if precision == 7 then era = mw.ustring.gsub(i18n.datetime[7], "$1", tostring(math.floor((math.abs(year) - 1) / 100) + 1)) end
end
if precision == 8 then era = mw.ustring.gsub(i18n.datetime[8], "$1", tostring(math.floor(math.abs(year) / 10) * 10)) end
if era then
if year < 0 then era = mw.ustring.gsub(mw.ustring.gsub(i18n.datetime.bc, '"', ""), "$1", era)
elseif year > 0 then era = mw.ustring.gsub(mw.ustring.gsub(i18n.datetime.ad, '"', ""), "$1", era) end
return era
end
end
return false
end


-- precision is year
function wd.hasQualifierNumber(claim, acceptedqualifs, acceptedvals, excludequalifiervalues)
if precision == 9 then
local claimqualifs = claim.qualifiers
return year
if (not claimqualifs) then
return false
end
end


-- precision is less than years
acceptedqualifs = wd.splitStr(acceptedqualifs)
if precision > 9 then
acceptedvals = wd.splitStr( acceptedvals)
--[[ the following code replaces the UTC suffix with the given negated timezone to convert the global time to the given local time
 
timezone = tonumber(timezone)
 
if timezone and timezone ~= 0 then
local function ok(qualif) -- vérification pour un qualificatif individuel
timezone = -timezone
if not claimqualifs[qualif] then
timezone = string.format("%.2d%.2d", timezone / 60, timezone % 60)
return false
if timezone[1] ~= '-' then timezone = "+" .. timezone end
end
date = mw.text.trim(date, "Z") .. " " .. timezone
if not (acceptedvals) then  -- si aucune valeur spécifique n'est demandée, OK
return true
end
end
]]--
for i, wanted in pairs(acceptedvals) do
for j, actual in pairs(claimqualifs[qualif]) do
if mw.wikibase.renderSnak(actual) == wanted then
return true
end
end
end
end


local formatstr = i18n.datetime[precision]
for i, qualif in pairs(acceptedqualifs) do
if year == 0 then formatstr = mw.ustring.gsub(formatstr, i18n.datetime[9], "")
if ok(qualif) then
elseif year < 0 then
return true
-- Mediawiki formatDate doesn't support negative years
date = mw.ustring.sub(date, 2)
formatstr = mw.ustring.gsub(formatstr, i18n.datetime[9], mw.ustring.gsub(i18n.datetime.bc, "$1", i18n.datetime[9]))
elseif year > 0 and i18n.datetime.ad ~= "$1" then
formatstr = mw.ustring.gsub(formatstr, i18n.datetime[9], mw.ustring.gsub(i18n.datetime.ad, "$1", i18n.datetime[9]))
end
end
return mw.language.new(wiki.langcode):formatDate(formatstr, date)
end
end
return false
end
end


local function printDatavalueEntity(data, parameter)
local function hasSource(claim, targetsource, sourceproperty)
-- data fields: entity-type [string], numeric-id [int, Wikidata id]
sourceproperty = sourceproperty or 'P248'
local id
if targetsource == "-" then
return true
end
if (not claim.references) then return
false
end
local candidates = claim.references[1].snaks[sourceproperty] -- les snaks utilisant la propriété demandée
if (not candidates) then
return false
end
if (targetsource == "any") then -- si n'importe quelle valeur est acceptée tant qu'elle utilise en ref la propriété demandée
return true
end
targetsource = wd.splitStr(targetsource)
for _, source in pairs(candidates) do
local s = wd.getId(source)
for i, target in pairs(targetsource) do
if s == target then return true end
end
end
return false
end
 
local function excludeQualifier(claim, qualifier, qualifiervalues)
return not wd.hasQualifier(claim, qualifier, qualifiervalues)
end
 
function wd.hasDate(claim)
if not claim then
return false --error() ?
end
if wd.getDateFromQualif(claim, 'P585') or wd.getDateFromQualif(claim, 'P580') or wd.getDateFromQualif(claim, 'P582') then
return true
end
return false
end


if data["entity-type"] == "item" then id = "Q" .. data["numeric-id"]
local function hasLink(claim, site, lang)
elseif data["entity-type"] == "property" then id = "P" .. data["numeric-id"]
if (claim.mainsnak.snaktype ~= 'value') then -- ne pas supprimer les valeurs spéciales, il y a une fonction dédiée pour ça
else return printError("unknown-entity-type")
return true
end
local id = wd.getMainId(claim)
local link = wd.siteLink(id, site, lang)
if link then
return true
end
end
end


if parameter then
local function isInLanguage(claim, lang) -- ne fonctionne que pour les monolingualtext / étendre aux autres types en utilisant les qualifiers ?
if parameter == "link" then
if type(lang) == 'table' then -- si c'est une table de language séparées par des virgules, on les accepte toutes
local linkTarget = mw.wikibase.getSitelink(id)
for i, l in pairs(lang) do
local linkName = mw.wikibase.getLabel(id)
local v = isInLanguage(claim, l)
if linkTarget then
if v then
-- if there is a local Wikipedia article link to it using the label or the article title
return true
return "[[" .. linkTarget .. "|" .. (linkName or linkTarget) .. "]]"
else
-- if there is no local Wikipedia article output the label or link to the Wikidata object to let the user input a proper label
if linkName then return linkName else return "[[:d:" .. id .. "|" .. id .. "]]" end
end
end
end
end
if type(lang) ~= ('string') then
return --?
end
if (lang == '-') then
return true
end
if (lang == 'locallang') then
lang =  mw.getContentLanguage():getCode()
end
-- pour les monolingual text
local snak = claim.mainsnak or claim
if snak.snaktype == 'value' and snak.datavalue.type == 'monolingualtext' then
if snak.datavalue.value.language == lang then
return true
end
return false
end
-- pour les autres types de données : recherche dans les qualificatifs
if (lang == 'fr') then
lang = 'Q150'
elseif (lang == 'en') then
lang = 'Q1860'
else
lang = databases.invertedlangcodes[lang]
end
if claim.qualifiers and claim.qualifiers.P407 then
if wd.hasQualifier(claim, {'P407'}, {lang}) then
return true
else
else
return data[parameter]
return false
end
end
 
return true -- si on ne ne sait pas la langue, on condière que c'est bon
end
 
local function firstVals(claims, numval) -- retourn les numval premières valeurs de la table claims
    local numval = tonumber(numval) or 0 -- raise a error if numval is not a positive integer ?
    if not claims then
    return nil
    end
    while (#claims > numval) do
    table.remove(claims)
    end
    return claims
end
 
local function lastVals(claims, numval2) -- retourn les valeurs de la table claims à partir de numval2
    local numval2 = tonumber(numval2) or 0 -- raise a error if numval is not a positive integer ?
    if not claims then
    return nil
    end
    for i=1,numval2 do
    table.remove(claims, 1)
    end
    return claims
end
 
-- retourne les valeurs de la table claims à partir de removedupesdate,
-- sans les dates en doublons avec conversion entre les calendrier julien et grégorien,
-- ou uniquement en catégorisant si le paramètre removedupesdate est égale à 'cat'
local function removeDupesDate(claims, removedupesdate)
if not claims or #claims < 2 then
return claims, ''
end
local cat = ''
local newClaims = {}
local newIsos = {}
 
local function findIndex(searchset, val) -- similaire à wd.isHere mais retourne l'index de la valeur trouvée
for i, j in pairs(searchset) do
if val == j then
return i
end
end
return -1
end
 
for _, claim in ipairs( claims ) do
local snak = claim.mainsnak or claim
if (snak.snaktype == 'value') and (snak.datatype == 'time') and snak.datavalue.value.precision >= 11 then -- s'il s'agit d'un time et que la précision est au moins l'année
local iso = snak.datavalue.value.time
_, _, iso = string.find(iso, "(+%d+-%d+-%d+T)")
local deleteIfDuplicate = false
if snak.datavalue.value.calendarmodel == 'http://www.wikidata.org/entity/Q1985727' then -- si la date est grégorienne
if modules.formatDate.before('+1582', iso) then -- si avant 1582 on calcule la date julienne
_, _, y, m, d = string.find(iso, "+(%d+)-(%d+)-(%d+)T")
y, m , d = modules.datemodule.gregorianToJulian(y, m , d)
if m < 10 then m = '0' .. m end
if d < 10 then d = '0' .. d end
iso = '+' .. y .. '-' .. m .. '-' .. d .. 'T'
deleteIfDuplicate = true
end
local index = findIndex(newIsos, iso)
if index >= 0 then -- si la date est déjà présente
cat = cat .. '[[Catégorie:Article avec des dates identiques venant de wikidata dans le code de l\'infobox]]'
if removedupesdate == "cat" then -- ne faire que catégoriser
table.insert(newIsos, iso)
table.insert(newClaims, claim)
elseif not deleteIfDuplicate then -- supprimer l'autre date si la date courante n'a pas été convertie
newClaims[index] = claim
end -- sinon supprimer la date courante
else -- pas de doublon
table.insert(newIsos, iso)
table.insert(newClaims, claim)
end
elseif snak.datavalue.value.calendarmodel == 'http://www.wikidata.org/entity/Q1985786' then -- si date julienne
if not modules.formatDate.before('+1582', iso) then -- si après 1582 on calcule la date grégorienne
_, _, y, m, d = string.find(iso, "+(%d+)-(%d+)-(%d+)T")
y, m , d = modules.datemodule.julianToGregorian(y, m , d)
if m < 10 then m = '0' .. m end
if d < 10 then d = '0' .. d end
iso = '+' .. y .. '-' .. m .. '-' .. d .. 'T'
deleteIfDuplicate = true
end
local index = findIndex(newIsos, iso)
if index >= 0 then -- si date déjà présente
cat = cat .. '[[Catégorie:Article avec des dates identiques venant de wikidata dans le code de l\'infobox]]'
if removedupesdate == "cat" then -- ne faire que catégoriser
table.insert(newIsos, iso)
table.insert(newClaims, claim)
elseif not deleteIfDuplicate then -- supprimer l'autre date si la date courante n'a pas été convertie
newClaims[index] = claim
end -- sinon supprimer la date courante
else -- pas de doublon
table.insert(newIsos, iso)
table.insert(newClaims, claim)
end
else -- autre calendrier
table.insert(newIsos, iso)
table.insert(newClaims, claim)
end
else -- précision insuffisante
table.insert(newIsos, iso)
table.insert(newClaims, claim)
end
end
else
return mw.wikibase.getLabel(id) or id
end
end
return newClaims, cat
end
end


local function printDatavalueTime(data, parameter)
local function timeFromQualifs(claim, qualifs)
-- data fields: time [ISO 8601 time], timezone [int in minutes], before [int], after [int], precision [int], calendarmodel [wikidata URI]
local claimqualifs = claim.qualifiers
--  precision: 0 - billion years, 1 - hundred million years, ..., 6 - millennia, 7 - century, 8 - decade, 9 - year, 10 - month, 11 - day, 12 - hour, 13 - minute, 14 - second
if not claimqualifs then
--  calendarmodel: e.g. http://www.wikidata.org/entity/Q1985727 for the proleptic Gregorian calendar or http://www.wikidata.org/wiki/Q11184 for the Julian calendar]
return nil
if parameter then
end
if parameter == "calendarmodel" then data.calendarmodel = mw.ustring.match(data.calendarmodel, "Q%d+") -- extract entity id from the calendar model URI
for i, qualif in ipairs(qualifs or datequalifiers) do
elseif parameter == "time" then data.time = normalizeDate(data.time) end
local vals = claimqualifs[qualif]
return data[parameter]
if vals and (vals[1].snaktype == 'value') then
else
return vals[1].datavalue.value.time, vals[1].datavalue.value.precision
return formatDate(data.time, data.precision, data.timezone)
end
end
end
end
end


local function printDatavalueMonolingualText(data, parameter)
local function atDate(claim, mydate)  
-- data fields: language [string], text [string]
if mydate == "today" then
if parameter then
mydate = os.date("!%Y-%m-%dT%TZ")
return data[parameter]
end
-- determines required precision depending on the atdate format
local d = mw.text.split(mydate, "-")
 
local myprecision
if d[3] then
myprecision =  11 -- day
elseif d[2] then
myprecision = 10 -- month
else
else
local result = mw.ustring.gsub(mw.ustring.gsub(i18n.monolingualtext, "%%language", data["language"]), "%%text", data["text"])
myprecision = 9 -- year
return result
end
 
-- with point in time
local d, storedprecision = timeFromQualifs(claim, {'P585'})
if d then
return modules.formatDate.equal(mydate, d, math.min(myprecision, storedprecision))
end
-- with start or end date -- TODO: precision
local mindate = timeFromQualifs(claim, {'P580'})
local maxdate = timeFromQualifs(claim, {'P582'})
if modules.formatDate.before(mydate, mindate) and  modules.formatDate.before(maxdate, mydate) then
return true
end
end
return false
end
end


local function findClaims(entity, property)
local function check(claim, condition)
if not property or not entity or not entity.claims then return end
if type(condition) == 'function' then -- cas standard
return condition(claim)
end
return formatError('invalid type', 'function', type(condition))
end


if mw.ustring.match(property, "^P%d+$") then
local function minPrecision(claim, minprecision)
-- if the property is given by an id (P..) access the claim list by this id
local snak
return entity.claims[property]
if claim.qualifiers then -- si une date est donnée en qualificatif, c'est elle qu'on utilise de préférence au mainsnak
else
for i, j in ipairs(datequalifiers) do
property = mw.wikibase.resolvePropertyId(property)
if claim.qualifiers[j] then
if not property then return end
snak = claim.qualifiers[j][1]
break
end
end
end
if not snak then
snak = claim.mainsnak or claim
end
if (snak.snaktype == 'value') and (snak.datatype == 'time') and (snak.datavalue.value.precision < minprecision) then
return false
end
return true
end


return entity.claims[property]
function wd.sortClaims(claims, sorttype)
if not claims then
return nil
end
end
if wd.isHere({'chronological', 'order', 'inverted', 'age', 'ageinverted'}, sorttype) then
return wd.chronoSort(claims, sorttype)
elseif sorttype == 'ascending' then
return wd.quantitySort(claims)
elseif sorttype == 'descending' then
return wd.quantitySort(claims, true)
elseif type(sorttype) == 'function' then
table.sort(claims, sorttype)
return claims
elseif type(sorttype) == 'string' and sorttype:sub(1, 1) == 'P' then
return wd.numericPropertySort(claims, sorttype)
end
return claims
end
end


local function getSnakValue(snak, parameter)
function wd.filterClaims(claims, args) --retire de la tables de claims celles qui sont éliminés par un des filters de la table des filters
if snak.snaktype == "value" then
 
-- call the respective snak parser
local function filter(condition, filterfunction, funargs)
if snak.datavalue.type == "string" then return snak.datavalue.value
if not args[condition] then
elseif snak.datavalue.type == "globecoordinate" then return printDatavalueCoordinate(snak.datavalue.value, parameter)
return
elseif snak.datavalue.type == "quantity" then return printDatavalueQuantity(snak.datavalue.value, parameter)
elseif snak.datavalue.type == "time" then return printDatavalueTime(snak.datavalue.value, parameter)
elseif snak.datavalue.type == "wikibase-entityid" then return printDatavalueEntity(snak.datavalue.value, parameter)
elseif snak.datavalue.type == "monolingualtext" then return printDatavalueMonolingualText(snak.datavalue.value, parameter)
end
end
for i = #claims, 1, -1 do
if not( filterfunction(claims[i], args[funargs[1]], args[funargs[2]], args[funargs[3]]) ) then
table.remove(claims, i)
end
end
end
filter('isinlang', isInLanguage, {'isinlang'} )
filter('excludespecial', notSpecial, {} )
filter('condition', check, {'condition'} )
if claims[1] and claims[1].mainsnak then
filter('targetvalue', hasTargetValue, {'targetvalue'} )
filter('targetclass', hasTargetClass, {'targetclass'} )
filter('targetsuperclass', hasTargetSuperclass, {'targetsuperclass'} )
filter('atdate', atDate, {'atdate'} )
filter('qualifier', wd.hasQualifier, {'qualifier', 'qualifiervalue'} )
filter('qualifiernumber', wd.hasQualifierNumber, {'qualifiernumber', 'qualifiernumbervalue'} )
filter('excludequalifier', excludeQualifier, {'excludequalifier', 'excludequalifiervalue'} )
filter('withsource', hasSource, {'withsource', 'sourceproperty'} )
filter('withdate', wd.hasDate, {} )
filter('excludevalues', excludeValues, {'excludevalues'})
filter('excludeclasses', excludeClasses, {'excludeclasses'})
filter('excludesuperclasses', excludeSuperclasses, {'excludesuperclasses'})
filter('withlink', hasLink, {'withlink', 'linklang'} )
filter('minprecision', minPrecision, {'minprecision'} )
claims = withRank(claims, args.rank or 'best')
end
if #claims == 0 then
return nil
end
end
return mw.wikibase.renderSnak(snak)
if args.sorttype then
claims = wd.sortClaims(claims, args.sorttype)
end
if args.numval2 then
claims = lastVals(claims, args.numval2)
end
if args.numval then
claims = firstVals(claims, args.numval)
end
return claims
 
end
end


local function getQualifierSnak(claim, qualifierId)
function wd.loadEntity(entity, cache)
-- a "snak" is Wikidata terminology for a typed key/value pair
if type(entity) ~= 'table' then
-- a claim consists of a main snak holding the main information of this claim,
if cache then
-- as well as a list of attribute snaks and a list of references snaks
if not cache[entity] then
if qualifierId then
cache[entity] = mw.wikibase.getEntity(entity)
-- search the attribute snak with the given qualifier as key
mw.log("cached")
if claim.qualifiers then
    end
local qualifier = claim.qualifiers[qualifierId]
return cache[entity]
if qualifier then return qualifier[1] end
        else
if entity == '' or (entity == '-') then
entity = nil
end
        return mw.wikibase.getEntity(entity)
        end
    else
    return entity
    end
end
 
 
function wd.getClaims( args ) -- returns a table of the claims matching some conditions given in args
if args.claims then -- if claims have already been set, return them
return args.claims
end
local properties = args.property
if type(properties) == 'string' then
properties = wd.splitStr(string.upper(args.property))
end
if not properties then
return formatError( 'property-param-not-provided' )
end
 
--Get entity
local entity = args.entity
if type(entity) == 'string' then
if entity == '' then
entity = nil
end
end
return nil, printError("qualifier-not-found")
elseif type(entity) == 'table' then
entity = entity.id
end
        if (not entity) then
            entity = mw.wikibase.getEntityIdForCurrentPage()
        end
if (not entity) or (entity == '-') or (entity == wd.translate('somevalue')) or (entity == modules.linguistic.ucfirst(wd.translate('somevalue'))) then
return nil
end
if args.labelformat and args.labelformat == 'gendered' then
local longgender = {m = 'male', f = 'female'}
args.labelformat = longgender[wd.getgender(entity)]
end
local claims = {}
 
if #properties == 1 then
claims = mw.wikibase.getAllStatements(entity, properties[1]) -- do not use mw.wikibase.getBestStatements at this stage, as it may remove the best ranked values that match other criteria in the query
else
else
-- otherwise return the main snak
for i, prop in ipairs(properties) do
return claim.mainsnak
local newclaims = mw.wikibase.getAllStatements(entity, prop)
if newclaims and #newclaims > 0 then
for j, claim in ipairs(newclaims) do
table.insert(claims, claim)
end
end
end
end
end
if (not claims) or (#claims == 0) then
return nil
end
return wd.filterClaims(claims, args)
end
end


local function getValueOfClaim(claim, qualifierId, parameter)
--=== ENTITY FORMATTING ===
local error
 
local snak
function wd.getLabel(entity, lang1, lang2)
snak, error = getQualifierSnak(claim, qualifierId)
if snak then
if (not entity) then
return getSnakValue(snak, parameter)
return nil -- ou option de gestion des erreurs ?
end
entity = entity.id or ( type(entity) == "string" and entity)
if not(type(entity) == 'string') then return nil end
lang1 = lang1 or defaultlang
local str, lang --str : texte rendu, lang : langue de celui-ci
if lang1 == defaultlang then -- le plus économique
str, lang = mw.wikibase.getLabelWithLang(entity) -- le libellé peut être en français ou en anglais
else
else
return nil, error
str = mw.wikibase.getLabelByLang(entity, lang1)
if str then lang = lang1 end
end
if str and (lang == lang1 or lang == "mul") then --pas de catégorie "à traduire" si on a obtenu un texte dans la langue désirée (normalement fr) ou multilingue
return str
end
if lang2 then -- langue secondaire, avec catégorie "à traduire"
str2 = mw.wikibase.getLabelByLang(entity, lang2)
if str2 then
lang = lang2
str = str2
end
end
if not str then --si ni lang1, ni lang2 ni l'anglais ne sont présents,  parcours de la hiérarchie des langues
for _, trylang in ipairs(databases.langhierarchy.codes) do
str = mw.wikibase.getLabelByLang(entity, trylang)
if str then
lang = trylang
break
end
end
end
if str then
local translationCat = databases.i18n['to translate']
translationCat = translationCat .. (databases.langhierarchy.cattext[lang] or '')
translationCat = addCat(translationCat)
return str, translationCat
end
end
end
end


local function getReferences(frame, claim)
function wd.formatEntity( entity, params )
local result = ""
 
-- traverse through all references
if (not entity) then
for ref in pairs(claim.references or {}) do
return nil --formatError('entity-not-found')
local refparts
end
-- traverse through all parts of the current reference
local id = entity
for snakkey, snakval in orderedpairs(claim.references[ref].snaks or {}, claim.references[ref]["snaks-order"]) do
if type(id) == 'table' then
if refparts then refparts = refparts .. ", " else refparts = "" end
id = id.id
-- output the label of the property of the reference part, e.g. "imported from" for P143
end
refparts = refparts .. tostring(mw.wikibase.getLabel(snakkey)) .. ": "
 
-- output all values of this reference part, e.g. "German Wikipedia" and "English Wikipedia" if the referenced claim was imported from both sites
params = params or {}
for snakidx = 1, #snakval do
local lang = params.lang or defaultlang
if snakidx > 1 then refparts = refparts .. ", " end
local speciallabels = params.speciallabels
refparts = refparts .. getSnakValue(snakval[snakidx])
local displayformat = params.displayformat
local labelformat = params.labelformat
local labelformat2 = params.labelformat2
local defaultlabel = params.defaultlabel or id
local linktype = params.link
local defaultlink = params.defaultlink
local defaultlinkquery = params.defaultlinkquery
 
if speciallabels and speciallabels[id] then --speciallabels override the standard label + link combination
return speciallabels[id]
end
if params.displayformat == 'raw' then
return id
end
if params.labelformat == 'male' then
labelformat = function(objectid) return wd.genderedlabel(objectid, 'm') end
end
if params.labelformat == 'female' then
labelformat = function(objectid) return wd.genderedlabel(objectid, 'f') end
end
 
local label, translationCat
if type(labelformat) == 'function' then -- sert à des cas particuliers
label, translationCat = labelformat(entity)
end
if not label then
label, translationCat = wd.getLabel(entity, lang, params.wikidatalang)
end
 
if type(labelformat2) == 'function' and label then -- sert à des cas particuliers
label = labelformat2(label)
end
translationCat = translationCat or "" -- sera toujours ajoutée au résultat mais sera vide si la catégorie de maintenance n'est pas nécessaire
if not label then
if (defaultlabel == '-') then
return nil
end
local link = wd.siteLink(id, 'wikidata')
return '[[' .. link .. '|' .. id .. ']]' .. translationCat
-- si pas de libellé, on met un lien vers Wikidata pour qu'on comprenne à quoi ça fait référence
end
 
-- détermination du fait qu'on soit ou non en train de rendre l'élément sur la page de son article
local rendering_entity_on_its_page = wd.isPageOfQId(id)
if (linktype == '-') or rendering_entity_on_its_page then
return label .. translationCat
end
 
local link = wd.siteLink(entity, linktype, lang)
 
-- defaultlinkquery will try to link to another page on this Wiki
if (not link) and defaultlinkquery then
if type(defaultlinkquery) == 'string' then
defaultlinkquery = {property = defaultlinkquery}
end
defaultlinkquery.excludespecial = true
defaultlinkquery.entity = entity
local claims = wd.getClaims(defaultlinkquery)
if claims then
for i, j in pairs(claims) do
local id = wd.getMainId(j)
link = wd.siteLink(id, linktype, lang)
if link then
break
end
end
end
end
end
if refparts then result = result .. frame:extensionTag("ref", refparts) end
end
end
return result
 
if link then
if link:match('^Category:') or link:match('^Catégorie:') then -- attention, le « é » est multibyte
-- lier vers une catégorie au lieu de catégoriser
link = ':' .. link
end
return '[[' .. link .. '|' .. label .. ']]' .. translationCat
end
 
-- if not link, you can use defaultlink: a sidelink to another Wikimedia project
if (not defaultlink) then
defaultlink = {'enwiki'}
end
if defaultlink and (defaultlink ~= '-') then
local linktype
local sidelink, site, langcode
if type(defaultlink) == 'string' then
defaultlink = {defaultlink}
end
for i, j in ipairs(defaultlink) do
sidelink, site, langcode = wd.siteLink(entity, j, lang)
if sidelink then
break
end
end
if not sidelink then
sidelink, site = wd.siteLink(entity, 'wikidata')
end
local icon, class, title = site, nil, nil -- le texte affiché du lien
if site == 'wiki' then
icon, class, title = langcode, "indicateur-langue", wd.translate('see-another-language', mw.language.fetchLanguageName(langcode, defaultlang))
elseif site == 'wikidata' then
icon, class, title = 'd',  "indicateur-langue", wd.translate('see-wikidata')
else
title = wd.translate('see-another-project', site)
end
local val = '[[' .. sidelink .. '|' .. '<span class = "' .. (class or '').. '" title = "' .. (title or '') .. '">' .. icon .. '</span>]]'
return label .. ' <small>(' .. val .. ')</small>' .. translationCat
end
return label .. translationCat
end
end


local function parseInput(frame)
 
local qid = frame.args.qid
function wd.addTrackingCat(prop, cat) -- doit parfois être appelé par d'autres modules
if qid and (#qid == 0) then qid = nil end
if type(prop) == 'table' then
local propertyID = mw.text.trim(frame.args[1] or "")
prop = prop[1] -- devrait logiquement toutes les ajouter
local input_parm = mw.text.trim(frame.args[2] or "")
end
if input_parm ~= "FETCH_WIKIDATA" then
if not prop and not cat then
return false, input_parm, nil, nil
return formatError("property-param-not-provided")
end
end
local entity = mw.wikibase.getEntity(qid)
if not cat then
local claims
cat = wd.translate('trackingcat', prop or 'P??')
if entity and entity.claims then
end
claims = entity.claims[propertyID]
return addCat(cat )
if not claims then
end
return false, "", nil, nil
 
local function unknownValue(snak, label)
local str = label
 
if type(str) == "function" then
str = str(snak)
end
 
if (not str) then
if snak.datatype == 'time' then
str = wd.translate('sometime')
else
str = wd.translate('somevalue')
end
end
else
return false, "", nil, nil
end
end
return true, entity, claims, propertyID
 
if type(str) ~= "string" then
return formatError(snak.datatype)
end
return str
end
 
local function noValue(displayformat)
if not displayformat then
return wd.translate('novalue')
end
if type(displayformat) == 'string' then
return displayformat
end
return formatError()
end
end
local function isType(claims, type)
 
return claims[1] and claims[1].mainsnak.snaktype == "value" and claims[1].mainsnak.datavalue.type == type
local function getLangCode(entityid)
return databases.langcodes[tonumber(entityid:sub(2))]
end
end
local function getValue(entity, claims, propertyID, delim, labelHook)  
 
if labelHook == nil then
local function showLang(statement) -- retourne le code langue entre parenthèses avant la valeur (par exemple pour les biblios et liens externes)
labelHook = function (qnumber)
local mainsnak = statement.mainsnak
return nil;
if mainsnak.snaktype ~= 'value' then
end
return nil
end
end
if isType(claims, "wikibase-entityid") then
local langlist = {}
local out = {}
if mainsnak.datavalue.type == 'monolingualtext' then
for k, v in pairs(claims) do
langlist = {mainsnak.datavalue.value.language}
local qnumber = "Q" .. v.mainsnak.datavalue.value["numeric-id"]
elseif (not statement.qualifiers) or (not statement.qualifiers.P407) then
local sitelink = mw.wikibase.getSitelink(qnumber)
return
local label = labelHook(qnumber) or mw.wikibase.getLabel(qnumber) or qnumber
else
if sitelink then
for i, j in pairs( statement.qualifiers.P407 ) do
out[#out + 1] = "[[" .. sitelink .. "|" .. label .. "]]"
if  j.snaktype == 'value' then
else
local langentity = wd.getId(j)
out[#out + 1] = "[[:d:" .. qnumber .. "|" .. label .. "]]<abbr title='" .. i18n["errors"]["local-article-not-found"] .. "'>[*]</abbr>"
local langcode = getLangCode(langentity)
table.insert(langlist, langcode)
end
end
end
end
return table.concat(out, delim)
else
-- just return best values
return entity:formatPropertyValues(propertyID).value
end
end
if (#langlist > 1) or (#langlist == 1 and langlist[1] ~= defaultlang) then -- si c'est en français, pas besoin de le dire
langlist.maxLang = 3
return modules.langmodule.indicationMultilingue(langlist)
end
end
-- === DATE HANDLING ===
local function fuzzydate(str, precision) -- ajoute le qualificatif "vers" à une date
if not str then
return nil
end
if (precision >= 11) or (precision == 7) or (precision == 6)  then --dates avec jour, siècles, millénaires
return "vers le " .. str
end
if (precision == 8) then --décennies ("années ...")
return "vers les " .. str
end
return "vers " .. str
end
end


------------------------------------------------------------------------------
function wd.addStandardQualifs(str, statement, onlygeneral)
-- module global functions
-- qualificateurs de date ou de lieu approximatif ou d'info globalement incertaine ; onlygenereal=true pour rerstreindre à ces derniers
if (not statement) or (not statement.qualifiers) then
return str
end
if not str then
return error()-- what's that ?
end


if debug then
if statement.qualifiers.P1480 then
function p.inspectI18n(frame)
for i, j in pairs(statement.qualifiers.P1480) do
local val = i18n
local v = wd.getId(j)
for _, key in pairs(frame.args) do
if (v == "Q21818619") and not onlygeneral then --"à proximité de"
key = mw.text.trim(key)
str = wd.translate('approximate-place', str)
val = val[key]
elseif (v == "Q18122778") or (v == "Q18912752") or (v == "Q56644435") or (v == "Q30230067") then --"présumé", "controversé", "probablement", "possible"
str = wd.translate('uncertain-information', str)
elseif (v == "Q5727902") and not onlygeneral and statement.mainsnak.datatype == 'time' then --date approximative
local datevalue = statement.mainsnak.datavalue
if datevalue then str = fuzzydate(str, datevalue.value.precision) end
end
end
end
return val
end
end
return str
end
end


function p.descriptionIn(frame)
function wd.getDateFromQualif(statement, qualif)
local langcode = frame.args[1]
if (not statement) or (not statement.qualifiers) or not (statement.qualifiers[qualif]) then
local id = frame.args[2]
return nil
-- return description of a Wikidata entity in the given language or the default language of this Wikipedia site
end
return mw.wikibase.getEntity(id):getDescription(langcode or wiki.langcode)
local v = statement.qualifiers[qualif][1]
if v.snaktype ~= 'value' then -- que faire dans ce cas ?
return nil
end
return modules.formatDate.dateObject(v.datavalue.value)
end
end


function p.labelIn(frame)
function wd.getDate(statement)
local langcode = frame.args[1]
local period = wd.getDateFromQualif(statement, 'P585') -- retourne un dateobject
local id = frame.args[2]
if period then
-- return label of a Wikidata entity in the given language or the default language of this Wikipedia site
return period
return mw.wikibase.getEntity(id):getLabel(langcode or wiki.langcode)
end
local begin, ending = wd.getDateFromQualif(statement, 'P580'),  wd.getDateFromQualif(statement, 'P582')
if begin or ending then
return modules.formatDate.rangeObject(begin, ending) -- retourne un rangeobject fait de deux dateobject
end
return nil
end
end


-- This is used to get a value, or a comma separated list of them if multiple values exist
function wd.getFormattedDate(statement, params)
p.getValue = function(frame)
if not statement then
local delimdefault = ", " -- **internationalise later**
return nil
local delim = frame.args.delimiter or ""
end
delim = string.gsub(delim, '"', '')
local str
if #delim == 0 then
 
delim = delimdefault
--cherche la date avec les qualifs P580/P582
local datetable = wd.getDate(statement)
if datetable then
str = modules.formatDate.objectToText(datetable, params)
end
-- puis limite intérieur / supérieur
if not str then
local start, ending = wd.getDateFromQualif(statement, 'P1319'), wd.getDateFromQualif(statement, 'P1326')
str = modules.formatDate.between(start, ending, params)
end
local fromqualif = false
if str then fromqualif = true end --si la date est tirée des qualificateurs, on n'y ajoute pas l'éventuel "vers ..."
 
-- sinon, le mainsnak, pour les données de type time
if (not str) and (statement.mainsnak.datatype == 'time') then
local mainsnak = statement.mainsnak
if (mainsnak.snaktype == 'value') or (mainsnak.snaktype == 'somevalue') then
str = wd.formatSnak(mainsnak, params)
end
end
end
local go, errorOrentity, claims, propertyID = parseInput(frame)
if not go then
if str and params and (params.addstandardqualifs ~= '-') then
return errorOrentity
str = wd.addStandardQualifs(str, statement, fromqualif)
end
end
return getValue(errorOrentity, claims, propertyID, delim)
 
return str
end
end


-- Same as above, but uses the short name property for label if available.
wd.compare.by_quantity = function(c1, c2)
p.getValueShortName = function(frame)
local v1 = wd.getDataValue(c1.mainsnak)
local go, errorOrentity, claims, propertyID = parseInput(frame)
local v2 = wd.getDataValue(c2.mainsnak)  
if not go then
if not (v1 and v2) then
return errorOrentity
return true
end
end
local entity = errorOrentity
return v1 < v2
-- if wiki-linked value output as link if possible
end
local function labelHook (qnumber)
 
local label
--[[ tri chronologique générique :
local claimEntity = mw.wikibase.getEntity(qnumber)
            retourne une fonction de tri de liste de déclaration
if claimEntity ~= nil then
            en fonction d’une fonction qui calcule la clé de tri
if claimEntity.claims.P1813 then
            et d’une fonction qui compare les clés de tri
for k2, v2 in pairs(claimEntity.claims.P1813) do
 
if v2.mainsnak.datavalue.value.language == "en" then
  paramètres nommés: (appel type wikidata.compare.chrono_key_sort{sortKey="nom clé"})
label = v2.mainsnak.datavalue.value.text
        sortKey (optionnel)  : chaine, le nom de la clé utilisée pour un tri
end
                                (pour éviter de rentrer en collision avec "dateSortKey"
                                utilisé par chronoSort au besoin)
        snak_key_get_function : fonction qui calcule la valeur de la clé à partir d’un snak ou d’une déclaration,
        (obligatoire)          le résultat n’est calculé qu’une fois et est stocké en cache dans claim[sortKey]
        key_compare_function  : fonction de comparaison des clés calculées par snak_key_get_function
        (optionnel)
--]]   
 
function wd.chrono_key_sort(arg)
local snak_key_get_function = arg.snak_key_get_function
local sortKey = arg.sortKey or "dateSortKey"
local key_compare_function = arg.key_compare_function or
function(c1, c2) return c1 < c2 end
return function(claims)
for _, claim in ipairs( claims ) do
if not claim[sortKey] then
local key = snak_key_get_function(claim)
if key then
claim[sortKey] = wd.compare.get_claim_date(key)
else
claim[sortKey] = 0
end
end
end
end
end
end
if label == nil or label == "" then return nil end
table.sort(
return label
claims,
function(c1, c2)
return key_compare_function(c1[sortKey], c2[sortKey])
end
)
return claims
end
end
return getValue(errorOrentity, claims, propertyID, ", ", labelHook);
end
end


-- This is used to get a value, or a comma separated list of them if multiple values exist
 
-- from an arbitrary entry by using its QID.
function wd.quantitySort(claims, inverted)
-- Use : {{#invoke:Wikidata|getValueFromID|<ID>|<Property>|FETCH_WIKIDATA}}
local function sort(c1, c2)
-- E.g.: {{#invoke:Wikidata|getValueFromID|Q151973|P26|FETCH_WIKIDATA}} - to fetch value of 'spouse' (P26) from 'Richard Burton' (Q151973)
local v1 = wd.getDataValue(c1.mainsnak)
-- Please use sparingly - this is an *expensive call*.
local v2 = wd.getDataValue(c2.mainsnak)  
p.getValueFromID = function(frame)
if not (v1 and v2) then
local itemID = mw.text.trim(frame.args[1] or "")
return true
local propertyID = mw.text.trim(frame.args[2] or "")
local input_parm = mw.text.trim(frame.args[3] or "")
if input_parm == "FETCH_WIKIDATA" then
local entity = mw.wikibase.getEntity(itemID)
local claims
if entity and entity.claims then
claims = entity.claims[propertyID]
end
end
if claims then
if inverted then
return getValue(entity, claims, propertyID, ", ")
return v2 < v1
else
return ""
end
end
return v1 < v2
end
table.sort(claims, sort )
return claims
end
function wd.compare.get_claim_date(claim, datetype) -- rend une date au format numérique pour faire des comparaisons
local snak = claim.mainsnak or claim
if datetype and datetype == 'personbirthdate' then -- fonctionne avec un claim dont la valeur est une personne dont on va rendre la date de naissance
if (snak.snaktype == 'value') and (snak.datatype == 'wikibase-item') then
local personid = wd.getId(snak)
local birthclaims = wd.getClaims({ entity = personid, property = 'P569', numval = 1})
if birthclaims then
return wd.compare.get_claim_date(birthclaims[1] or birthclaims)
else return math.huge end
else return math.huge end -- en cas de donnée manquante, valeur infinie qui entraîne le classement en fin de liste
end
local iso, datequalif, isonumber
if (snak.snaktype == 'value') and (snak.datatype == 'time') then
iso = snak.datavalue.value.time
else
else
return input_parm
for i, dqualif in ipairs(datequalifiers) do
iso = timeFromQualifs(claim, {dqualif})
if iso then
datequalif = dqualif
break
end
end
if not iso then return math.huge end
end
-- transformation en nombre (indication de la base car gsub retourne deux valeurs)
isonumber = tonumber( iso:gsub( '(%d)%D', '%1' ), 10 )
-- ajustement de la date tenant compte du qualificatif dont elle est issue : un fait se terminant à une date est antérieur à un autre commençant à cette date
if datequalif == 'P582' then --date de fin
isonumber = isonumber - 2
elseif datequalif == 'P1326' then -- date au plus tard
isonumber = isonumber - 1
elseif datequalif == 'P1319' then -- date au plus tôt
isonumber = isonumber + 1
elseif datequalif == 'P571' or datequalif == 'P580' then -- date de début et date de création
isonumber = isonumber + 2
end
end
return isonumber
end
end
local function getQualifier(frame, outputHook)  
 
local propertyID = mw.text.trim(frame.args[1] or "")
function wd.compare.chronoCompare(c1, c2)
local qualifierID = mw.text.trim(frame.args[2] or "")
return wd.compare.get_claim_date(c1) < wd.compare.get_claim_date(c2)
local input_parm = mw.text.trim(frame.args[3] or "")
end
if input_parm == "FETCH_WIKIDATA" then
 
local entity = mw.wikibase.getEntity()
-- fonction pour renverser l’ordre d’une autre fonction
if entity.claims[propertyID] ~= nil then
function wd.compare.rev(comp_criteria)
local out = {}
return function(c1, c2)
for k, v in pairs(entity.claims[propertyID]) do
-- attention les tris en lua attendent des fonctions de comparaison strictement inférieur, on doit
for k2, v2 in pairs(v.qualifiers[qualifierID]) do
-- vérifier la non égalité quand on inverse l’ordre d’un critère, d’ou "and comp_criteria(c2,c1)"
if v2.snaktype == 'value' then
return not(comp_criteria(c1,c2)) and comp_criteria(c2,c1)
out[#out + 1] = outputHook(v2);
end
end
end
end
 
-- Fonction qui trie des Claims de type time selon l'ordre chronologique
-- Une clé de tri nomée « dateSortKey » est ajouté à chaque claim.
-- Si des clés de tri de ce nom existent déjà, elles sont utilisées sans modification.
 
function wd.chronoSort( claims, sorttype )
for _, claim in ipairs( claims ) do
if not claim.dateSortKey then
if sorttype and (sorttype == 'age' or sorttype == 'ageinverted') then
claim.dateSortKey = wd.compare.get_claim_date(claim, 'personbirthdate')
else
claim.dateSortKey = wd.compare.get_claim_date(claim)
end
if sorttype and (sorttype == 'inverted' or sorttype == 'ageinverted') and claim.dateSortKey == math.huge then
claim.dateSortKey = -math.huge -- quand la donnée est manquante on lui assigne la valeur qui entraîne le classement en fin de liste
end
end
return table.concat(out, ", "), true
else
return "", false
end
end
else
return input_parm, false
end
end
table.sort(
claims,
function ( c1, c2 )
if sorttype and (sorttype == 'inverted' or sorttype == 'ageinverted') then
return c2.dateSortKey < c1.dateSortKey
end
return c1.dateSortKey < c2.dateSortKey
end
)
return claims
end
end
p.getQualifierValue = function(frame)
 
local function outputValue(value)
local function get_numeric_claim_value(claim, propertySort)
local qnumber = "Q" .. value.datavalue.value["numeric-id"]
local val
if (mw.wikibase.getSitelink(qnumber)) then
local claimqualifs = claim.qualifiers
return "[[" .. mw.wikibase.getSitelink(qnumber) .. "]]"
if claimqualifs then
else
local vals = claimqualifs[propertySort]
return "[[:d:" .. qnumber .. "|" ..qnumber .. "]]<abbr title='" .. i18n["errors"]["local-article-not-found"] .. "'>[*]</abbr>"
if vals and vals[1].snaktype == 'value' then
val = vals[1].datavalue.value
end
end
end
end
return (getQualifier(frame, outputValue))
return tonumber(val or 0)
end
end


-- This is used to get a value like 'male' (for property p21) which won't be linked and numbers without the thousand separators
function wd.compare.numeric(propertySort)
p.getRawValue = function(frame)
return function(c1, c2)
local go, errorOrentity, claims, propertyID = parseInput(frame)
return get_numeric_claim_value(c1, propertySort) < get_numeric_claim_value(c2, propertySort)
if not go then
return errorOrentity
end
end
local entity = errorOrentity
end
local result = entity:formatPropertyValues(propertyID, mw.wikibase.entity.claimRanks).value
 
-- if number type: remove thousand separators, bounds and units
-- Fonction qui trie des Claims de type value selon l'ordre de la propriété fournit
if isType(claims, "quantity") then
-- Une clé de tri nomée « dateSortKey » est ajouté à chaque claim.
result = mw.ustring.gsub(result, "(%d),(%d)", "%1%2")
-- Si des clés de tri de ce nom existent déjà, elles sont utilisées sans modification.
result = mw.ustring.gsub(result, "(%d)±.*", "%1")
 
function wd.numericPropertySort( claims, propertySort )
for _, claim in ipairs( claims ) do
if not claim.dateSortKey then
local val = get_numeric_claim_value(claim, propertySort)
claim.dateSortKey = tonumber(val or 0)
end
end
end
return result
table.sort(
claims,
function ( c1, c2 )
return c1.dateSortKey < c2.dateSortKey
end
)
return claims
end
end


-- This is used to get the unit name for the numeric value returned by getRawValue
--[[
p.getUnits = function(frame)
test possible en console pour la fonction précédente :
local go, errorOrentity, claims, propertyID = parseInput(frame)
= p.formatStatements{entity = "Q375946", property = 'P50', sorttype = 'P1545', linkback = "true"}
if not go then
--]]
return errorOrentity
 
-- ===================
function wd.getReferences(statement)
local refdata = statement.references
if not refdata then
return nil
end
 
local refs = {}
local hashes = {}
for i, ref in pairs(refdata) do
local s
local function hasValue(prop) -- checks that the prop is here with valid value
if ref.snaks[prop] and ref.snaks[prop][1].snaktype == 'value' then
return true
end
return false
end
 
if ref.snaks.P248 then -- cas lorsque P248 (affirmé dans) est utilisé
for j, source in pairs(ref.snaks.P248) do
if source.snaktype == 'value' then
local page, accessdate, quotation
if hasValue('P304') then -- page
page = wd.formatSnak(ref.snaks.P304[1])
end
if hasValue('P813') then -- date de consultation
accessdate = wd.formatSnak(ref.snaks.P813[1])
end
if hasValue('P1683') then -- citation
quotation = wd.formatSnak(ref.snaks.P1683[1])
end
local sourceId = wd.getId(source)
s = modules.reference.citeitem(sourceId, {['page'] = page, ['accessdate'] = accessdate, ['citation'] = quotation})
table.insert(refs, s)
table.insert(hashes, ref.hash .. sourceId)
end
end
elseif hasValue('P8091') or hasValue('P854') then -- cas lorsque P8091 (Archival Resource Key) ou P854 (URL de la référence)est utilisé
local arkKey, url, title, author, publisher, accessdate, publishdate, publishlang, quotation, description
 
if hasValue('P8091') then
arkKey = wd.formatSnak(ref.snaks.P8091[1], {text = "-"})
url = 'https://n2t.net/' .. arkKey
if hasValue('P1476') then
title = wd.formatSnak(ref.snaks.P1476[1])
else
title = arkKey
end
elseif hasValue('P854') then
url = wd.formatSnak(ref.snaks.P854[1], {text = "-"})
if hasValue('P1476') then
title = wd.formatSnak(ref.snaks.P1476[1])
else
title = mw.ustring.gsub(url, '^[Hh][Tt][Tt][Pp]([Ss]?):(/?)([^/])', 'http%1://%3')
end
end
 
--todo : handle multiple values for author, etc.
if hasValue('P1810') then -- sous le nom
description = 'sous le nom ' .. wd.formatSnak(ref.snaks.P1810[1])
end
if hasValue('P813') then -- date de consultation
accessdate = wd.formatSnak(ref.snaks.P813[1])
end
if hasValue('P50') then  -- author (item type)
author = wd.formatSnak(ref.snaks.P50[1])
elseif hasValue('P2093') then -- author (string type)
author = wd.formatSnak(ref.snaks.P2093[1])
end
if hasValue('P123') then -- éditeur
publisher = wd.formatSnak(ref.snaks.P123[1])
end
if hasValue('P1683') then -- citation
quotation = wd.formatSnak(ref.snaks.P1683[1])
end
if hasValue('P577') then -- date de publication
publishdate = wd.formatSnak(ref.snaks.P577[1])
end
if hasValue('P407') then -- langue de l'œuvre
local id = wd.getId(ref.snaks.P407[1])
publishlang = getLangCode(id)
end
s = modules.cite.lienWeb{titre = title, url = url, auteur = author, editeur = publisher, langue = publishlang, ['en ligne le'] = publishdate, ['consulté le'] = accessdate, ['citation'] = quotation, ['description'] = description}
table.insert(hashes, ref.hash)
table.insert(refs, s)
 
elseif ref.snaks.P854 and ref.snaks.P854[1].snaktype == 'value' then
s = wd.formatSnak(ref.snaks.P854[1], {text = "-"})
table.insert(hashes, ref.snaks.P854[1].hash)
table.insert(refs, s)
end
end
end
local entity = errorOrentity
if #refs > 0 then
local result = entity:formatPropertyValues(propertyID, mw.wikibase.entity.claimRanks).value
if #hashes == #refs then
if isType(claims, "quantity") then
return refs, hashes
result = mw.ustring.sub(result, mw.ustring.find(result, " ")+1, -1)
end
return refs
end
end
return result
end
end


-- This is used to get the unit's QID to use with the numeric value returned by getRawValue
function wd.sourceStr(sources, hashes)
p.getUnitID = function(frame)
if not sources or (#sources == 0) then
local go, errorOrentity, claims = parseInput(frame)
return nil
if not go then
return errorOrentity
end
end
local entity = errorOrentity
local useHashes = hashes and #hashes == #sources
local result
for i, j in ipairs(sources) do
if isType(claims, "quantity") then
local refArgs = {name = 'ref', content = j}
-- get the url for the unit entry on Wikidata:
if useHashes and hashes[i] ~= '-' then
result = claims[1].mainsnak.datavalue.value.unit
refArgs.args = {name = 'wikidata-' .. hashes[i]}
-- and just reurn the last bit from "Q" to the end (which is the QID):
end
result = mw.ustring.sub(result, mw.ustring.find(result, "Q"), -1)
sources[i] = mw.getCurrentFrame():extensionTag(refArgs)
end
end
return result
return table.concat(sources, '<sup class="reference cite_virgule">,</sup>')
end
end


p.getRawQualifierValue = function(frame)
function wd.getDataValue(snak, params)
local function outputHook(value)
if not params then
if value.datavalue.value["numeric-id"] then
params = {}
return mw.wikibase.getLabel("Q" .. value.datavalue.value["numeric-id"])
end
local speciallabels = params.speciallabels -- parfois on a besoin de faire une liste d'éléments pour lequel le libellé doit être changé, pas très pratique d'utiliser une fonction pour ça
 
if snak.snaktype ~= 'value' then
return nil
end
 
local datatype = snak.datatype
local value = snak.datavalue.value
local displayformat = params.displayformat
if type(displayformat) == 'function' then
return displayformat(snak, params)
end
 
if datatype == 'wikibase-item' then
return wd.formatEntity(wd.getId(snak), params)
end
 
if datatype == 'url' then
if params.displayformat == 'raw' then
return value
else
return modules.weblink.makelink(value, params.text)
end
end
 
if datatype == 'math' then
return mw.getCurrentFrame():extensionTag( "math", value)
end
 
if datatype == 'tabular-data' then
return mw.ustring.sub(value, 6, 100)  -- returns the name of the file, without the "Data:" prefix
end
 
if (datatype == 'string') or (datatype == 'external-id') or (datatype == 'commonsMedia') then -- toutes les données de type string sauf "math"
if params.urlpattern then
local urlpattern = params.urlpattern
if type(urlpattern) == 'function' then
urlpattern = urlpattern(value)
end
-- encodage de l'identifiant qui se retrouve dans le path de l'URL, à l'exception des slashes parfois rencontrés, qui sont des séparateurs à ne pas encoder
local encodedValue = mw.uri.encode(value, 'PATH'):gsub('%%2F', '/')
-- les parenthèses autour du encodedValue:gsub() sont nécessaires, sinon sa 2e valeur de retour est aussi passée en argument au mw.ustring.gsub() parent
local url = mw.ustring.gsub(urlpattern, '$1', (encodedValue:gsub('%%', '%%%%')))
value = '[' .. url .. ' ' .. (params.text or value) .. ']'
end
return value
end
if datatype == 'time' then -- format example: +00000001809-02-12T00:00:00Z
if displayformat == 'raw' then
return value.time
else
local dateobject = modules.formatDate.dateObject(value, {precision = params.precision})
return modules.formatDate.objectToText(dateobject, params)
end
end
 
if datatype == 'globe-coordinate' then
-- retourne une table avec clés latitude, longitude, précision et globe à formater par un autre module (à changer ?)
if displayformat == 'latitude' then
return value.latitude
elseif displayformat == 'longitude' then
return value.longitude
else
else
return value.datavalue.value
local coordvalue = mw.clone( value )
coordvalue.globe = databases.globes[value.globe] -- transforme l'ID du globe en nom anglais utilisable par geohack
return coordvalue -- note : les coordonnées Wikidata peuvent être utilisée depuis Module:Coordinates. Faut-il aussi autoriser à appeler Module:Coordiantes ici ?
end
end
end
end
local ret, gotData = getQualifier(frame, outputHook)
 
if gotData then
if datatype == 'quantity' then -- todo : gérer les paramètres précision
ret = string.upper(string.sub(ret, 1, 1)) .. string.sub(ret, 2)
local amount, unit = value.amount, value.unit
 
if unit then
unit = unit:match('Q%d+')
end
if not unit then
unit = 'dimensionless'
end
local raw
if displayformat == "raw" then
raw = true
end
return modules.formatNum.displayvalue(amount, unit,
{targetunit = params.targetunit, raw = raw, rounding = params.rounding, showunit = params.showunit or 'short', showlink = params.showlink}
)
end
end
return ret
if datatype == 'monolingualtext' then
if value.language == defaultlang then
return value.text
else
return modules.langmodule.langue({value.language, value.text, nocat=true})
end
end
return formatError('unknown-datavalue-type' )
 
end
 
function wd.stringTable(args) -- like getClaims, but get a list of string rather than a list of snaks, for easier manipulation
local claims = args.claims
local cat = ''
 
if not claims then
claims = wd.getClaims(args)
end
if not claims or claims == {} then
return {}, {}, cat
end
if args.removedupesdate and (args.removedupesdate ~= '-') then
claims, cat = removeDupesDate(claims, args.removedupesdate)
end
local props = {} -- liste des propriétés associété à chaque string pour catégorisation et linkback
for i, j in pairs(claims) do
claims[i] = wd.formatStatement(j, args)
table.insert(props, j.mainsnak.property)
end
if args.removedupes and (args.removedupes ~= '-') then
claims = wd.addNewValues({}, claims) -- devrait aussi supprimer de props celles qui ne sont pas utilisées
end
return claims, props, cat
end
end


-- This is used to get a date value for date_of_birth (P569), etc. which won't be linked
function wd.getQualifiers(statement, qualifs, params)
-- Dates and times are stored in ISO 8601 format (sort of).
if not statement.qualifiers then
-- At present the local formatDate(date, precision, timezone) function doesn't handle timezone
return nil
-- So I'll just supply "Z" in the call to formatDate below:
p.getDateValue = function(frame)
local date_format = mw.text.trim(frame.args[3] or i18n["datetime"]["default-format"])
local date_addon = mw.text.trim(frame.args[4] or i18n["datetime"]["default-addon"])
local go, errorOrentity, claims = parseInput(frame)
if not go then
return errorOrentity
end
end
local entity = errorOrentity
local vals = {}
local out = {}
if type(qualifs) == 'string' then
for k, v in pairs(claims) do
qualifs = wd.splitStr(qualifs)
if v.mainsnak.datavalue.type == 'time' then
end
local timestamp = v.mainsnak.datavalue.value.time
for i, j in pairs(qualifs) do
local dateprecision = v.mainsnak.datavalue.value.precision
if statement.qualifiers[j] then
-- A year can be stored like this: "+1872-00-00T00:00:00Z",
for k, l in pairs(statement.qualifiers[j]) do
-- which is processed here as if it were the day before "+1872-01-01T00:00:00Z",
table.insert(vals, l)
-- and that's the last day of 1871, so the year is wrong.
end
-- So fix the month 0, day 0 timestamp to become 1 January instead:
timestamp = timestamp:gsub("%-00%-00T", "-01-01T")
out[#out + 1] = parseDateFull(timestamp, dateprecision, date_format, date_addon)
end
end
end
end
return table.concat(out, ", ")
if #vals == 0 then
return nil
end
return vals
end
end
p.getQualifierDateValue = function(frame)
 
local date_format = mw.text.trim(frame.args[4] or i18n["datetime"]["default-format"])
function wd.getFormattedQualifiers(statement, qualifs, params)
local date_addon = mw.text.trim(frame.args[5] or i18n["datetime"]["default-addon"])
if not params then params = {} end
local function outputHook(value)
local qualiftable = wd.getQualifiers(statement, qualifs)
local timestamp = value.datavalue.value.time
if not qualiftable then
return parseDateValue(timestamp, date_format, date_addon)
return nil
end
end
return (getQualifier(frame, outputHook))
qualiftable = wd.filterClaims(qualiftable, params) or {}
for i, j in pairs(qualiftable) do
qualiftable[i] = wd.formatSnak(j, params)
end
return modules.linguistic.conj(qualiftable, params.conjtype)
end
end


-- This is used to fetch all of the images with a particular property, e.g. image (P18), Gene Atlas Image (P692), etc.
function wd.showQualifiers(str, statement, args)
-- Parameters are | propertyID | value / FETCH_WIKIDATA / nil | separator (default=space) | size (default=frameless)
local qualifs = args.showqualifiers
-- It will return a standard wiki-markup [[File:Filename | size]] for each image with a selectable size and separator (which may be html)
if not qualifs then
-- e.g. {{#invoke:Wikidata|getImages|P18|FETCH_WIKIDATA}}
return str -- or error ?
-- e.g. {{#invoke:Wikidata|getImages|P18|FETCH_WIKIDATA|<br>|250px}}
-- If a property is chosen that is not of type "commonsMedia", it will return empty text.
p.getImages = function(frame)
local sep = mw.text.trim(frame.args[3] or " ")
local imgsize = mw.text.trim(frame.args[4] or "frameless")
local go, errorOrentity, claims = parseInput(frame)
if not go then
return errorOrentity
end
end
local entity = errorOrentity
if type(qualifs) == 'string' then
if (claims[1] and claims[1].mainsnak.datatype == "commonsMedia") then
qualifs = wd.splitStr(qualifs)
local out = {}
end
for k, v in pairs(claims) do
local qualifargs = args.qualifargs or {}
local filename = v.mainsnak.datavalue.value
-- formatage des qualificatifs = args commençant par "qualif", ou à défaut, les mêmes que pour la valeur principale
out[#out + 1] = "[[File:" .. filename .. "|" .. imgsize .. "]]"
qualifargs.displayformat = args.qualifdisplayformat or args.displayformat
qualifargs.labelformat = args.qualiflabelformat or args.labelformat
qualifargs.labelformat2 = args.qualiflabelformat2 or args.labelformat2
qualifargs.link = args.qualiflink or args.link
qualifargs.linktopic = args.qualiflinktopic or args.linktopic
qualifargs.conjtype = args.qualifconjtype
qualifargs.precision = args.qualifprecision
qualifargs.targetunit = args.qualiftargetunit
qualifargs.defaultlink = args.qualifdefaultlink or args.defaultlink
qualifargs.defaultlinkquery = args.qualifdefaultlinkquery or args.defaultlinkquery
local formattedqualifs
if args.qualifformat and type (args.qualifformat) == 'function' then
formattedqualifs = args.qualifformat(statement, qualifs, qualifargs)
else
formattedqualifs = wd.getFormattedQualifiers(statement, qualifs, qualifargs)
end
if formattedqualifs and formattedqualifs ~= "" then
str = str .. " (" .. formattedqualifs .. ")"
end
return str
end
 
 
function wd.formatSnak( snak, params )
if not params then params = {} end -- pour faciliter l'appel depuis d'autres modules
if snak.snaktype == 'somevalue' then
return unknownValue(snak, params.unknownlabel)
elseif snak.snaktype == 'novalue' then
return noValue(params.novaluelabel)
elseif snak.snaktype == 'value' then
return wd.getDataValue( snak, params)
else
return formatError( 'unknown-snak-type' )
end
end
 
function wd.formatStatement( statement, args ) -- FONCTION A REORGANISER (pas très lisible)
if not args then
args = {}
end
if not statement.type or statement.type ~= 'statement' then
return formatError( 'unknown-claim-type' )
end
local prop = statement.mainsnak.property
 
local str
 
-- special displayformat f
if args.statementformat and (type(args.statementformat) == 'function') then
str = args.statementformat(statement, args)
elseif (statement.mainsnak.datatype == 'time') and (statement.mainsnak.dateformat ~= '-') then
if args.displayformat == 'raw' and statement.mainsnak.snaktype == 'value' then
str = statement.mainsnak.datavalue.value.time
else
str = wd.getFormattedDate(statement, args)
end
elseif args.showonlyqualifier and (args.showonlyqualifier ~= '') then
str = wd.getFormattedQualifiers(statement, args.showonlyqualifier, args)
if not str then
return nil
end
if args.addstandardqualifs ~= '-' then
str = wd.addStandardQualifs(str, statement, true)
end
end
return table.concat(out, sep)
else
else
return ""
str = wd.formatSnak( statement.mainsnak, args )
if (args.addstandardqualifs ~= '-') and (args.displayformat ~= 'raw') then
str = wd.addStandardQualifs(str, statement)
end
end
 
-- ajouts divers
if args.showlang == true then
local indicateur = showLang(statement)
if indicateur then
str = indicateur .. '&nbsp;' .. str
end
end
if args.showqualifiers then
str = wd.showQualifiers(str, statement, args)
end
 
if args.showdate then -- when "showdate and chronosort are both set, date retrieval is performed twice
local period = wd.getFormattedDate(statement, args, "-") -- 3 arguments indicate the we should not use additional qualifiers, already added by wd.formatStatement
if period then
str = str .. " <small>(" .. period .. ")</small>"
end
end
end
end


-- This is used to get the TA98 (Terminologia Anatomica first edition 1998) values like 'A01.1.00.005' (property P1323)
if args.showsource and args.showsource ~= '-' and args.showsource ~= "false" then
-- which are then linked to https://ifaa.unifr.ch/Public/EntryPage/TA98%20Tree/Entity%20TA98%20EN/01.1.00.005%20Entity%20TA98%20EN.htm
if args.showsource == "only" then str="" end -- si showsource="only", alors ne montrer que la (les) source(s),
-- uses the newer mw.wikibase calls instead of directly using the snaks
                                            -- sans la valeur qui, auparavant, était enregistrée dans str
-- formatPropertyValues returns a table with the P1323 values concatenated with ", " so we have to split them out into a table in order to construct the return string
                                            -- Utilisé par le modèle {{PH census}}
p.getTAValue = function(frame)
local sources, hashes = wd.getReferences(statement)
local ent = mw.wikibase.getEntity()
if sources then
local props = ent:formatPropertyValues('P1323')
local source = wd.sourceStr(sources, hashes)
local out = {}
if source then
local t = {}
str = str .. source
for k, v in pairs(props) do
if k == 'value' then
t = mw.text.split( v, ", ")
for k2, v2 in pairs(t) do
out[#out + 1] = "[https://ifaa.unifr.ch/Public/EntryPage/TA98%20Tree/Entity%20TA98%20EN/" .. string.sub(v2, 2) .. "%20Entity%20TA98%20EN.htm " .. v2 .. "]"
end
end
end
end
end
end
local ret = table.concat(out, "<br> ")
 
if #ret == 0 then
return str
ret = "Invalid TA"
end
 
function wd.addLinkBack(str, id, property)
if not id or id == '' then
id = wd.getEntityIdForCurrentPage()
end
end
return ret
if not id then
return str
end
if type(property) == 'table' then
property = property[1]
end
id = mw.text.trim(wd.entityId(id))
 
local class = ''
if property then
class = 'wd_' .. string.lower(property)
end
local icon = '[[File:Blue pencil.svg|%s|10px|baseline|class=noviewer|link=%s]]'
local title = wd.translate('see-wikidata-value')
local url = mw.uri.fullUrl('d:' .. id, 'uselang=fr')
url.fragment = property -- ajoute une #ancre si paramètre "property" défini
url = tostring(url)
local v = mw.html.create('span')
:addClass(class)
:wikitext(str)
:tag('span')
:addClass('noprint wikidata-linkback skin-invert')
:wikitext(icon:format(title, url))
:allDone()
return tostring(v)
end
end


function wd.addRefAnchor(str, id)
--[[
--[[
This is used to return an image legend from Wikidata
Insère une ancre pour une référence générée à partir d'un élément wd.
image is property P18
L'id Wikidata sert d'identifiant à l'ancre, à utiliser dans les modèles type "harvsp"
image legend is property P2096
--]]
return tostring(
mw.html.create('span')
:attr('id', id)
:attr('class', "ouvrage")
:wikitext(str)
)
end


Call as {{#invoke:Wikidata |getImageLegend | <PARAMETER> | lang=<ISO-639code> |id=<QID>}}
--=== FUNCTIONS USING AN ENTITY AS ARGUMENT ===
Returns PARAMETER, unless it is equal to "FETCH_WIKIDATA", from Item QID (expensive call)
local function formatStatementsGrouped(args, type)
If QID is omitted or blank, the current article is used (not an expensive call)
-- regroupe les affirmations ayant la même valeur en mainsnak, mais des qualificatifs différents
If lang is omitted, it uses the local wiki language, otherwise it uses the provided ISO-639 language code
-- (seulement pour les propriétés de type élément)
ISO-639: https://docs.oracle.com/cd/E13214_01/wli/docs92/xref/xqisocodes.html#wp1252447


Ranks are: 'preferred' > 'normal'
local claims = wd.getClaims(args)
This returns the label from the first image with 'preferred' rank
if not claims then
Or the label from the first image with 'normal' rank if preferred returns nothing
return nil
Ranks: https://www.mediawiki.org/wiki/Extension:Wikibase_Client/Lua
end
]]
local groupedClaims = {}


p.getImageLegend = function(frame)
-- regroupe les affirmations par valeur de mainsnak
-- look for named parameter id; if it's blank make it nil
local function addClaim(claim)
local id = frame.args.id
local id = wd.getMainId(claim)
if id and (#id == 0) then
for i, j in pairs(groupedClaims) do
id = nil
if (j.id == id) then
table.insert(groupedClaims[i].claims, claim)
return
end
end
table.insert(groupedClaims, {id = id, claims = {claim}})
end
for i, claim in pairs(claims) do
addClaim(claim)
end
end


-- look for named parameter lang
local stringTable = {}
-- it should contain a two-character ISO-639 language code
-- if it's blank fetch the language of the local wiki
local lang = frame.args.lang
if (not lang) or (#lang < 2) then
lang = mw.language.getContentLanguage().code
end


-- first unnamed parameter is the local parameter, if supplied
-- instructions ad hoc pour les paramètres concernant la mise en forme d'une déclaration individuelle
local input_parm = mw.text.trim(frame.args[1] or "")
local funs = {
if input_parm == "FETCH_WIKIDATA" then
{param = "showqualifiers", fun = function(str, claims)
local ent = mw.wikibase.getEntity(id)
local qualifs = {}
local imgs
for i, claim in pairs(claims) do
if ent and ent.claims then
local news = wd.getFormattedQualifiers(claim, args.showqualifiers, args)
imgs = ent.claims.P18
if news then
end
table.insert(qualifs, news)
local imglbl
end
if imgs then
end
-- look for an image with 'preferred' rank
local qualifstr = modules.linguistic.conj(qualifs, wd.translate("qualif-separator"))
for k1, v1 in pairs(imgs) do
if qualifstr and qualifstr ~= "" then
if v1.rank == "preferred" and v1.qualifiers and v1.qualifiers.P2096 then
str = str .. " (" .. qualifstr .. ")"
local imglbls = v1.qualifiers.P2096
end
for k2, v2 in pairs(imglbls) do
return str
if v2.datavalue.value.language == lang then
end
imglbl = v2.datavalue.value.text
},
break
{param = "showdate", fun = function(str, claims)
end
-- toutes les dates sont regroupées à l'intérieur des mêmes parenthèses ex "médaille d'or (1922, 1924)"
local dates = {}
for i, statement in pairs(claims) do
local s = wd.getFormattedDate(statement, args, true)
if statement then table.insert(dates, s) end
end
local datestr = modules.linguistic.conj(dates)
if datestr and datestr ~= "" then
str = str .. " <small>(" .. datestr .. ")</small>"
end
return str
end
},
{param = "showsource", fun = function(str, claims)
-- les sources sont toutes affichées au même endroit, à la fin
-- si deux affirmations ont la même source, on ne l'affiche qu'une fois
local sources = {}
local hashes = {}
local function dupeRef(old, new)
for i, j in pairs(old) do
if j == new then
return true
end
end
end
end
end
end
-- if we don't find one, look for an image with 'normal' rank
for i, claim in pairs(claims) do
if (not imglbl) then
local refs, refHashes = wd.getReferences(claim)
for k1, v1 in pairs(imgs) do
if refs then
if v1.rank == "normal" and v1.qualifiers and v1.qualifiers.P2096 then
for i, j in pairs(refs) do
local imglbls = v1.qualifiers.P2096
if not dupeRef(sources, j) then
for k2, v2 in pairs(imglbls) do
table.insert(sources, j)
if v2.datavalue.value.language == lang then
local hash = (refHashes and refHashes[i]) or '-'
imglbl = v2.datavalue.value.text
table.insert(hashes, hash)
break
end
end
end
end
end
end
end
end
end
return str .. (wd.sourceStr(sources, hashes) or "")
end
}
}
for i, group in pairs(groupedClaims) do -- bricolage pour utiliser les arguments de formatStatements
local str = wd.formatEntity(group.id, args)
if not str then
str = '???' -- pour éviter erreur Lua si formatEntity a retourné nil
end
for i, fun in pairs(funs) do
if args[fun.param] then
str = fun.fun(str, group.claims, args)
end
end
table.insert(stringTable, str)
end
args.valuetable = stringTable
return wd.formatStatements(args)
end
function wd.formatStatements( args )--Format statement and concat them cleanly
if args.value == '-' then
return nil
end
-- If a value is already set: use it, except if it's the special value {{WD}} (use wikidata)
if args.value and args.value ~= '' then
local valueexpl = wd.translate("activate-query")
if args.value ~= valueexpl then
return args.value
end
end
return imglbl
-- There is no value set, and args.expl disables wikidata on empty values
else
elseif args.expl then
return input_parm
return nil
end
 
if args.grouped and args.grouped ~= '' then
args.grouped = false
return formatStatementsGrouped(args)
end
local valuetable = args.valuetable -- dans le cas où les valeurs sont déjà formatées
local props -- les propriétés réellement utilisées (dans certains cas, ce ne sont pas toutes celles de args.property
local cat = ''
if not valuetable then -- cas le plus courant
valuetable, props, cat = wd.stringTable(args)
end
if args.ucfirst == '-' and args.conjtype == 'new line' then args.conjtype = 'lowercase new line' end
local str = modules.linguistic.conj(valuetable, args.conjtype)
if not str then
return args.default
end
if not props then
props = wd.splitStr(args.property)[1]
end
if args.ucfirst ~= '-' then
str = modules.linguistic.ucfirst(str)
end
 
if args.addcat and (args.addcat ~= '-') then
str = str .. wd.addTrackingCat(props) .. cat
end
if args.linkback and (args.linkback ~= '-') then
str = wd.addLinkBack(str, args.entity, props)
end
if args.returnnumberofvalues then
return str, #valuetable
end
end
return str
end
end


-- This is used to get the QIDs of all of the values of a property, as a comma separated list if multiple values exist
function wd.formatAndCat(args)
-- Usage: {{#invoke:Wikidata |getPropertyIDs |<PropertyID> |FETCH_WIKIDATA}}
if not args then
-- Usage: {{#invoke:Wikidata |getPropertyIDs |<PropertyID> |<InputParameter> |qid=<QID>}}
return nil
end
args.linkback = args.linkback or true
args.addcat = true
if args.value then -- do not ignore linkback and addcat, as formatStatements do
if args.value == '-' then
return nil
end
local val = args.value .. wd.addTrackingCat(args.property)
val = wd.addLinkBack(val, args.entity, args.property)
return val
end
return wd.formatStatements( args )
end


p.getPropertyIDs = function(frame)
function wd.getTheDate(args)
local go, errorOrentity, propclaims = parseInput(frame)
local claims = wd.getClaims(args)
if not go then
if not claims then
return errorOrentity
return nil
end
local formattedvalues = {}
for i, j in pairs(claims) do
local v = wd.getFormattedDate(j, args)
if v then
table.insert(formattedvalues, v )
end
end
local val = modules.linguistic.conj(formattedvalues)
if not val then
return nil
end
if args.addcat == true then
val = val .. wd.addTrackingCat(args.property)
end
end
local entity = errorOrentity
val = wd.addLinkBack(val, args.entity, args.property)
-- if wiki-linked value collect the QID in a table
return val
if (propclaims[1] and propclaims[1].mainsnak.snaktype == "value" and propclaims[1].mainsnak.datavalue.type == "wikibase-entityid") then
end
local out = {}
 
for k, v in pairs(propclaims) do
 
out[#out + 1] = "Q" .. v.mainsnak.datavalue.value["numeric-id"]
function wd.keyDate (event, item, params)
params = params or {}
params.entity = item
if type(event) == 'table' then
for i, j in pairs(event) do
params.targetvalue = nil -- réinitialisation barbare des paramètres modifiés
local s = wd.keyDate(j, item, params)
if s then
return s
end
end
end
return table.concat(out, ", ")
elseif type(event) ~= 'string' then
return formatError('invalid-datatype', type(event), 'string')
elseif string.sub(event, 1, 1) == 'Q' then -- on demande un élément utilisé dans P:P793 (événement clé)
params.property = 'P793'
params.targetvalue = event
params.addcat = params.addcat or true
return wd.getTheDate(params)
elseif string.sub(event, 1, 1) == 'P'  then -- on demande une propriété
params.property = event
return wd.formatAndCat(params)
else
else
-- not a wikibase-entityid, so return empty
return formatError('invalid-entity-id', event)
return ""
end
end
end
end


-- returns the page id (Q...) of the current page or nothing of the page is not connected to Wikidata
function wd.mainDate(entity)
function p.pageId(frame)
-- essaye P580/P582
return mw.wikibase.getEntityIdForCurrentPage()
local args = {entity = entity, addcat = true}
args.property = 'P580'
local startpoint = wd.formatStatements(args)
args.property = 'P582'
local endpoint = wd.formatStatements(args)
 
local str
if (startpoint or endpoint) then
str = modules.formatDate.daterange(startpoint, endpoint, params)
str = wd.addLinkBack(str, entity, 'P582')
return str
end
 
-- défaut : P585
args.property = {'P585', 'P571'}
args.linkback = true
return wd.formatStatements(args)
end
end


function p.claim(frame)
-- ==== Fonctions sur le genre ====
local property = frame.args[1] or ""
 
local id = frame.args["id"]
function wd.getgender(id)
local qualifierId = frame.args["qualifier"]
local vals = {
local parameter = frame.args["parameter"]
['Q6581072'] = 'f', -- féminin
local list = frame.args["list"]
['Q6581097'] = 'm', -- masculin
local references = frame.args["references"]
['Q1052281'] = 'f', -- femme transgenre
local showerrors = frame.args["showerrors"]
['Q2449503'] = 'm', -- homme transgenre
local default = frame.args["default"]
['Q17148251'] = 'f', -- en:Travesti (gender identity)
if default then showerrors = nil end
['Q43445'] = 'f', -- femelle
['Q44148'] = 'm', -- mâle
default      = '?'
}
local gender = wd.formatStatements{entity = id, property = 'P21', displayformat = 'raw', numval = 1}
return vals[gender] or vals.default
end


-- get wikidata entity
-- catégories de genre/nombre
local entity = mw.wikibase.getEntity(id)
function wd.getgendernum(claims)
if not entity then
local personid, gender
if showerrors then return printError("entity-not-found") else return default end
local anym = false
local anyf = false
local anyunknown = false
for i, claim in pairs(claims) do
local snak = claim.mainsnak or claim
if(snak.snaktype == 'value') and (snak.datatype == 'wikibase-item') then
personid = wd.getId(snak)
gender = wd.getgender(personid)
anym = anym or (gender == 'm')
anyf = anyf or (gender == 'f')
anyunknown = anyunknown or (gender == '?')
else
anyunknown = true
end
end
end
-- fetch the first claim of satisfying the given property
local claims = findClaims(entity, property)
local gendernum
if not claims or not claims[1] then
if #claims > 1 then
if showerrors then return printError("property-not-found") else return default end
if anyunknown then
gendernum = 'p'
else
if anym and not anyf then gendernum = 'mp' end
if anyf and not anym then gendernum = 'fp' end
if anym and anyf then gendernum = 'mixtep' end
end
else
gendernum = 's'
if anym then gendernum = 'ms' end
if anyf then gendernum = 'fs' end
end
end
return gendernum
end


-- get initial sort indices
-- récupération des libellés genrés de Wikidata
local sortindices = {}
function wd.genderedlabel(id, labelgender)
for idx in pairs(claims) do
local label
sortindices[#sortindices + 1] = idx
if not labelgender then return nil end
if labelgender == 'f' then -- femme : chercher le libellé dans P2521 (libellé féminin)
label = wd.formatStatements{entity = id, property = 'P2521', isinlang = 'fr', numval = 1, ucfirst = '-'}
elseif labelgender == 'm' then -- homme : chercher le libellé dans P3321 (libellé masculin)
label = wd.formatStatements{entity = id, property = 'P3321', isinlang = 'fr', numval = 1, ucfirst = '-'}
end
end
-- sort by claim rank
if not label then
local comparator = function(a, b)
label = wd.getLabel(id)
local rankmap = { deprecated = 2, normal = 1, preferred = 0 }
local ranka = rankmap[claims[a].rank or "normal"] .. string.format("%08d", a)
local rankb = rankmap[claims[b].rank or "normal"] .. string.format("%08d", b)
return ranka < rankb
end
end
table.sort(sortindices, comparator)
return label
end


local result
 
local error
-- === FUNCTIONS FOR TRANSITIVE PROPERTIES ===
if list then
 
local value
function wd.getIds(item, query)
-- iterate over all elements and return their value (if existing)
query.excludespecial = true
result = {}
query.displayformat = 'raw'
for idx in pairs(claims) do
query.entity = item
local claim = claims[sortindices[idx]]
query.addstandardqualifs = '-'
value, error = getValueOfClaim(claim, qualifierId, parameter)
return wd.stringTable(query)
if not value and showerrors then value = error end
end
if value and references then value = value .. getReferences(frame, claim) end
 
result[#result + 1] = value
 
-- recursively adds a list of qid to an existing list, based on the results of a query
function wd.addVals(list, query, maxdepth, maxnodes, stopval)
maxdepth = tonumber(maxdepth) or 10
maxnodes = tonumber(maxnodes) or 100
if (maxdepth < 0) then
return list
end
if stopval and wd.isHere(list, stopval) then
return list
end
local origsize = #list
for i = 1, origsize do
-- tried a  "checkpos" param instead of starting to 1 each time, but no impact on performance
local candidates = wd.getIds(list[i], query)
list = wd.addNewValues(list, candidates, maxnodes, stopval)
if list[#list] == stopval then
return list
end
if #list >= maxnodes then
return list
end
end
result = table.concat(result, list)
else
-- return first element
local claim = claims[sortindices[1]]
result, error = getValueOfClaim(claim, qualifierId, parameter)
if result and references then result = result .. getReferences(frame, claim) end
end
end
if (#list == origsize) then
return list
end
return wd.addVals(list, query, maxdepth - 1, maxnodes, stopval, origsize + 1)
end


if result then return result else
-- returns a list of items transitively matching a query (orig item is not included in the list)
if showerrors then return error else return default end
 
function wd.transitiveVals(item, query, maxdepth, maxnodes, stopval)
maxdepth = tonumber(maxdepth) or 5
if type(query) == "string" then
query = {property = query}
end
end
end


-- look into entity object
-- récupération des valeurs
function p.ViewSomething(frame)
local vals = wd.getIds(item, query)
local f = (frame.args[1] or frame.args.id) and frame or frame:getParent()
if not vals then
local id = f.args.id
return nil
if id and (#id == 0) then
id = nil
end
end
local data = mw.wikibase.getEntity(id)
local v = wd.addVals(vals, query, maxdepth - 1, maxnodes, stopval)  
if not data then
if not v then
return nil
return nil
end
end


local i = 1
-- réarrangement des valeurs
while true do
if query.valorder == "inverted" then
local index = f.args[i]
local a = {}
if not index then
for i = #v, 1, -1 do
if type(data) == "table" then
a[#a+1] = v[i]
return mw.text.jsonEncode(data, mw.text.JSON_PRESERVE_KEYS + mw.text.JSON_PRETTY)
end
else
v = a
return tostring(data)
end
end
 
return v
end
 
-- returns true if an item is the value of a query, transitively
function wd.inTransitiveVals(searchedval, sourceval, query, maxdepth, maxnodes )
local vals = wd.transitiveVals(sourceval, query, maxdepth, maxnodes, searchedval )
if (not vals) then
return false
end
for _, val in ipairs(vals) do
if (val == searchedval) then
return true
end
end
end
return false
end


data = data[index] or data[tonumber(index)]
-- returns true if an item is a superclass of another, based on P279
if not data then
function wd.isSubclass(class, item, maxdepth)
return
local query = {property = 'P279'}
if class == item then -- item is a subclass of itself iff it is a class
if wd.getIds(item, query) then
return true
end
end
return false
end
return wd.inTransitiveVals(class, item, query, maxdepth )
end


i = i + 1
-- returns true if one of the best ranked P31 values of an item is the target or a subclass of the target
-- rank = 'valid' would seem to make sense, but it would need to check for date qualifiers as some P31 values have begin or end date
function wd.isInstance(targetclass, item, maxdepth)
maxdepth = maxdepth or 10
local directclasses = wd.transitiveVals(item, {property = 'P31'}, 1)
if not directclasses then
return false
end
for i, class in pairs(directclasses) do
if wd.isSubclass(targetclass, class, maxdepth - 1) then
return true
end
end
end
return false
end
end


-- getting sitelink of a given wiki
-- return the first value in a transitive query that belongs to a particular class. For instance find a value of P131 that is a province of Canada
-- get sitelink of current item if qid not supplied
function wd.findVal(sourceitem, targetclass, query, recursion, instancedepth)
function p.getSiteLink(frame)
if type(query) == "string" then
local qid = frame.args.qid
query = {property = query}
if qid == "" then qid = nil end
local f = mw.text.trim( frame.args[1] or "")
local entity = mw.wikibase.getEntity(qid)
if not entity then
return
end
end
local link = entity:getSitelink( f )
local candidates = wd.getIds(sourceitem, query)
if not link then
if candidates then
return
for i, j in pairs(candidates) do
if wd.isInstance(targetclass, j,  instancedepth) then
return j
end
end
if not recursion then
recursion = 3
else
recursion = recursion - 1
end
if recursion < 0 then
return nil
end
for i, candidate in pairs(candidates) do
return wd.findVal(candidate, targetclass, query, recursion, instancedepth)
end
end
end
return link
end
end


function p.Dump(frame)
local f = (frame.args[1] or frame.args.id) and frame or frame:getParent()
local data = mw.wikibase.getEntity(f.args.id)
if not data then
return i18n.warnDump
end


local i = 1
-- === VARIA ===
while true do
function wd.getDescription(entity, lang)
local index = f.args[i]
lang = lang or defaultlang
if not index then
return "<pre>"..mw.dumpObject(data).."</pre>".. i18n.warnDump
end


data = data[index] or data[tonumber(index)]
local description
if not data then
if lang == defaultlang then
return i18n.warnDump
return  mw.wikibase.description(qid)
end
end
if not entity.descriptions then
return wd.translate('no description')
end
local descriptions = entity.descriptions
if not descriptions then
return nil
end
if descriptions[lang] then
return descriptions[delang].value
end
return entity.id
end


i = i + 1
function wd.Dump(entity)
entity = wd.getEntity(entity)
if not entity then
return formatError("entity-param-not-provided")
end
end
return "<pre>"..mw.dumpObject(entity).."</pre>"
end
function wd.frameFun(frame)
local args = frame.args
local funname = args[1]
table.remove(args, 1)
return wd[funname](args)
end
end


return p
 
return wd

Dernière version du 19 septembre 2024 à 17:01

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

--script that retrieves basic data stored in Wikidata, for the datamodel, see https://www.mediawiki.org/wiki/Extension:Wikibase_Client/Lua

local wd = {}

-- creation of a subobject to store comparison funtions, used for sorting claims
-- to be able to build more complex sorts like topological sorts

wd.compare = {}

local databases = { }
local modules = { }
local databasesNames = { -- modules de données statiques pouvant être appelés avec mw.loadData(), ne nécessitant pas require()
	i18n = 'Module:Wikidata/I18n',
	globes = 'Module:Wikidata/Globes',
	langhierarchy = 'Module:Wikidata/Hiérarchie des langues',
	langcodes = 'Module:Dictionnaire Wikidata/Codes langue', -- big, infrequently useda
	invertedlangcodes = 'Module:Dictionnaire Wikidata/Codes langue/inversé'
}
local modulesNames = {
	reference = 'Module:Wikidata/Références',
	linguistic = 'Module:Linguistique',
	datemodule = 'Module:Date',
	formatDate = 'Module:Date complexe',
	formatNum = 'Module:Conversion',
	langmodule = 'Module:Langue',
	cite = 'Module:Biblio',
	weblink = 'Module:Weblink'
}

local function loadDatabase( t, key )
	if databasesNames[key] then
		local m = mw.loadData( databasesNames[key] )
		t[key] = m
		return m
	end
end
local function loadModule( t, key )
	if modulesNames[key] then
		local m = require( modulesNames[key] )
		t[key] = m
		return m
	end
end
setmetatable( databases, { __index = loadDatabase } )
setmetatable( modules, { __index = loadModule } ) -- ainsi le require() sera opéré seulement si nécessaire par modules.(nom du module)

local datequalifiers = {'P585', 'P571', 'P580', 'P582', 'P1319', 'P1326'}

-- === I18n ===
local defaultlang = mw.getContentLanguage():getCode()

function wd.translate(str, rep1, rep2)
	str = databases.i18n[str] or str
	if rep1 and (type (rep1) == 'string') then
		str = str:gsub('$1', rep1)
	end
	if rep2 and (type (rep2) == 'string')then
		str = str:gsub('$2', rep2)
	end
	return str
end

local function addCat(cat, sortkey)
	if sortkey then
		return  '[[Category:' .. cat .. '|' .. sortkey .. ']]'
	end
	return '[[Category:' .. cat  .. ']]'
end

local function formatError( key , category, debug)
    if debug then
        return error(databases.i18n[key] or key)
    end
    if category then
        return addCat(category, key)
    else
        return addCat('cat-unsorted-issue', key)
    end
end

-- 

function wd.isSpecial(snak)
	return (snak.snaktype ~= 'value')
end

function wd.getId(snak)
	if (snak.snaktype == 'value') then
		return 'Q' .. snak.datavalue.value['numeric-id']
	end
end

function wd.getNumericId(snak)
	if (snak.snaktype == 'value') then
		return snak.datavalue.value['numeric-id']
	end
end

function wd.getMainId(claim)
	return wd.getId(claim.mainsnak)
end

function wd.entityId(entity)
	if type(entity) == 'string' then
		return entity
	elseif type(entity) == 'table' then
		return entity.id
	end
end

function wd.getEntityIdForCurrentPage()
	return mw.wikibase.getEntityIdForCurrentPage()
end

-- function that returns true if the "qid" parameter is the qid 
-- of the item that is linked to the calling page
function wd.isPageOfQId(qid) 
	local self_id = mw.wikibase.getEntityIdForCurrentPage() 
	return self_id ~= nil and qid == self_id 
end

function wd.getEntity( val ) 
	if type(val) == 'table' then
		return val
	end
	if val == '-' then
		return nil
	end
	if val == '' then
		val = nil
	end
	return mw.wikibase.getEntity(val)
end

function wd.splitStr(val) -- transforme en table les chaînes venant du Wikitexte qui utilisent des virgules de séparation
	if type(val) == 'string' then
		val = mw.text.split(val, ",")
	end
	return val
end

function wd.isHere(searchset, val, matchfunction)
	for i, j in pairs(searchset) do
		if matchfunction then
			if matchfunction(val,j) then
				return true
			end
		else
			if val == j then
				return true
			end
		end
	end
	return false
end


local function wikidataLink(entity)
	local name =':d:'
	
	if type(entity) == 'string' then
		if entity:match("P[0-9]+") then
			entity = "Property:" .. entity
		end
		return name .. entity
	elseif type(entity) == 'table' then
		if entity["type"] == "property" then
			name = ":d:Property:"
		end
		return name .. entity.id
	elseif type(entity) == nil then
		return formatError('entity-not-found')
	end
end

function wd.siteLink(entity, project, lang)
	-- returns 3 values: a sitelink (with the relevant prefix) a project name and a language
	lang = lang or defaultlang
	if (type(project) ~= 'string') then
		project = 'wiki'
	end
	project = project:lower()
	if project == 'wikipedia' then
		project = 'wiki'
	end
	if type(entity) == 'string' and (project == 'wiki') and ( (not lang or lang == defaultlang) ) then -- évite de charger l'élément entier
		local link = mw.wikibase.getSitelink(entity)
		if link then
			local test_redirect = mw.title.new(link) -- remplacement des redirections (retirer si trop coûteux)
			if test_redirect.isRedirect and test_redirect.redirectTarget then
				link = test_redirect.redirectTarget.fullText
			end
		end
		return  link, 'wiki', defaultlang
	end
	if project == 'wikidata' then
		return wikidataLink(entity), 'wikidata'
	end
	local projects = {
		-- nom = {préfixe sur Wikidata, préfix pour les liens sur Wikipédia, ajouter préfixe de langue}
		wiki = {'wiki', nil, true}, -- wikipedia
		commons = {'commonswiki', 'commons', false},
		commonswiki = {'commonswiki', 'commons', false},
		wikiquote = {'wikiquote', 'q', true},
		wikivoyage = {'wikivoyage', 'voy', true},
		wikibooks = {'wikibooks', 'b', true},
		wikinews = {'wikinews', 'n', true},
		wikiversity = {'wikiversity', 'v', true},
		wikisource = {'wikisource', 's', true},
		wiktionary = {'wiktionary', 'wikt', true},
		specieswiki = {'specieswiki', 'species', false},
		metawiki = {'metawiki', 'm', false},
		incubator = {'incubator', 'incubator', false},
		outreach = {'outreach', 'outreach', false},
		mediawiki = {'mediawiki', 'mw', false}
	}

	local entityid = entity.id or entity

	local projectdata = projects[project:lower()]
	if not projectdata then -- defaultlink might be in the form "dewiki" rather than "project: 'wiki', lang: 'de' "
		for k, v in pairs(projects) do
			if project:match( k .. '$' ) 
				and mw.language.isKnownLanguageTag(project:sub(1, #project-#k))
			then
				lang = project:sub(1, #project-#k)
				project = project:sub(#lang + 1, #project)
				projectdata = projects[project]
				break
			end
		end
		if not mw.language.isKnownLanguageTag(lang) then
			return --formatError('invalid-project-code', projet or 'nil')
		end
	end
	if not projectdata then
		return -- formatError('invalid-project-code', projet or 'nil')
	end
	
	local linkcode = projectdata[1]
	local prefix = projectdata[2]
	local multiversion = projectdata[3]
	if multiversion then
		linkcode = lang .. linkcode
	end
	local link = mw.wikibase.getSitelink(entityid, linkcode)
	if not link then
		return nil
	end
	
	if prefix then
		link = prefix .. ':' .. link
	end
	if multiversion then
		link = ':' .. lang .. ':' .. link
	end
	return link, project, lang
end

-- add new values to a list, avoiding duplicates
function wd.addNewValues(olditems, newitems, maxnum, stopval)
	if not newitems then
		return olditems
	end
	for _, qid in pairs(newitems) do
		if stopval and (qid == stopval) then
			table.insert(olditems, qid)
			return olditems
		end
		if maxnum and (#olditems >= maxnum) then
			return olditems
		end
		if not wd.isHere(olditems, qid) then
			table.insert(olditems, qid)
		end
	end
	return olditems
end

--=== FILTER CLAIMS ACCORDING TO VARIOUS CRITERIA : FUNCTION GETCLAIMS et alii ===

local function notSpecial(claim)
	local type
	
	if claim.mainsnak ~= nil then
		type = claim.mainsnak.snaktype
	else
		-- condition respectée quand showonlyqualifier est un paramètre renseigné
		-- dans ce cas, claim n'est pas une déclaration entière, mais UNE snak qualifiée du main snak
		type = claim.snaktype
	end
	
	return type == 'value'
end

local function hasTargetValue(claim, targets) -- retourne true si la valeur est dans la liste des target, ou si c'est une valeur spéciale filtrée séparément par excludespecial
	local id = wd.getMainId(claim)
	local targets = wd.splitStr(targets)
	return wd.isHere(targets, id) or wd.isSpecial(claim.mainsnak)
end

local function excludeValues(claim, values) -- true si la valeur n'est pas dans la liste, ou si c'est une valeur spéciale (filtrée à part par excludespecial)
	return wd.isSpecial(claim.mainsnak) or not ( hasTargetValue(claim, values) )
end

local function hasTargetClass(claim, targets, maxdepth) -- retourne true si la valeur est une instance d'une classe dans la liste des target, ou si c'est une valeur spéciale filtrée séparément par excludespecial
	local id = wd.getMainId(claim)
	local targets = wd.splitStr(targets)
	local maxdepth = maxdepth or 10
	local matchfunction = function(value, target) return wd.isInstance(target, value, maxdepth) end
	return wd.isHere(targets, id, matchfunction) or wd.isSpecial(claim.mainsnak)
end

local function excludeClasses(claim, classes) -- true si la valeur n'est pas une instance d'une classe dans la liste, ou si c'est une valeur spéciale (filtrée à part par excludespecial)
	return wd.isSpecial(claim.mainsnak) or not ( hasTargetClass(claim, classes, maxdepth) )
end

local function hasTargetSuperclass(claim, targets, maxdepth) -- retourne true si la valeur est une sous-classe d'une classe dans la liste des target, ou si c'est une valeur spéciale filtrée séparément par excludespecial
	local id = wd.getMainId(claim)
	local targets = wd.splitStr(targets)
	local maxdepth = maxdepth or 10
	local matchfunction = function(value, target) return wd.isSubclass(target, value, maxdepth) end
	return wd.isHere(targets, id, matchfunction) or wd.isSpecial(claim.mainsnak)
end

local function excludeSuperclasses(claim, classes) -- true si la valeur n'est pas une sous-classe d'une classe dans la liste, ou si c'est une valeur spéciale (filtrée à part par excludespecial)
	return wd.isSpecial(claim.mainsnak) or not ( hasTargetSuperclass(claim, classes, maxdepth) )
end


local function bestRanked(claims)
	if not claims then
		return nil
	end
	local preferred, normal = {}, {}
	for i, j in pairs(claims) do
		if j.rank == 'preferred' then
			table.insert(preferred, j)
		elseif j.rank == 'normal' then
			table.insert(normal, j)
		end
	end
	if #preferred > 0 then
		return preferred
	else
		return normal
	end
end

local function withRank(claims, target)
	if target == 'best' then
		return bestRanked(claims)
	end
	local newclaims = {}
	for pos, claim in pairs(claims)  do
		if target == 'valid' then
			if claim.rank ~= 'deprecated' then
				table.insert(newclaims, claim)
			end
		elseif claim.rank == target then
			table.insert(newclaims, claim)
		end
	end
	return newclaims
end

function wd.hasQualifier(claim, acceptedqualifs, acceptedvals, excludequalifiervalues)
	local claimqualifs = claim.qualifiers
	
	if (not claimqualifs) then
		return false
	end

	acceptedqualifs = wd.splitStr(acceptedqualifs)
	acceptedvals = wd.splitStr( acceptedvals)


	local function ok(qualif) -- vérification pour un qualificatif individuel
		if not claimqualifs[qualif] then
			return false
		end
		if not (acceptedvals) then  -- si aucune valeur spécifique n'est demandée, OK
			return true
		end
		for i, wanted in pairs(acceptedvals) do
			for j, actual in pairs(claimqualifs[qualif]) do
				if wd.getId(actual) == wanted then
					return true
				end
			end
		end
	end

	for i, qualif in pairs(acceptedqualifs) do
		if ok(qualif) then
			return true
		end
	end
	return false
end

function wd.hasQualifierNumber(claim, acceptedqualifs, acceptedvals, excludequalifiervalues)
	local claimqualifs = claim.qualifiers
	
	if (not claimqualifs) then
		return false
	end

	acceptedqualifs = wd.splitStr(acceptedqualifs)
	acceptedvals = wd.splitStr( acceptedvals)


	local function ok(qualif) -- vérification pour un qualificatif individuel
		if not claimqualifs[qualif] then
			return false
		end
		if not (acceptedvals) then  -- si aucune valeur spécifique n'est demandée, OK
			return true
		end
		for i, wanted in pairs(acceptedvals) do
			for j, actual in pairs(claimqualifs[qualif]) do
				if mw.wikibase.renderSnak(actual) == wanted then
					return true
				end
			end
		end
	end

	for i, qualif in pairs(acceptedqualifs) do
		if ok(qualif) then
			return true
		end
	end
	return false
end

local function hasSource(claim, targetsource, sourceproperty)
	sourceproperty = sourceproperty or 'P248'
	if targetsource == "-" then
		return true
	end
	if (not claim.references) then return
		false
	end
	local candidates = claim.references[1].snaks[sourceproperty] -- les snaks utilisant la propriété demandée
	if (not candidates) then
		return false
	end
	if (targetsource == "any") then -- si n'importe quelle valeur est acceptée tant qu'elle utilise en ref la propriété demandée
		return true
	end
	targetsource = wd.splitStr(targetsource)
	for _, source in pairs(candidates) do
		local s = wd.getId(source)
		for i, target in pairs(targetsource) do
			if s == target then return true end
		end
	end
	return false
end

local function excludeQualifier(claim, qualifier, qualifiervalues)
	return not wd.hasQualifier(claim, qualifier, qualifiervalues)
end

function wd.hasDate(claim)
	if not claim then
		return false --error() ?
	end
	if wd.getDateFromQualif(claim, 'P585') or wd.getDateFromQualif(claim, 'P580') or wd.getDateFromQualif(claim, 'P582') then
		return true
	end
	return false
end

local function hasLink(claim, site, lang)
	if (claim.mainsnak.snaktype ~= 'value') then -- ne pas supprimer les valeurs spéciales, il y a une fonction dédiée pour ça
		return true
	end
	local id = wd.getMainId(claim)
	local link = wd.siteLink(id, site, lang)
	if link then
		return true
	end
end

local function isInLanguage(claim, lang) -- ne fonctionne que pour les monolingualtext / étendre aux autres types en utilisant les qualifiers ?
	if type(lang) == 'table' then -- si c'est une table de language séparées par des virgules, on les accepte toutes
		for i, l in pairs(lang) do
			local v = isInLanguage(claim, l)
			if v then
				return true
			end
		end
	end
	if type(lang) ~= ('string') then
		return --?
	end

	if (lang == '-') then
		return true
	end
	if (lang == 'locallang') then
		lang =  mw.getContentLanguage():getCode()
	end
	
	-- pour les monolingual text
	local snak = claim.mainsnak or claim
	if snak.snaktype == 'value' and snak.datavalue.type == 'monolingualtext' then
		if snak.datavalue.value.language == lang then
			return true
		end
		return false
	end

	-- pour les autres types de données : recherche dans les qualificatifs
	if (lang == 'fr') then
		lang = 'Q150'
	elseif (lang == 'en') then
		lang = 'Q1860'
	else
		lang = databases.invertedlangcodes[lang]
	end
	if claim.qualifiers and claim.qualifiers.P407 then
		if wd.hasQualifier(claim, {'P407'}, {lang}) then
			return true
		else
			return false
		end
	end

	return true -- si on ne ne sait pas la langue, on condière que c'est bon
end

local function firstVals(claims, numval) -- retourn les numval premières valeurs de la table claims
    local numval = tonumber(numval) or 0 -- raise a error if numval is not a positive integer ?
    if not claims then
    	return nil
    end
    while (#claims > numval) do
    	table.remove(claims)
    end
    return claims
end

local function lastVals(claims, numval2) -- retourn les valeurs de la table claims à partir de numval2
    local numval2 = tonumber(numval2) or 0 -- raise a error if numval is not a positive integer ?
    if not claims then
    	return nil
    end
    for i=1,numval2 do
    	table.remove(claims, 1)
    end
    return claims
end

-- retourne les valeurs de la table claims à partir de removedupesdate,
-- sans les dates en doublons avec conversion entre les calendrier julien et grégorien,
-- ou uniquement en catégorisant si le paramètre removedupesdate est égale à 'cat'
local function removeDupesDate(claims, removedupesdate)
	if not claims or #claims < 2 then
		return claims, ''
	end
	local cat = ''
	local newClaims = {}
	local newIsos = {}

	local function findIndex(searchset, val) -- similaire à wd.isHere mais retourne l'index de la valeur trouvée
		for i, j in pairs(searchset) do
			if val == j then
				return i
			end
		end
		return -1
	end

	for _, claim in ipairs( claims ) do
		local snak = claim.mainsnak or claim
		if (snak.snaktype == 'value') and (snak.datatype == 'time') and snak.datavalue.value.precision >= 11 then -- s'il s'agit d'un time et que la précision est au moins l'année
			local iso = snak.datavalue.value.time
			_, _, iso = string.find(iso, "(+%d+-%d+-%d+T)")
			local deleteIfDuplicate = false
			if snak.datavalue.value.calendarmodel == 'http://www.wikidata.org/entity/Q1985727' then -- si la date est grégorienne
				if modules.formatDate.before('+1582', iso) then -- si avant 1582 on calcule la date julienne
					_, _, y, m, d = string.find(iso, "+(%d+)-(%d+)-(%d+)T")
					y, m , d = modules.datemodule.gregorianToJulian(y, m , d)
					if m < 10 then m = '0' .. m end
					if d < 10 then d = '0' .. d end
					iso = '+' .. y .. '-' .. m .. '-' .. d .. 'T'
					deleteIfDuplicate = true
				end
				local index = findIndex(newIsos, iso)
				if index >= 0 then -- si la date est déjà présente
					cat = cat .. '[[Catégorie:Article avec des dates identiques venant de wikidata dans le code de l\'infobox]]'
					if removedupesdate == "cat" then -- ne faire que catégoriser
						table.insert(newIsos, iso)
						table.insert(newClaims, claim)
					elseif not deleteIfDuplicate then -- supprimer l'autre date si la date courante n'a pas été convertie
						newClaims[index] = claim
					end -- sinon supprimer la date courante
				else -- pas de doublon
					table.insert(newIsos, iso)
					table.insert(newClaims, claim)
				end
			elseif snak.datavalue.value.calendarmodel == 'http://www.wikidata.org/entity/Q1985786' then -- si date julienne
				if not modules.formatDate.before('+1582', iso) then -- si après 1582 on calcule la date grégorienne
					_, _, y, m, d = string.find(iso, "+(%d+)-(%d+)-(%d+)T")
					y, m , d = modules.datemodule.julianToGregorian(y, m , d)
					if m < 10 then m = '0' .. m end
					if d < 10 then d = '0' .. d end
					iso = '+' .. y .. '-' .. m .. '-' .. d .. 'T'
					deleteIfDuplicate = true
				end
				local index = findIndex(newIsos, iso)
				if index >= 0 then -- si date déjà présente
					cat = cat .. '[[Catégorie:Article avec des dates identiques venant de wikidata dans le code de l\'infobox]]'
					if removedupesdate == "cat" then -- ne faire que catégoriser
						table.insert(newIsos, iso)
						table.insert(newClaims, claim)
					elseif not deleteIfDuplicate then -- supprimer l'autre date si la date courante n'a pas été convertie
						newClaims[index] = claim
					end -- sinon supprimer la date courante
				else -- pas de doublon
					table.insert(newIsos, iso)
					table.insert(newClaims, claim)
				end
			else -- autre calendrier
				table.insert(newIsos, iso)
				table.insert(newClaims, claim)
			end
		else -- précision insuffisante
			table.insert(newIsos, iso)
			table.insert(newClaims, claim)
		end
	end
	return newClaims, cat
end

local function timeFromQualifs(claim, qualifs)
	local claimqualifs = claim.qualifiers
	if not claimqualifs then
		return nil
	end
	for i, qualif in ipairs(qualifs or datequalifiers) do
		local vals = claimqualifs[qualif]
		if vals and (vals[1].snaktype == 'value') then
			return vals[1].datavalue.value.time, vals[1].datavalue.value.precision
		end
	end
end

local function atDate(claim, mydate) 
	if mydate == "today" then
		mydate = os.date("!%Y-%m-%dT%TZ")
	end
	-- determines required precision depending on the atdate format
	local d = mw.text.split(mydate, "-")

	local myprecision
	if d[3] then
		myprecision =  11 -- day
	elseif d[2] then
		myprecision = 10 -- month
	else
		myprecision = 9 -- year
 	end

	-- with point in time
	local d, storedprecision = timeFromQualifs(claim, {'P585'})
	if d then
		return modules.formatDate.equal(mydate, d, math.min(myprecision, storedprecision))
	end
	-- with start or end date -- TODO: precision 
	local mindate = timeFromQualifs(claim, {'P580'}) 
	local maxdate = timeFromQualifs(claim, {'P582'})
	if modules.formatDate.before(mydate, mindate) and  modules.formatDate.before(maxdate, mydate) then
		return true
	end
	return false
end

local function check(claim, condition)
	if type(condition) == 'function' then -- cas standard
		return condition(claim)
	end
	return formatError('invalid type', 'function', type(condition)) 
end

local function minPrecision(claim, minprecision)
	local snak
	if claim.qualifiers then -- si une date est donnée en qualificatif, c'est elle qu'on utilise de préférence au mainsnak
		for i, j in ipairs(datequalifiers) do
			if claim.qualifiers[j] then
				snak = claim.qualifiers[j][1] 
				break
			end
		end
	end
	if not snak then
		snak = claim.mainsnak or claim
	end
	if (snak.snaktype == 'value') and (snak.datatype == 'time') and (snak.datavalue.value.precision < minprecision) then
		return false
	end
	return true
end

function wd.sortClaims(claims, sorttype)
	if not claims then
		return nil
	end
	if wd.isHere({'chronological', 'order', 'inverted', 'age', 'ageinverted'}, sorttype) then
		return wd.chronoSort(claims, sorttype)
	elseif sorttype == 'ascending' then
		return wd.quantitySort(claims)
	elseif sorttype == 'descending' then
		return wd.quantitySort(claims, true)
	elseif type(sorttype) == 'function' then
		table.sort(claims, sorttype)
		return claims
	elseif type(sorttype) == 'string' and sorttype:sub(1, 1) == 'P' then
		return wd.numericPropertySort(claims, sorttype)
	end
	return claims
end

function wd.filterClaims(claims, args) --retire de la tables de claims celles qui sont éliminés par un des filters de la table des filters

	local function filter(condition, filterfunction, funargs)
		if not args[condition] then
			return
		end
		for i = #claims, 1, -1 do
			if not( filterfunction(claims[i], args[funargs[1]], args[funargs[2]], args[funargs[3]]) ) then
				table.remove(claims, i)
			end
		end
	end
	filter('isinlang', isInLanguage, {'isinlang'} )
	filter('excludespecial', notSpecial, {} )
	filter('condition', check, {'condition'} )
	if claims[1] and claims[1].mainsnak then
		filter('targetvalue', hasTargetValue, {'targetvalue'} )
		filter('targetclass', hasTargetClass, {'targetclass'} )
		filter('targetsuperclass', hasTargetSuperclass, {'targetsuperclass'} )
		filter('atdate', atDate, {'atdate'} )
		filter('qualifier', wd.hasQualifier, {'qualifier', 'qualifiervalue'} )
		filter('qualifiernumber', wd.hasQualifierNumber, {'qualifiernumber', 'qualifiernumbervalue'} )
		filter('excludequalifier', excludeQualifier, {'excludequalifier', 'excludequalifiervalue'} )
		filter('withsource', hasSource, {'withsource', 'sourceproperty'} )
		filter('withdate', wd.hasDate, {} )
		filter('excludevalues', excludeValues, {'excludevalues'})
		filter('excludeclasses', excludeClasses, {'excludeclasses'})
		filter('excludesuperclasses', excludeSuperclasses, {'excludesuperclasses'})
		filter('withlink', hasLink, {'withlink', 'linklang'} )
		filter('minprecision', minPrecision, {'minprecision'} )
		claims = withRank(claims, args.rank or 'best')
	end
	if #claims == 0 then
		return nil
	end
	if args.sorttype then
		claims = wd.sortClaims(claims, args.sorttype)
	end
	if args.numval2 then
		claims = lastVals(claims, args.numval2)
	end
	if args.numval then
		claims = firstVals(claims, args.numval)
	end
	return claims

end

function wd.loadEntity(entity, cache)
	if type(entity) ~= 'table' then
		if cache then
			if not cache[entity] then 
				cache[entity] = mw.wikibase.getEntity(entity)
				mw.log("cached")
     		end
			return cache[entity]
        else
			if entity == '' or (entity == '-') then
				entity = nil
			end
        	return mw.wikibase.getEntity(entity)
        end
    else 
    	return entity
    end
end


function wd.getClaims( args ) -- returns a table of the claims matching some conditions given in args
	if args.claims then -- if claims have already been set, return them
		return args.claims
	end
	local properties = args.property
	if type(properties) == 'string' then
		properties = wd.splitStr(string.upper(args.property))
	end
	if not properties then
		return formatError( 'property-param-not-provided' )
	end

	--Get entity
	local entity = args.entity
	if type(entity) == 'string' then
		if entity == '' then
			entity = nil
		end
	elseif type(entity) == 'table' then
		entity = entity.id
	end
        if (not entity) then
            entity = mw.wikibase.getEntityIdForCurrentPage()
        end
	if (not entity) or (entity == '-') or (entity == wd.translate('somevalue')) or (entity == modules.linguistic.ucfirst(wd.translate('somevalue'))) then
		return nil
	end
	if args.labelformat and args.labelformat == 'gendered' then
		local longgender = {m = 'male', f = 'female'}
		args.labelformat = longgender[wd.getgender(entity)]
	end
	local claims = {}

	if #properties == 1 then
		claims = mw.wikibase.getAllStatements(entity, properties[1]) -- do not use mw.wikibase.getBestStatements at this stage, as it may remove the best ranked values that match other criteria in the query
	else
		for i, prop in ipairs(properties) do
			local newclaims = mw.wikibase.getAllStatements(entity, prop) 
			if newclaims and #newclaims > 0 then
				for j, claim in ipairs(newclaims) do
					table.insert(claims, claim)
				end
			end
		end
	end


	if (not claims) or (#claims == 0) then
		return nil
	end
	return wd.filterClaims(claims, args)
end

--=== ENTITY FORMATTING ===

function wd.getLabel(entity, lang1, lang2)
	
	if (not entity) then
		return nil -- ou option de gestion des erreurs ?
	end
	entity = entity.id or ( type(entity) == "string" and entity)
	if not(type(entity) == 'string') then return nil end
	
	lang1 = lang1 or defaultlang
	
	local str, lang --str : texte rendu, lang : langue de celui-ci
	if lang1 == defaultlang then -- le plus économique
		str, lang = mw.wikibase.getLabelWithLang(entity) -- le libellé peut être en français ou en anglais
	else
		str = mw.wikibase.getLabelByLang(entity, lang1)
		if str then lang = lang1 end
	end
	if str and (lang == lang1 or lang == "mul") then --pas de catégorie "à traduire" si on a obtenu un texte dans la langue désirée (normalement fr) ou multilingue
		return str
	end
	if lang2 then -- langue secondaire, avec catégorie "à traduire"
		str2 = mw.wikibase.getLabelByLang(entity, lang2)
		if str2 then
			lang = lang2
			str = str2
		end
	end
	if not str then --si ni lang1, ni lang2 ni l'anglais ne sont présents,  parcours de la hiérarchie des langues
		for _, trylang in ipairs(databases.langhierarchy.codes) do
			str = mw.wikibase.getLabelByLang(entity, trylang)
			if str then
				lang = trylang
				break
			end
		end
	end
		
	if str then
		local translationCat = databases.i18n['to translate']
		translationCat = translationCat .. (databases.langhierarchy.cattext[lang] or '')
		translationCat = addCat(translationCat)
		return str, translationCat
	end
end

function wd.formatEntity( entity, params )

	if (not entity) then
		return nil --formatError('entity-not-found')
	end
	local id = entity
	if type(id) == 'table' then
		id = id.id
	end

	params = params or {}
	local lang = params.lang or defaultlang
	local speciallabels = params.speciallabels
	local displayformat = params.displayformat
	local labelformat = params.labelformat
	local labelformat2 = params.labelformat2
	local defaultlabel = params.defaultlabel or id
	local linktype = params.link
	local defaultlink = params.defaultlink
	local defaultlinkquery = params.defaultlinkquery

	if speciallabels and speciallabels[id] then --speciallabels override the standard label + link combination
		return speciallabels[id]
	end
	if params.displayformat == 'raw' then
		return id
	end
	if params.labelformat == 'male' then
		labelformat = function(objectid) return wd.genderedlabel(objectid, 'm') end
	end
	if params.labelformat == 'female' then
		labelformat = function(objectid) return wd.genderedlabel(objectid, 'f') end
	end

	local label, translationCat
	
	if type(labelformat) == 'function' then -- sert à des cas particuliers
		label, translationCat = labelformat(entity)
	end
	
	if not label then
		label, translationCat = wd.getLabel(entity, lang, params.wikidatalang)
	end

	if type(labelformat2) == 'function' and label then -- sert à des cas particuliers
		label = labelformat2(label)
	end
	
	translationCat = translationCat or "" -- sera toujours ajoutée au résultat mais sera vide si la catégorie de maintenance n'est pas nécessaire
	
	if not label then
		if (defaultlabel == '-') then 
			return nil
		end
		local link = wd.siteLink(id, 'wikidata')
		return '[[' .. link .. '|' .. id .. ']]' .. translationCat
-- si pas de libellé, on met un lien vers Wikidata pour qu'on comprenne à quoi ça fait référence
	end

	-- détermination du fait qu'on soit ou non en train de rendre l'élément sur la page de son article
	local rendering_entity_on_its_page = wd.isPageOfQId(id)
	
	if (linktype == '-') or rendering_entity_on_its_page then
		return label .. translationCat
	end

	local link = wd.siteLink(entity, linktype, lang)

	-- defaultlinkquery will try to link to another page on this Wiki
	if (not link) and defaultlinkquery then
		if type(defaultlinkquery) == 'string' then
				defaultlinkquery = {property = defaultlinkquery}
		end
		defaultlinkquery.excludespecial = true
		defaultlinkquery.entity = entity
		local claims = wd.getClaims(defaultlinkquery)
		if claims then
			for i, j in pairs(claims) do
				local id = wd.getMainId(j)
				link = wd.siteLink(id, linktype, lang)
				if link then
					break
				end
			end
		end
	end

	if link then
		if link:match('^Category:') or link:match('^Catégorie:') then -- attention, le « é » est multibyte
			-- lier vers une catégorie au lieu de catégoriser
			link = ':' .. link
		end
		return '[[' .. link .. '|' .. label .. ']]' .. translationCat
	end

	-- if not link, you can use defaultlink: a sidelink to another Wikimedia project
	if (not defaultlink) then
		defaultlink = {'enwiki'}
	end
	if defaultlink and (defaultlink ~= '-') then
		local linktype
		local sidelink, site, langcode
	
		if type(defaultlink) == 'string' then
			defaultlink = {defaultlink}
		end
		for i, j in ipairs(defaultlink) do
			sidelink, site, langcode = wd.siteLink(entity, j, lang)
			if sidelink then
				break
			end
		end
		if not sidelink then
			sidelink, site = wd.siteLink(entity, 'wikidata')
		end
		
		local icon, class, title = site, nil, nil -- le texte affiché du lien
		if site == 'wiki' then
			icon, class, title = langcode, "indicateur-langue", wd.translate('see-another-language', mw.language.fetchLanguageName(langcode, defaultlang))	
		elseif site == 'wikidata' then
			icon, class, title = 'd',  "indicateur-langue", wd.translate('see-wikidata')		
		else
			title = wd.translate('see-another-project', site)
		end
		local val = '[[' .. sidelink .. '|' .. '<span class = "' .. (class or '').. '" title = "' .. (title or '') .. '">' .. icon .. '</span>]]'
		return label .. ' <small>(' .. val .. ')</small>' .. translationCat
	end 
	return label .. translationCat
end


function wd.addTrackingCat(prop, cat) -- doit parfois être appelé par d'autres modules
	if type(prop) == 'table' then
		prop = prop[1] -- devrait logiquement toutes les ajouter
	end
	if not prop and not cat then
		return formatError("property-param-not-provided")
	end
	if not cat then
		cat = wd.translate('trackingcat', prop or 'P??')
	end
	return addCat(cat )
end

local function unknownValue(snak, label)
	local str = label

	if type(str) == "function" then
		str = str(snak)
	end

	if (not str) then
		if snak.datatype == 'time' then
			str = wd.translate('sometime')
		else
			str = wd.translate('somevalue')
		end
	end

	if type(str) ~= "string" then
		return formatError(snak.datatype)
	end
	return str
end

local function noValue(displayformat)
	if not displayformat then
		return wd.translate('novalue')
	end
	if type(displayformat) == 'string' then
		return displayformat
	end
	return formatError()
end

local function getLangCode(entityid)
	return databases.langcodes[tonumber(entityid:sub(2))]
end

local function showLang(statement) -- retourne le code langue entre parenthèses avant la valeur (par exemple pour les biblios et liens externes)
	local mainsnak = statement.mainsnak
	if mainsnak.snaktype ~= 'value' then
		return nil
	end
	local langlist = {}
	if mainsnak.datavalue.type == 'monolingualtext' then
		langlist = {mainsnak.datavalue.value.language}
	elseif (not statement.qualifiers) or (not statement.qualifiers.P407) then
		return
	else
		for i, j in pairs( statement.qualifiers.P407 ) do
			if  j.snaktype == 'value' then
				local langentity = wd.getId(j)
				local langcode =  getLangCode(langentity)
				table.insert(langlist, langcode)
			end
		end
	end
	if (#langlist > 1) or (#langlist == 1 and langlist[1] ~= defaultlang) then -- si c'est en français, pas besoin de le dire
		langlist.maxLang = 3
		return modules.langmodule.indicationMultilingue(langlist)
	end
end


-- === DATE HANDLING ===

local function fuzzydate(str, precision) -- ajoute le qualificatif "vers" à une date
	if not str then
		return nil
	end
	if (precision >= 11) or (precision == 7) or (precision == 6)  then --dates avec jour, siècles, millénaires
		return "vers le " .. str
	end
	if (precision == 8) then --décennies ("années ...")
		return "vers les " .. str
	end
	return "vers " .. str
end

function wd.addStandardQualifs(str, statement, onlygeneral)
	-- qualificateurs de date ou de lieu approximatif ou d'info globalement incertaine ; onlygenereal=true pour rerstreindre à ces derniers
	if (not statement) or (not statement.qualifiers) then
		return str
	end
	if not str then
		return error()-- what's that ?
	end

	if statement.qualifiers.P1480 then
		for i, j in pairs(statement.qualifiers.P1480) do
			local v = wd.getId(j)
			if (v == "Q21818619") and not onlygeneral then --"à proximité de"
				str = wd.translate('approximate-place', str)
			elseif (v == "Q18122778") or (v == "Q18912752") or (v == "Q56644435") or (v == "Q30230067") then --"présumé", "controversé", "probablement", "possible"
				str = wd.translate('uncertain-information', str)
			elseif (v == "Q5727902") and not onlygeneral and statement.mainsnak.datatype == 'time' then --date approximative
				local datevalue = statement.mainsnak.datavalue
				if datevalue then str = fuzzydate(str, datevalue.value.precision) end
			end			
		end
	end
	return str
end

function wd.getDateFromQualif(statement, qualif)
	if (not statement) or (not statement.qualifiers) or not (statement.qualifiers[qualif]) then
		return nil
	end
	local v = statement.qualifiers[qualif][1]
	if v.snaktype ~= 'value' then -- que faire dans ce cas ?
		return nil
	end
	return modules.formatDate.dateObject(v.datavalue.value)
end

function wd.getDate(statement)
	local period = wd.getDateFromQualif(statement, 'P585') -- retourne un dateobject
	if period then
		return period
	end
	local begin, ending = wd.getDateFromQualif(statement, 'P580'),  wd.getDateFromQualif(statement, 'P582')
	if begin or ending then
		return modules.formatDate.rangeObject(begin, ending) -- retourne un rangeobject fait de deux dateobject
	end
	return nil
end

function wd.getFormattedDate(statement, params)
	if not statement then
		return nil
	end
	local str

	--cherche la date avec les qualifs P580/P582
	local datetable = wd.getDate(statement)
	if datetable then
		str = modules.formatDate.objectToText(datetable, params)
	end
	
	-- puis limite intérieur / supérieur
	if not str then
		local start, ending = wd.getDateFromQualif(statement, 'P1319'), wd.getDateFromQualif(statement, 'P1326')
		str = modules.formatDate.between(start, ending, params)
	end
	
	local fromqualif = false
	if str then fromqualif = true end --si la date est tirée des qualificateurs, on n'y ajoute pas l'éventuel "vers ..."

	 -- sinon, le mainsnak, pour les données de type time
	if (not str) and (statement.mainsnak.datatype == 'time') then
		local mainsnak = statement.mainsnak
		if (mainsnak.snaktype == 'value') or (mainsnak.snaktype == 'somevalue') then
			str = wd.formatSnak(mainsnak, params)
		end
	end
	
	if str and params and (params.addstandardqualifs ~= '-') then
		str = wd.addStandardQualifs(str, statement, fromqualif)
	end

	return str
end

wd.compare.by_quantity = function(c1, c2)
	local v1 = wd.getDataValue(c1.mainsnak)
	local v2 = wd.getDataValue(c2.mainsnak) 
	if not (v1 and v2) then
			return true
	end
	return v1 < v2
end

--[[ tri chronologique générique : 
             retourne une fonction de tri de liste de déclaration
             en fonction d’une fonction qui calcule la clé de tri
             et d’une fonction qui compare les clés de tri
   
   paramètres nommés: (appel type wikidata.compare.chrono_key_sort{sortKey="nom clé"})
        sortKey (optionnel)   : chaine, le nom de la clé utilisée pour un tri 
                                (pour éviter de rentrer en collision avec "dateSortKey" 
                                utilisé par chronoSort au besoin)
        snak_key_get_function : fonction qui calcule la valeur de la clé à partir d’un snak ou d’une déclaration,
        (obligatoire)           le résultat n’est calculé qu’une fois et est stocké en cache dans claim[sortKey]
        key_compare_function  : fonction de comparaison des clés calculées par snak_key_get_function
        (optionnel)
--]]    

function wd.chrono_key_sort(arg)
	
	local snak_key_get_function = arg.snak_key_get_function
	local sortKey = arg.sortKey or "dateSortKey"
	local key_compare_function = arg.key_compare_function or 
									function(c1, c2) return c1 < c2 end
	return function(claims)
		for _, claim in ipairs( claims ) do
			if not claim[sortKey] then
				local key = snak_key_get_function(claim)
				if key then
					claim[sortKey] = wd.compare.get_claim_date(key)
				else
					claim[sortKey] = 0
				end
			end
		end
		table.sort(
			claims,
			function(c1, c2)
				return key_compare_function(c1[sortKey], c2[sortKey])
			end
		)
		return claims
	end
end


function wd.quantitySort(claims, inverted)
	local function sort(c1, c2)
		local v1 = wd.getDataValue(c1.mainsnak)
		local v2 = wd.getDataValue(c2.mainsnak) 
		if not (v1 and v2) then
				return true
		end
		if inverted then
			return v2 < v1
		end
		return v1 < v2
	end
	table.sort(claims, sort )
	return claims
end

function wd.compare.get_claim_date(claim, datetype) -- rend une date au format numérique pour faire des comparaisons
	local snak = claim.mainsnak or claim
	
	if datetype and datetype == 'personbirthdate' then -- fonctionne avec un claim dont la valeur est une personne dont on va rendre la date de naissance
		if (snak.snaktype == 'value') and (snak.datatype == 'wikibase-item') then
			local personid = wd.getId(snak)
			local birthclaims = wd.getClaims({ entity = personid, property = 'P569', numval = 1})
			if birthclaims then
				return wd.compare.get_claim_date(birthclaims[1] or birthclaims)
			else return math.huge end
		else return math.huge end -- en cas de donnée manquante, valeur infinie qui entraîne le classement en fin de liste
	end
	
	local iso, datequalif, isonumber
	if (snak.snaktype == 'value') and (snak.datatype == 'time') then
		iso = snak.datavalue.value.time
	else
		for i, dqualif in ipairs(datequalifiers) do
			iso = timeFromQualifs(claim, {dqualif})
			if iso then
				datequalif = dqualif
				break
			end
		end
		if not iso then return math.huge end
	end
	-- transformation en nombre (indication de la base car gsub retourne deux valeurs)
	isonumber = tonumber( iso:gsub( '(%d)%D', '%1' ), 10 )
	
	-- ajustement de la date tenant compte du qualificatif dont elle est issue : un fait se terminant à une date est antérieur à un autre commençant à cette date
	if datequalif == 'P582' then --date de fin
		isonumber = isonumber - 2
	elseif datequalif == 'P1326' then -- date au plus tard
		isonumber = isonumber - 1
	elseif datequalif == 'P1319' then -- date au plus tôt
		isonumber = isonumber + 1
	elseif datequalif == 'P571' or datequalif == 'P580' then -- date de début et date de création
		isonumber = isonumber + 2
	end

	return isonumber
end

function wd.compare.chronoCompare(c1, c2)
	return wd.compare.get_claim_date(c1) < wd.compare.get_claim_date(c2)
end

-- fonction pour renverser l’ordre d’une autre fonction
function wd.compare.rev(comp_criteria)
	return function(c1, c2)
		-- attention les tris en lua attendent des fonctions de comparaison strictement inférieur, on doit
		-- vérifier la non égalité quand on inverse l’ordre d’un critère, d’ou "and comp_criteria(c2,c1)"
		return not(comp_criteria(c1,c2)) and comp_criteria(c2,c1)
	end
end

-- Fonction qui trie des Claims de type time selon l'ordre chronologique
-- Une clé de tri nomée « dateSortKey » est ajouté à chaque claim.
-- Si des clés de tri de ce nom existent déjà, elles sont utilisées sans modification.

function wd.chronoSort( claims, sorttype )
	for _, claim in ipairs( claims ) do
		if not claim.dateSortKey then
			if sorttype and (sorttype == 'age' or sorttype == 'ageinverted') then
				claim.dateSortKey = wd.compare.get_claim_date(claim, 'personbirthdate')
			else
				claim.dateSortKey = wd.compare.get_claim_date(claim)
			end
			if sorttype and (sorttype == 'inverted' or sorttype == 'ageinverted') and claim.dateSortKey == math.huge then
				claim.dateSortKey = -math.huge -- quand la donnée est manquante on lui assigne la valeur qui entraîne le classement en fin de liste
			end
		end
	end
	table.sort( 
		claims,
		function ( c1, c2 )
			if sorttype and (sorttype == 'inverted' or sorttype == 'ageinverted') then
				return c2.dateSortKey < c1.dateSortKey
			end
			return c1.dateSortKey < c2.dateSortKey
		end
	)
	return claims
end

local function get_numeric_claim_value(claim, propertySort)
	local val
	local claimqualifs = claim.qualifiers
	if claimqualifs then
		local vals = claimqualifs[propertySort]
		if vals and vals[1].snaktype == 'value' then
			val = vals[1].datavalue.value
		end
	end
	return tonumber(val or 0)
end

function wd.compare.numeric(propertySort)
	return function(c1, c2)
		return get_numeric_claim_value(c1, propertySort) < get_numeric_claim_value(c2, propertySort)
	end
end

-- Fonction qui trie des Claims de type value selon l'ordre de la propriété fournit
-- Une clé de tri nomée « dateSortKey » est ajouté à chaque claim.
-- Si des clés de tri de ce nom existent déjà, elles sont utilisées sans modification.

function wd.numericPropertySort( claims, propertySort )
	for _, claim in ipairs( claims ) do
		if not claim.dateSortKey then
			local val = get_numeric_claim_value(claim, propertySort)
			claim.dateSortKey = tonumber(val or 0)
		end
	end
	table.sort(
		claims,
		function ( c1, c2 )
			return c1.dateSortKey < c2.dateSortKey
		end
	)
	return claims
end

--[[
test possible en console pour la fonction précédente : 
= p.formatStatements{entity = "Q375946", property = 'P50', sorttype = 'P1545', linkback = "true"}
--]]

-- ===================
function wd.getReferences(statement)
	local refdata = statement.references
	if not refdata then
		return nil
	end

	local refs = {}
	local hashes = {}
	for i, ref in pairs(refdata) do
		local s
		local function hasValue(prop) -- checks that the prop is here with valid value
			if ref.snaks[prop] and ref.snaks[prop][1].snaktype == 'value' then
				return true
			end
			return false
		end		

		if ref.snaks.P248 then -- cas lorsque P248 (affirmé dans) est utilisé 
			for j, source in pairs(ref.snaks.P248) do
				if source.snaktype == 'value' then
					local page, accessdate, quotation
					if hasValue('P304') then -- page 
						page = wd.formatSnak(ref.snaks.P304[1])
					end
					if hasValue('P813') then -- date de consultation
						accessdate = wd.formatSnak(ref.snaks.P813[1])
					end
					if hasValue('P1683') then -- citation
						quotation = wd.formatSnak(ref.snaks.P1683[1])
					end
					local sourceId = wd.getId(source)
					s = modules.reference.citeitem(sourceId, {['page'] = page, ['accessdate'] = accessdate, ['citation'] = quotation})
					table.insert(refs, s)
					table.insert(hashes, ref.hash .. sourceId)
				end
			end
	
		elseif hasValue('P8091') or hasValue('P854') then -- cas lorsque P8091 (Archival Resource Key) ou P854 (URL de la référence)est utilisé
			local arkKey, url, title, author, publisher, accessdate, publishdate, publishlang, quotation, description

			if hasValue('P8091') then
				arkKey = wd.formatSnak(ref.snaks.P8091[1], {text = "-"})
				url = 'https://n2t.net/' .. arkKey
				if hasValue('P1476') then
					title = wd.formatSnak(ref.snaks.P1476[1])
				else
					title = arkKey
				end
			elseif hasValue('P854') then
				url = wd.formatSnak(ref.snaks.P854[1], {text = "-"})
				if hasValue('P1476') then
					title = wd.formatSnak(ref.snaks.P1476[1])
				else
					title = mw.ustring.gsub(url, '^[Hh][Tt][Tt][Pp]([Ss]?):(/?)([^/])', 'http%1://%3')
				end
			end

			--todo : handle multiple values for author, etc.
			if hasValue('P1810') then -- sous le nom
				description = 'sous le nom ' .. wd.formatSnak(ref.snaks.P1810[1])
			end
			if hasValue('P813') then -- date de consultation
				accessdate = wd.formatSnak(ref.snaks.P813[1])
			end
			if hasValue('P50') then  -- author (item type)
				author = wd.formatSnak(ref.snaks.P50[1])
			elseif hasValue('P2093') then -- author (string type)
				author = wd.formatSnak(ref.snaks.P2093[1])
			end
			if hasValue('P123') then -- éditeur
				publisher = wd.formatSnak(ref.snaks.P123[1])
			end
			if hasValue('P1683') then -- citation
				quotation = wd.formatSnak(ref.snaks.P1683[1])
			end
			if hasValue('P577') then -- date de publication
				publishdate = wd.formatSnak(ref.snaks.P577[1])
			end 
			if hasValue('P407') then -- langue de l'œuvre
				local id = wd.getId(ref.snaks.P407[1])
				publishlang = getLangCode(id)
			end
			s = modules.cite.lienWeb{titre = title, url = url, auteur = author, editeur = publisher, langue = publishlang, ['en ligne le'] = publishdate, ['consulté le'] = accessdate, ['citation'] = quotation, ['description'] = description}
			table.insert(hashes, ref.hash)
			table.insert(refs, s)

		elseif ref.snaks.P854 and ref.snaks.P854[1].snaktype == 'value' then
			s = wd.formatSnak(ref.snaks.P854[1], {text = "-"})
			table.insert(hashes, ref.snaks.P854[1].hash)
			table.insert(refs, s)
		end
	end
	if #refs > 0 then
		if #hashes == #refs then
			return refs, hashes
		end
		return refs
	end
end

function wd.sourceStr(sources, hashes)
	if not sources or (#sources == 0) then
		return nil
	end
	local useHashes = hashes and #hashes == #sources
	for i, j in ipairs(sources) do
		local refArgs = {name = 'ref', content = j}
		if useHashes and hashes[i] ~= '-' then
			refArgs.args = {name = 'wikidata-' .. hashes[i]}
		end
		sources[i] = mw.getCurrentFrame():extensionTag(refArgs)
	end
	return table.concat(sources, '<sup class="reference cite_virgule">,</sup>')
end

function wd.getDataValue(snak, params)
	if not params then
		params = {}
	end
	local speciallabels = params.speciallabels -- parfois on a besoin de faire une liste d'éléments pour lequel le libellé doit être changé, pas très pratique d'utiliser une fonction pour ça

	if snak.snaktype ~= 'value' then
		return nil
	end

	local datatype = snak.datatype
	local value = snak.datavalue.value
	
	local displayformat = params.displayformat
	if type(displayformat) == 'function' then
		return displayformat(snak, params)
	end

	if datatype == 'wikibase-item' then
		return wd.formatEntity(wd.getId(snak), params)
	end

	if datatype == 'url' then
		if params.displayformat == 'raw' then
			return value
		else
			return modules.weblink.makelink(value, params.text)
		end
	end

	if datatype == 'math' then
		return mw.getCurrentFrame():extensionTag( "math", value)
	end

	if datatype == 'tabular-data' then
		return mw.ustring.sub(value, 6, 100)  -- returns the name of the file, without the "Data:" prefix
	end

	if (datatype == 'string') or (datatype == 'external-id') or (datatype == 'commonsMedia') then -- toutes les données de type string sauf "math"
		if params.urlpattern then
			local urlpattern = params.urlpattern
			if type(urlpattern) == 'function' then
				urlpattern = urlpattern(value)
			end
			-- encodage de l'identifiant qui se retrouve dans le path de l'URL, à l'exception des slashes parfois rencontrés, qui sont des séparateurs à ne pas encoder
			local encodedValue = mw.uri.encode(value, 'PATH'):gsub('%%2F', '/')
			-- les parenthèses autour du encodedValue:gsub() sont nécessaires, sinon sa 2e valeur de retour est aussi passée en argument au mw.ustring.gsub() parent
			local url = mw.ustring.gsub(urlpattern, '$1', (encodedValue:gsub('%%', '%%%%')))
			value = '[' .. url .. ' ' .. (params.text or value) .. ']'
		end
		return value
	end
	
	if datatype == 'time' then -- format example: +00000001809-02-12T00:00:00Z
		if displayformat == 'raw' then
			return value.time
		else
			local dateobject = modules.formatDate.dateObject(value, {precision = params.precision})
			return modules.formatDate.objectToText(dateobject, params)
		end
	end

	if datatype == 'globe-coordinate' then
		-- retourne une table avec clés latitude, longitude, précision et globe à formater par un autre module (à changer ?)
		if displayformat == 'latitude' then
			return value.latitude
		elseif displayformat == 'longitude' then
			return value.longitude
		else
			local coordvalue = mw.clone( value )
			coordvalue.globe = databases.globes[value.globe] -- transforme l'ID du globe en nom anglais utilisable par geohack
			return coordvalue -- note : les coordonnées Wikidata peuvent être utilisée depuis Module:Coordinates. Faut-il aussi autoriser à appeler Module:Coordiantes ici ?
		end
	end

	if datatype == 'quantity' then -- todo : gérer les paramètres précision
		local amount, unit = value.amount, value.unit

		if unit then
			unit = unit:match('Q%d+')
		end
		
		if not unit then
			unit = 'dimensionless'
		end
		
		local raw	
		if displayformat == "raw" then
			raw = true
		end
		return modules.formatNum.displayvalue(amount, unit,
			{targetunit = params.targetunit, raw = raw, rounding = params.rounding, showunit = params.showunit or 'short', showlink = params.showlink}
		)
	end
	if datatype == 'monolingualtext' then
		if value.language == defaultlang then
			return value.text
		else
			return modules.langmodule.langue({value.language, value.text, nocat=true})
		end
	end	
	return formatError('unknown-datavalue-type' )

end

function wd.stringTable(args) -- like getClaims, but get a list of string rather than a list of snaks, for easier manipulation
	local claims = args.claims
	local cat = ''

	if not claims then
		claims = wd.getClaims(args)
	end
	if not claims or claims == {} then
		return {}, {}, cat
	end
	if args.removedupesdate and (args.removedupesdate ~= '-') then
		claims, cat = removeDupesDate(claims, args.removedupesdate)
	end
	local props = {} -- liste des propriétés associété à chaque string pour catégorisation et linkback
	for i, j in pairs(claims) do
		claims[i] = wd.formatStatement(j, args)
		table.insert(props, j.mainsnak.property)
	end
	if args.removedupes and (args.removedupes ~= '-') then
		claims = wd.addNewValues({}, claims) -- devrait aussi supprimer de props celles qui ne sont pas utilisées
	end
	return claims, props, cat
end

function wd.getQualifiers(statement, qualifs, params)
	if not statement.qualifiers then
		return nil
	end
	local vals = {}
	if type(qualifs) == 'string' then
		qualifs = wd.splitStr(qualifs)
	end
	for i, j in pairs(qualifs) do
		if statement.qualifiers[j] then
			for k, l in pairs(statement.qualifiers[j]) do
				table.insert(vals, l)
			end
		end
	end
	if #vals == 0 then
		return nil
	end
	return vals
end

function wd.getFormattedQualifiers(statement, qualifs, params)
	if not params then params = {} end
	local qualiftable = wd.getQualifiers(statement, qualifs)
	if not qualiftable then
		return nil
	end
	qualiftable = wd.filterClaims(qualiftable, params) or {}
	for i, j in pairs(qualiftable) do
		qualiftable[i] = wd.formatSnak(j, params)
	end
	return modules.linguistic.conj(qualiftable, params.conjtype)
end

function wd.showQualifiers(str, statement, args)
	local qualifs =  args.showqualifiers
	if not qualifs then
		return str -- or error ?
	end
	if type(qualifs) == 'string' then
			qualifs = wd.splitStr(qualifs)
	end
	local qualifargs = args.qualifargs or {}
	-- formatage des qualificatifs = args commençant par "qualif", ou à défaut, les mêmes que pour la valeur principale
	qualifargs.displayformat = args.qualifdisplayformat or args.displayformat
	qualifargs.labelformat = args.qualiflabelformat or args.labelformat
	qualifargs.labelformat2 = args.qualiflabelformat2 or args.labelformat2
	qualifargs.link = args.qualiflink or args.link
	qualifargs.linktopic = args.qualiflinktopic or args.linktopic
	qualifargs.conjtype = args.qualifconjtype
	qualifargs.precision = args.qualifprecision
	qualifargs.targetunit = args.qualiftargetunit
	qualifargs.defaultlink = args.qualifdefaultlink or args.defaultlink
	qualifargs.defaultlinkquery = args.qualifdefaultlinkquery or args.defaultlinkquery
	
	local formattedqualifs
	if args.qualifformat and type (args.qualifformat) == 'function' then
		formattedqualifs = args.qualifformat(statement, qualifs, qualifargs)
	else		
		formattedqualifs = wd.getFormattedQualifiers(statement, qualifs, qualifargs)
	end
	if formattedqualifs and formattedqualifs ~= "" then
		str = str .. " (" .. formattedqualifs .. ")"
	end
	return str
end


function wd.formatSnak( snak, params )
	if not params then params = {} end -- pour faciliter l'appel depuis d'autres modules
	if snak.snaktype == 'somevalue' then
		return unknownValue(snak, params.unknownlabel)
	elseif snak.snaktype == 'novalue' then
		return noValue(params.novaluelabel)
	elseif snak.snaktype == 'value' then
		return wd.getDataValue( snak, params)
	else
		return formatError( 'unknown-snak-type' )
	end
end

function wd.formatStatement( statement, args ) -- FONCTION A REORGANISER (pas très lisible)
	if not args then
		args = {}
	end
	if not statement.type or statement.type ~= 'statement' then
		return formatError( 'unknown-claim-type' )
	end
	local prop = statement.mainsnak.property

	local str

	-- special displayformat f
	if args.statementformat and (type(args.statementformat) == 'function') then
		str = args.statementformat(statement, args)
	elseif (statement.mainsnak.datatype == 'time') and (statement.mainsnak.dateformat ~= '-') then
		if args.displayformat == 'raw' and statement.mainsnak.snaktype == 'value' then
			str = statement.mainsnak.datavalue.value.time
		else
			str = wd.getFormattedDate(statement, args)
		end
	elseif args.showonlyqualifier and (args.showonlyqualifier ~= '') then
		str = wd.getFormattedQualifiers(statement, args.showonlyqualifier, args)
		if not str then
			return nil
		end
		if args.addstandardqualifs ~= '-' then
			str = wd.addStandardQualifs(str, statement, true)
		end
	else
		str = wd.formatSnak( statement.mainsnak, args )
		if (args.addstandardqualifs ~= '-') and (args.displayformat ~= 'raw') then
			str = wd.addStandardQualifs(str, statement)
		end
	end

	-- ajouts divers
	if args.showlang == true then
		local indicateur = showLang(statement)
		if indicateur then
			str = indicateur .. '&nbsp;' .. str	
		end
	end
	if args.showqualifiers then
		str = wd.showQualifiers(str, statement, args)
	end

	if args.showdate then -- when "showdate and chronosort are both set, date retrieval is performed twice
		local period = wd.getFormattedDate(statement, args, "-") -- 3 arguments indicate the we should not use additional qualifiers, already added by wd.formatStatement
		if period then
			str = str .. " <small>(" .. period .. ")</small>"
		end
	end

	if args.showsource and args.showsource ~= '-' and args.showsource ~= "false" then
		if args.showsource == "only" then str="" end -- si showsource="only", alors ne montrer que la (les) source(s), 
		                                             -- sans la valeur qui, auparavant, était enregistrée dans str
		                                             -- Utilisé par le modèle {{PH census}}
		local sources, hashes = wd.getReferences(statement)
		if sources then
			local source = wd.sourceStr(sources, hashes)
			if source then
				str = str .. source
			end
		end
	end

	return str
end

function wd.addLinkBack(str, id, property)
	if not id or id == '' then
		id = wd.getEntityIdForCurrentPage()
	end
	if not id then
		return str
	end
	if type(property) == 'table' then
		property = property[1]
	end
	
	id = mw.text.trim(wd.entityId(id))

	local class = ''
	if property then
		class = 'wd_' .. string.lower(property)
	end
	local icon = '[[File:Blue pencil.svg|%s|10px|baseline|class=noviewer|link=%s]]'
	local title = wd.translate('see-wikidata-value')
	local url = mw.uri.fullUrl('d:' .. id, 'uselang=fr')
	url.fragment = property -- ajoute une #ancre si paramètre "property" défini
	url = tostring(url)
	local v = mw.html.create('span')
		:addClass(class)
		:wikitext(str)
		:tag('span')
			:addClass('noprint wikidata-linkback skin-invert')
			:wikitext(icon:format(title, url))
		:allDone()
	return tostring(v)
end

function wd.addRefAnchor(str, id)
--[[
	Insère une ancre pour une référence générée à partir d'un élément wd.
	L'id Wikidata sert d'identifiant à l'ancre, à utiliser dans les modèles type "harvsp"
--]]
	return tostring(
		mw.html.create('span')
			:attr('id', id)
			:attr('class', "ouvrage")
			:wikitext(str)
	)
end

--=== FUNCTIONS USING AN ENTITY AS ARGUMENT ===
local function formatStatementsGrouped(args, type)
	-- regroupe les affirmations ayant la même valeur en mainsnak, mais des qualificatifs différents
	-- (seulement pour les propriétés de type élément)

	local claims = wd.getClaims(args)
	if not claims then
		return nil
	end
	local groupedClaims = {}

	-- regroupe les affirmations par valeur de mainsnak
	local function addClaim(claim)
		local id = wd.getMainId(claim)
		for i, j in pairs(groupedClaims) do 
			if (j.id == id) then
				table.insert(groupedClaims[i].claims, claim)
				return
			end
		end
		table.insert(groupedClaims, {id = id, claims = {claim}})
	end
	for i, claim in pairs(claims) do	
		addClaim(claim)
	end

	local stringTable = {}

	-- instructions ad hoc pour les paramètres concernant la mise en forme d'une déclaration individuelle
	local funs = {
		{param = "showqualifiers", fun = function(str, claims)
			local qualifs = {}
			for i, claim in pairs(claims) do
				local news = wd.getFormattedQualifiers(claim, args.showqualifiers, args)
				if news then
					table.insert(qualifs, news)
				end
			end
			local qualifstr = modules.linguistic.conj(qualifs, wd.translate("qualif-separator"))
			if qualifstr and qualifstr ~= "" then
				str = str .. " (" .. qualifstr .. ")"
			end
			return str
			end
		},
		{param = "showdate", fun = function(str, claims)
			-- toutes les dates sont regroupées à l'intérieur des mêmes parenthèses ex "médaille d'or (1922, 1924)"
			local dates = {}
			for i, statement in pairs(claims) do
				local s = wd.getFormattedDate(statement, args, true)
				if statement then table.insert(dates, s) end
			end
			local datestr = modules.linguistic.conj(dates)
			if datestr and datestr ~= "" then
				str = str .. " <small>(" .. datestr .. ")</small>"
			end
			return str
			end
		},
		{param = "showsource", fun = function(str, claims)
			-- les sources sont toutes affichées au même endroit, à la fin
			-- si deux affirmations ont la même source, on ne l'affiche qu'une fois
			local sources = {}
			local hashes = {}
		
			local function dupeRef(old, new)
				for i, j in pairs(old) do
					if j == new then
						return true
					end
				end
			end
			for i, claim in pairs(claims) do
				local refs, refHashes = wd.getReferences(claim)
				if refs then
					for i, j in pairs(refs) do
						if not dupeRef(sources, j) then
							table.insert(sources, j)
							local hash = (refHashes and refHashes[i]) or '-'
							table.insert(hashes, hash)
						end
					end
				end
			end
			return str .. (wd.sourceStr(sources, hashes) or "")
			end
		}
	}

	for i, group in pairs(groupedClaims) do -- bricolage pour utiliser les arguments de formatStatements
		local str = wd.formatEntity(group.id, args)
		if not str then
			str = '???' -- pour éviter erreur Lua si formatEntity a retourné nil
		end
		for i, fun in pairs(funs) do
			if args[fun.param] then
				str = fun.fun(str, group.claims, args)
			end
		end
		table.insert(stringTable, str)
	end
				
	args.valuetable = stringTable
	return wd.formatStatements(args)
end


function wd.formatStatements( args )--Format statement and concat them cleanly

	if args.value == '-' then
		return nil
	end

	-- If a value is already set: use it, except if it's the special value {{WD}} (use wikidata)
	if args.value and args.value ~= '' then
		local valueexpl = wd.translate("activate-query")
		if args.value ~= valueexpl then
			return args.value
		end
	-- There is no value set, and args.expl disables wikidata on empty values
	elseif args.expl then
		return nil
	end

	if args.grouped and args.grouped ~= '' then
		args.grouped = false
		return formatStatementsGrouped(args)
	end
	local valuetable = args.valuetable -- dans le cas où les valeurs sont déjà formatées
	local props -- les propriétés réellement utilisées (dans certains cas, ce ne sont pas toutes celles de args.property
	local cat = ''
	if not valuetable then -- cas le plus courant
		valuetable, props, cat = wd.stringTable(args)
	end
	
	if args.ucfirst == '-' and args.conjtype == 'new line' then args.conjtype = 'lowercase new line' end
	local str = modules.linguistic.conj(valuetable, args.conjtype)
	if not str then
		return args.default
	end
	if not props then
		props = wd.splitStr(args.property)[1]
	end
	if args.ucfirst ~= '-' then
		str = modules.linguistic.ucfirst(str)
	end

	if args.addcat and (args.addcat ~= '-') then
		str = str .. wd.addTrackingCat(props) .. cat
	end
	if args.linkback and (args.linkback ~= '-') then
		str = wd.addLinkBack(str, args.entity, props)
	end
	if args.returnnumberofvalues then
		return str, #valuetable
	end
	return str
end

function wd.formatAndCat(args)
	if not args then
		return nil
	end
	args.linkback = args.linkback or true
	args.addcat = true
	if args.value then -- do not ignore linkback and addcat, as formatStatements do
		if args.value == '-' then
			return nil
		end
		local val = args.value .. wd.addTrackingCat(args.property)
		val = wd.addLinkBack(val, args.entity, args.property)
		return val
	end 
	return wd.formatStatements( args )
end

function wd.getTheDate(args)
	local claims = wd.getClaims(args)
	if not claims then
		return nil
	end
	local formattedvalues = {}
	for i, j in pairs(claims) do
		local v = wd.getFormattedDate(j, args)
		if v then
			table.insert(formattedvalues, v )
		end
	end
	local val = modules.linguistic.conj(formattedvalues)
	if not val then
		return nil
	end
	if args.addcat == true then
		val = val .. wd.addTrackingCat(args.property)
	end
	val = wd.addLinkBack(val, args.entity, args.property)
	return val
end


function wd.keyDate (event, item, params)
	params = params or {}
	params.entity = item
	if type(event) == 'table' then
		for i, j in pairs(event) do
			params.targetvalue = nil -- réinitialisation barbare des paramètres modifiés
			local s = wd.keyDate(j, item, params)
			if s then
				return s
			end
		end
	elseif type(event) ~= 'string' then
		 return formatError('invalid-datatype', type(event), 'string')
	elseif string.sub(event, 1, 1) == 'Q' then -- on demande un élément utilisé dans P:P793 (événement clé)
		params.property = 'P793'
		params.targetvalue = event
		params.addcat = params.addcat or true
		return wd.getTheDate(params)
	elseif string.sub(event, 1, 1) == 'P'  then -- on demande une propriété
		params.property = event
		return wd.formatAndCat(params)
	else
		return formatError('invalid-entity-id', event)
	end
end

function wd.mainDate(entity)	
	-- essaye P580/P582
	local args = {entity = entity, addcat = true}
	
	args.property = 'P580'
	local startpoint = wd.formatStatements(args)
	args.property = 'P582'
	local endpoint = wd.formatStatements(args)

	local str
	if (startpoint or endpoint) then
		str = modules.formatDate.daterange(startpoint, endpoint, params)
		str = wd.addLinkBack(str, entity, 'P582')
		return str
	end

	-- défaut : P585
	args.property = {'P585', 'P571'}
	args.linkback = true
	return wd.formatStatements(args)
end

-- ==== Fonctions sur le genre ====

function wd.getgender(id)
	local vals = {
		['Q6581072'] = 'f', -- féminin
		['Q6581097'] = 'm', -- masculin
		['Q1052281'] = 'f', -- femme transgenre
		['Q2449503'] = 'm', -- homme transgenre
		['Q17148251'] = 'f', -- en:Travesti (gender identity)
		['Q43445'] = 'f', -- femelle
		['Q44148'] = 'm', -- mâle
		default      = '?'
	}
	local gender = wd.formatStatements{entity = id, property = 'P21', displayformat = 'raw', numval = 1}
	return vals[gender] or vals.default
end

-- catégories de genre/nombre
function wd.getgendernum(claims)
	local personid, gender
	local anym = false
	local anyf = false
	local anyunknown = false
	
	for i, claim in pairs(claims) do
		local snak = claim.mainsnak or claim
		if(snak.snaktype == 'value') and (snak.datatype == 'wikibase-item') then
			personid = wd.getId(snak)
			gender = wd.getgender(personid)
			anym = anym or (gender == 'm')
			anyf = anyf or (gender == 'f')
			anyunknown = anyunknown or (gender == '?')
		else
			anyunknown = true
		end
	end
	
	local gendernum
	if #claims > 1 then
		if anyunknown then
			gendernum = 'p'
		else
			if anym and not anyf then gendernum = 'mp' end
			if anyf and not anym then gendernum = 'fp' end
			if anym and anyf then gendernum = 'mixtep' end
		end
	else
		gendernum = 's'
		if anym then gendernum = 'ms' end
		if anyf then gendernum = 'fs' end
	end
	
	return gendernum
end


-- récupération des libellés genrés de Wikidata
function wd.genderedlabel(id, labelgender)
	local label
	if not labelgender then return nil end
	if labelgender == 'f' then -- femme : chercher le libellé dans P2521 (libellé féminin)
		label = wd.formatStatements{entity = id, property = 'P2521', isinlang = 'fr', numval = 1, ucfirst = '-'}
	elseif labelgender == 'm' then -- homme : chercher le libellé dans P3321 (libellé masculin)
		label = wd.formatStatements{entity = id, property = 'P3321', isinlang = 'fr', numval = 1, ucfirst = '-'}
	end
	if not label then
		label = wd.getLabel(id)
	end
	return label
end


-- === FUNCTIONS FOR TRANSITIVE PROPERTIES ===

function wd.getIds(item, query)
	query.excludespecial = true
	query.displayformat = 'raw'
	query.entity = item
	query.addstandardqualifs = '-'
	return wd.stringTable(query)
end


-- recursively adds a list of qid to an existing list, based on the results of a query
function wd.addVals(list, query, maxdepth, maxnodes, stopval)
	maxdepth = tonumber(maxdepth) or 10
	maxnodes = tonumber(maxnodes) or 100
	if (maxdepth < 0) then
		return list
	end
	if stopval and wd.isHere(list, stopval) then
		return list
	end
	local origsize = #list
	for i = 1, origsize do
		-- tried a  "checkpos" param instead of starting to 1 each time, but no impact on performance
		local candidates = wd.getIds(list[i], query)
		list = wd.addNewValues(list, candidates, maxnodes, stopval)
		if list[#list] == stopval then
			return list
		end
		if #list >= maxnodes then
			return list
		end
	end
	
	if (#list == origsize) then
		return list
	end
	return wd.addVals(list, query, maxdepth - 1, maxnodes, stopval, origsize + 1)
end

-- returns a list of items transitively matching a query (orig item is not included in the list)

function wd.transitiveVals(item, query, maxdepth, maxnodes, stopval)
	maxdepth = tonumber(maxdepth) or 5
	if type(query) == "string" then
		query = {property = query}
	end

	-- récupération des valeurs
	local vals = wd.getIds(item, query)
	if not vals then
		return nil
	end
	local v = wd.addVals(vals, query, maxdepth - 1, maxnodes, stopval) 
	if not v then
		return nil
	end

	-- réarrangement des valeurs
	if query.valorder == "inverted" then
		local a = {}
		for i = #v, 1, -1 do
			a[#a+1] = v[i]
		end
		v = a
	end

	return v
end

-- returns true if an item is the value of a query, transitively
function wd.inTransitiveVals(searchedval, sourceval, query, maxdepth, maxnodes )
	local vals = wd.transitiveVals(sourceval, query, maxdepth, maxnodes, searchedval )
	if (not vals) then 
		return false
	end
	for _, val in ipairs(vals) do
		if (val == searchedval) then
			return true
		end
	end
	return false
end

-- returns true if an item is a superclass of another, based on P279
function wd.isSubclass(class, item, maxdepth)
	local query = {property = 'P279'}
	if class == item then -- item is a subclass of itself iff it is a class
		if wd.getIds(item, query) then
			return true
		end
		return false
	end
	return wd.inTransitiveVals(class, item, query, maxdepth )
end

-- returns true if one of the best ranked P31 values of an item is the target or a subclass of the target
-- rank = 'valid' would seem to make sense, but it would need to check for date qualifiers as some P31 values have begin or end date
function wd.isInstance(targetclass, item, maxdepth)
	maxdepth = maxdepth or 10
	local directclasses = wd.transitiveVals(item, {property = 'P31'}, 1)
	if not directclasses then
		return false
	end 
	for i, class in pairs(directclasses) do
		if wd.isSubclass(targetclass, class, maxdepth - 1) then
			return true
		end
	end
	return false
end

-- return the first value in a transitive query that belongs to a particular class. For instance find a value of P131 that is a province of Canada
function wd.findVal(sourceitem, targetclass, query, recursion, instancedepth)
	if type(query) == "string" then
		query = {property = query}
	end
	local candidates = wd.getIds(sourceitem, query)
	if candidates then
		for i, j in pairs(candidates) do
			if wd.isInstance(targetclass, j,  instancedepth) then
				return j
			end
		end
		if not recursion then
			recursion = 3
		else
			recursion = recursion - 1
		end
		if recursion < 0 then
			return nil
		end
		for i, candidate in pairs(candidates) do
			return wd.findVal(candidate, targetclass, query, recursion, instancedepth)
		end
	end
end


-- === VARIA ===
function wd.getDescription(entity, lang)
	lang = lang or defaultlang

	local description
	if lang == defaultlang then
		return  mw.wikibase.description(qid)
	end
	if not entity.descriptions then
		return wd.translate('no description')
	end
	local descriptions = entity.descriptions
	if not descriptions then
		return nil
	end
	if descriptions[lang] then
		return descriptions[delang].value
	end
	return entity.id
end

function wd.Dump(entity)
	entity = wd.getEntity(entity)
	if not entity then
		return formatError("entity-param-not-provided")
	end
	return "<pre>"..mw.dumpObject(entity).."</pre>"
end

function wd.frameFun(frame)
	local args = frame.args
	local funname = args[1]
	table.remove(args, 1)
	return wd[funname](args)
end


return wd