« Module:Wikidata » : différence entre les versions
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 : | ||
-- | --script that retrieves basic data stored in Wikidata, for the datamodel, see https://www.mediawiki.org/wiki/Extension:Wikibase_Client/Lua | ||
local | 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 | 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', | |||
local | linguistic = 'Module:Linguistique', | ||
{ | datemodule = 'Module:Date', | ||
formatDate = 'Module:Date complexe', | |||
formatNum = 'Module:Conversion', | |||
langmodule = 'Module:Langue', | |||
cite = 'Module:Biblio', | |||
weblink = 'Module:Weblink' | |||
} | } | ||
if | local function loadDatabase( t, key ) | ||
if databasesNames[key] then | |||
local m = mw.loadData( databasesNames[key] ) | |||
local | 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 | ||
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 | if rep2 and (type (rep2) == 'string')then | ||
str = str:gsub('$2', rep2) | |||
end | end | ||
return str | |||
end | |||
if | |||
local function addCat(cat, sortkey) | |||
if sortkey then | |||
return '[[Category:' .. cat .. '|' .. sortkey .. ']]' | |||
end | end | ||
return | return '[[Category:' .. cat .. ']]' | ||
end | end | ||
local function | 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 | 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 | ||
end | |||
function wd.getNumericId(snak) | |||
return | if (snak.snaktype == 'value') then | ||
return snak.datavalue.value['numeric-id'] | |||
end | end | ||
end | end | ||
function wd.getMainId(claim) | |||
return wd.getId(claim.mainsnak) | |||
end | |||
function wd.entityId(entity) | |||
if type(entity) == 'string' then | |||
if | return entity | ||
return | elseif type(entity) == 'table' then | ||
return entity.id | |||
return | |||
end | end | ||
end | end | ||
-- | function wd.getEntityIdForCurrentPage() | ||
-- | return mw.wikibase.getEntityIdForCurrentPage() | ||
end | |||
local | |||
-- 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 | if type(val) == 'table' then | ||
return val | |||
end | 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 | 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 | |||
if | end | ||
else | else | ||
if val == j then | |||
return true | |||
end | |||
end | end | ||
end | end | ||
return false | |||
end | |||
local | local function wikidataLink(entity) | ||
if | 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 | ||
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 | end | ||
if | project = project:lower() | ||
if project == 'wikipedia' then | |||
project = 'wiki' | |||
end | end | ||
if | 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 | end | ||
return | 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 | local projectdata = projects[project:lower()] | ||
if | 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 | end | ||
return | 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 | ||
-- 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 | ||
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 | local id = wd.getMainId(claim) | ||
return function() | local targets = wd.splitStr(targets) | ||
local maxdepth = maxdepth or 10 | |||
if | 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 | ||
end | |||
if #preferred > 0 then | |||
return preferred | |||
else | |||
return normal | |||
end | end | ||
end | end | ||
local function withRank(claims, target) | |||
local function | if target == 'best' then | ||
return bestRanked(claims) | |||
end | |||
local | local newclaims = {} | ||
for pos, claim in pairs(claims) do | |||
if target == 'valid' then | |||
return | 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 | ||
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 | ||
end | end | ||
for i, qualif in pairs(acceptedqualifs) do | |||
if ok(qualif) then | |||
return true | |||
end | |||
end | end | ||
return false | |||
end | |||
function wd.hasQualifierNumber(claim, acceptedqualifs, acceptedvals, excludequalifiervalues) | |||
if | local claimqualifs = claim.qualifiers | ||
return | |||
if (not claimqualifs) then | |||
return false | |||
end | end | ||
acceptedqualifs = wd.splitStr(acceptedqualifs) | |||
acceptedvals = wd.splitStr( acceptedvals) | |||
if | 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 | 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 | ||
end | end | ||
return false | |||
end | end | ||
local function | 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 | |||
if | 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 | ||
end | |||
if | 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 | local v = isInLanguage(claim, l) | ||
if | if v then | ||
return true | |||
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 | 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 | ||
end | end | ||
return newClaims, cat | |||
end | end | ||
local function | local function timeFromQualifs(claim, qualifs) | ||
local claimqualifs = claim.qualifiers | |||
if not claimqualifs then | |||
return nil | |||
end | |||
if | 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 | ||
end | end | ||
local function | local function atDate(claim, mydate) | ||
-- | if mydate == "today" then | ||
if | 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 | else | ||
local | myprecision = 9 -- year | ||
return | 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 | local function check(claim, condition) | ||
if | if type(condition) == 'function' then -- cas standard | ||
return condition(claim) | |||
end | |||
return formatError('invalid type', 'function', type(condition)) | |||
end | |||
if | 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 | |||
return | 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 | ||
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 | 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 | ||
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 | ||
function wd.loadEntity(entity, cache) | |||
if type(entity) ~= 'table' then | |||
if cache then | |||
-- | if not cache[entity] then | ||
if | 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 | end | ||
return nil, | 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 | ||
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 | end | ||
if (not claims) or (#claims == 0) then | |||
return nil | |||
end | |||
return wd.filterClaims(claims, args) | |||
end | end | ||
--=== ENTITY FORMATTING === | |||
function wd.getLabel(entity, lang1, lang2) | |||
if | 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 | else | ||
return | 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 | ||
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 {} | |||
for | 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 | end | ||
end | end | ||
return | |||
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 | ||
function wd.addTrackingCat(prop, cat) -- doit parfois être appelé par d'autres modules | |||
if | if type(prop) == 'table' then | ||
prop = prop[1] -- devrait logiquement toutes les ajouter | |||
end | |||
if | if not prop and not cat then | ||
return | return formatError("property-param-not-provided") | ||
end | end | ||
if not cat then | |||
local | cat = wd.translate('trackingcat', prop or 'P??') | ||
if | end | ||
return addCat(cat ) | |||
if | 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 | ||
end | end | ||
return | |||
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 | |||
return | local function getLangCode(entityid) | ||
return databases.langcodes[tonumber(entityid:sub(2))] | |||
end | end | ||
local function | |||
if | 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 | end | ||
if | local langlist = {} | ||
if mainsnak.datavalue.type == 'monolingualtext' then | |||
for | 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 | 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 | 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 | 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 | ||
end | end | ||
return str | |||
end | end | ||
function | function wd.getDateFromQualif(statement, qualif) | ||
if (not statement) or (not statement.qualifiers) or not (statement.qualifiers[qualif]) then | |||
local | return nil | ||
-- return | end | ||
return | 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 | function wd.getDate(statement) | ||
local | local period = wd.getDateFromQualif(statement, 'P585') -- retourne un dateobject | ||
local | 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 | end | ||
-- | function wd.getFormattedDate(statement, params) | ||
if not statement then | |||
return nil | |||
local | 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 | end | ||
if | if str and params and (params.addstandardqualifs ~= '-') then | ||
str = wd.addStandardQualifs(str, statement, fromqualif) | |||
end | end | ||
return | |||
return str | |||
end | end | ||
wd.compare.by_quantity = function(c1, c2) | |||
local v1 = wd.getDataValue(c1.mainsnak) | |||
local | local v2 = wd.getDataValue(c2.mainsnak) | ||
if not | if not (v1 and v2) then | ||
return true | |||
end | 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 | end | ||
end | end | ||
table.sort( | |||
return | claims, | ||
function(c1, c2) | |||
return key_compare_function(c1[sortKey], c2[sortKey]) | |||
end | |||
) | |||
return claims | |||
end | end | ||
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 | |||
local | |||
local | |||
if | |||
end | end | ||
if | if inverted then | ||
return | return v2 < v1 | ||
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 | 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 | ||
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 | 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 | end | ||
local | 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 | ||
end | end | ||
return ( | return tonumber(val or 0) | ||
end | 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 | ||
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 | end | ||
return | table.sort( | ||
claims, | |||
function ( c1, c2 ) | |||
return c1.dateSortKey < c2.dateSortKey | |||
end | |||
) | |||
return claims | |||
end | end | ||
-- | --[[ | ||
test possible en console pour la fonction précédente : | |||
local | = 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 | end | ||
if #refs > 0 then | |||
if #hashes == #refs then | |||
return refs, hashes | |||
end | |||
return refs | |||
end | end | ||
end | end | ||
function wd.sourceStr(sources, hashes) | |||
if not sources or (#sources == 0) then | |||
return nil | |||
if not | |||
return | |||
end | end | ||
local | 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 | end | ||
return | return table.concat(sources, '<sup class="reference cite_virgule">,</sup>') | ||
end | end | ||
function wd.getDataValue(snak, params) | |||
if not params then | |||
if value. | 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 | 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 | ||
end | end | ||
local | |||
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 | end | ||
return | 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 | ||
function wd.getQualifiers(statement, qualifs, params) | |||
if not statement.qualifiers then | |||
return nil | |||
if not | |||
return | |||
end | end | ||
local | 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 | ||
end | end | ||
return | if #vals == 0 then | ||
return nil | |||
end | |||
return vals | |||
end | end | ||
function wd.getFormattedQualifiers(statement, qualifs, params) | |||
local | if not params then params = {} end | ||
local qualiftable = wd.getQualifiers(statement, qualifs) | |||
if not qualiftable then | |||
return | return nil | ||
end | 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 | end | ||
function wd.showQualifiers(str, statement, args) | |||
local qualifs = args.showqualifiers | |||
if not qualifs then | |||
return str -- or error ? | |||
local | |||
if not | |||
return | |||
end | end | ||
local | if type(qualifs) == 'string' then | ||
if ( | 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 | end | ||
else | 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 .. ' ' .. 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 | ||
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 | |||
if | |||
end | end | ||
end | end | ||
end | end | ||
if | return str | ||
end | |||
function wd.addLinkBack(str, id, property) | |||
if not id or id == '' then | |||
id = wd.getEntityIdForCurrentPage() | |||
end | end | ||
return | 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) | |||
--[[ | --[[ | ||
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 = | local id = wd.getMainId(claim) | ||
for i, j in pairs(groupedClaims) do | |||
id = | 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 | ||
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")) | ||
for | if qualifstr and qualifstr ~= "" then | ||
if | 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 | 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 | ||
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 | -- There is no value set, and args.expl disables wikidata on empty values | ||
elseif args.expl then | |||
return | 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 | ||
-- | 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 | local claims = wd.getClaims(args) | ||
if not | if not claims then | ||
return | 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 | ||
val = wd.addLinkBack(val, args.entity, args.property) | |||
return val | |||
if ( | end | ||
for | |||
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 | 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 | ||
-- | return formatError('invalid-entity-id', event) | ||
end | end | ||
end | end | ||
-- | function wd.mainDate(entity) | ||
-- essaye P580/P582 | |||
return | 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 | -- ==== Fonctions sur le genre ==== | ||
local | |||
function wd.getgender(id) | |||
local vals = { | |||
['Q6581072'] = 'f', -- féminin | |||
['Q6581097'] = 'm', -- masculin | |||
local | ['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 | |||
local | 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 | end | ||
local | local gendernum | ||
if | if #claims > 1 then | ||
if | 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 | |||
-- récupération des libellés genrés de Wikidata | |||
local | 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 | end | ||
if not label then | |||
label = wd.getLabel(id) | |||
end | end | ||
return label | |||
end | |||
-- === FUNCTIONS FOR TRANSITIVE PROPERTIES === | |||
if | |||
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 | ||
end | end | ||
if (#list == origsize) then | |||
return list | |||
end | |||
return wd.addVals(list, query, maxdepth - 1, maxnodes, stopval, origsize + 1) | |||
end | |||
if | -- 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 | end | ||
-- | -- récupération des valeurs | ||
local vals = wd.getIds(item, query) | |||
local | if not vals then | ||
return nil | |||
if | |||
end | end | ||
local | local v = wd.addVals(vals, query, maxdepth - 1, maxnodes, stopval) | ||
if not | if not v then | ||
return nil | return nil | ||
end | end | ||
local i = 1 | -- réarrangement des valeurs | ||
if query.valorder == "inverted" then | |||
local a = {} | |||
if | 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 | ||
end | |||
return false | |||
end | |||
-- returns true if an item is a superclass of another, based on P279 | |||
if | 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 | -- 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 | ||
-- | -- 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) | |||
function | if type(query) == "string" then | ||
query = {property = query} | |||
if | |||
end | end | ||
local | local candidates = wd.getIds(sourceitem, query) | ||
if not | 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 | ||
end | end | ||
-- === VARIA === | |||
function wd.getDescription(entity, lang) | |||
lang = lang or defaultlang | |||
local description | |||
if not | 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 | 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 | |||
return wd | |||
Dernière version du 19 septembre 2024 à 16: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 .. ' ' .. 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