« 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 à 17:01
La documentation pour ce module peut être créée à Module:Wikidata/doc
--script that retrieves basic data stored in Wikidata, for the datamodel, see https://www.mediawiki.org/wiki/Extension:Wikibase_Client/Lua local wd = {} -- creation of a subobject to store comparison funtions, used for sorting claims -- to be able to build more complex sorts like topological sorts wd.compare = {} local databases = { } local modules = { } local databasesNames = { -- modules de données statiques pouvant être appelés avec mw.loadData(), ne nécessitant pas require() i18n = 'Module:Wikidata/I18n', globes = 'Module:Wikidata/Globes', langhierarchy = 'Module:Wikidata/Hiérarchie des langues', langcodes = 'Module:Dictionnaire Wikidata/Codes langue', -- big, infrequently useda invertedlangcodes = 'Module:Dictionnaire Wikidata/Codes langue/inversé' } local modulesNames = { reference = 'Module:Wikidata/Références', linguistic = 'Module:Linguistique', datemodule = 'Module:Date', formatDate = 'Module:Date complexe', formatNum = 'Module:Conversion', langmodule = 'Module:Langue', cite = 'Module:Biblio', weblink = 'Module:Weblink' } local function loadDatabase( t, key ) if databasesNames[key] then local m = mw.loadData( databasesNames[key] ) t[key] = m return m end end local function loadModule( t, key ) if modulesNames[key] then local m = require( modulesNames[key] ) t[key] = m return m end end setmetatable( databases, { __index = loadDatabase } ) setmetatable( modules, { __index = loadModule } ) -- ainsi le require() sera opéré seulement si nécessaire par modules.(nom du module) local datequalifiers = {'P585', 'P571', 'P580', 'P582', 'P1319', 'P1326'} -- === I18n === local defaultlang = mw.getContentLanguage():getCode() function wd.translate(str, rep1, rep2) str = databases.i18n[str] or str if rep1 and (type (rep1) == 'string') then str = str:gsub('$1', rep1) end if rep2 and (type (rep2) == 'string')then str = str:gsub('$2', rep2) end return str end local function addCat(cat, sortkey) if sortkey then return '[[Category:' .. cat .. '|' .. sortkey .. ']]' end return '[[Category:' .. cat .. ']]' end local function formatError( key , category, debug) if debug then return error(databases.i18n[key] or key) end if category then return addCat(category, key) else return addCat('cat-unsorted-issue', key) end end -- function wd.isSpecial(snak) return (snak.snaktype ~= 'value') end function wd.getId(snak) if (snak.snaktype == 'value') then return 'Q' .. snak.datavalue.value['numeric-id'] end end function wd.getNumericId(snak) if (snak.snaktype == 'value') then return snak.datavalue.value['numeric-id'] end end function wd.getMainId(claim) return wd.getId(claim.mainsnak) end function wd.entityId(entity) if type(entity) == 'string' then return entity elseif type(entity) == 'table' then return entity.id end end function wd.getEntityIdForCurrentPage() return mw.wikibase.getEntityIdForCurrentPage() end -- function that returns true if the "qid" parameter is the qid -- of the item that is linked to the calling page function wd.isPageOfQId(qid) local self_id = mw.wikibase.getEntityIdForCurrentPage() return self_id ~= nil and qid == self_id end function wd.getEntity( val ) if type(val) == 'table' then return val end if val == '-' then return nil end if val == '' then val = nil end return mw.wikibase.getEntity(val) end function wd.splitStr(val) -- transforme en table les chaînes venant du Wikitexte qui utilisent des virgules de séparation if type(val) == 'string' then val = mw.text.split(val, ",") end return val end function wd.isHere(searchset, val, matchfunction) for i, j in pairs(searchset) do if matchfunction then if matchfunction(val,j) then return true end else if val == j then return true end end end return false end local function wikidataLink(entity) local name =':d:' if type(entity) == 'string' then if entity:match("P[0-9]+") then entity = "Property:" .. entity end return name .. entity elseif type(entity) == 'table' then if entity["type"] == "property" then name = ":d:Property:" end return name .. entity.id elseif type(entity) == nil then return formatError('entity-not-found') end end function wd.siteLink(entity, project, lang) -- returns 3 values: a sitelink (with the relevant prefix) a project name and a language lang = lang or defaultlang if (type(project) ~= 'string') then project = 'wiki' end project = project:lower() if project == 'wikipedia' then project = 'wiki' end if type(entity) == 'string' and (project == 'wiki') and ( (not lang or lang == defaultlang) ) then -- évite de charger l'élément entier local link = mw.wikibase.getSitelink(entity) if link then local test_redirect = mw.title.new(link) -- remplacement des redirections (retirer si trop coûteux) if test_redirect.isRedirect and test_redirect.redirectTarget then link = test_redirect.redirectTarget.fullText end end return link, 'wiki', defaultlang end if project == 'wikidata' then return wikidataLink(entity), 'wikidata' end local projects = { -- nom = {préfixe sur Wikidata, préfix pour les liens sur Wikipédia, ajouter préfixe de langue} wiki = {'wiki', nil, true}, -- wikipedia commons = {'commonswiki', 'commons', false}, commonswiki = {'commonswiki', 'commons', false}, wikiquote = {'wikiquote', 'q', true}, wikivoyage = {'wikivoyage', 'voy', true}, wikibooks = {'wikibooks', 'b', true}, wikinews = {'wikinews', 'n', true}, wikiversity = {'wikiversity', 'v', true}, wikisource = {'wikisource', 's', true}, wiktionary = {'wiktionary', 'wikt', true}, specieswiki = {'specieswiki', 'species', false}, metawiki = {'metawiki', 'm', false}, incubator = {'incubator', 'incubator', false}, outreach = {'outreach', 'outreach', false}, mediawiki = {'mediawiki', 'mw', false} } local entityid = entity.id or entity local projectdata = projects[project:lower()] if not projectdata then -- defaultlink might be in the form "dewiki" rather than "project: 'wiki', lang: 'de' " for k, v in pairs(projects) do if project:match( k .. '$' ) and mw.language.isKnownLanguageTag(project:sub(1, #project-#k)) then lang = project:sub(1, #project-#k) project = project:sub(#lang + 1, #project) projectdata = projects[project] break end end if not mw.language.isKnownLanguageTag(lang) then return --formatError('invalid-project-code', projet or 'nil') end end if not projectdata then return -- formatError('invalid-project-code', projet or 'nil') end local linkcode = projectdata[1] local prefix = projectdata[2] local multiversion = projectdata[3] if multiversion then linkcode = lang .. linkcode end local link = mw.wikibase.getSitelink(entityid, linkcode) if not link then return nil end if prefix then link = prefix .. ':' .. link end if multiversion then link = ':' .. lang .. ':' .. link end return link, project, lang end -- add new values to a list, avoiding duplicates function wd.addNewValues(olditems, newitems, maxnum, stopval) if not newitems then return olditems end for _, qid in pairs(newitems) do if stopval and (qid == stopval) then table.insert(olditems, qid) return olditems end if maxnum and (#olditems >= maxnum) then return olditems end if not wd.isHere(olditems, qid) then table.insert(olditems, qid) end end return olditems end --=== FILTER CLAIMS ACCORDING TO VARIOUS CRITERIA : FUNCTION GETCLAIMS et alii === local function notSpecial(claim) local type if claim.mainsnak ~= nil then type = claim.mainsnak.snaktype else -- condition respectée quand showonlyqualifier est un paramètre renseigné -- dans ce cas, claim n'est pas une déclaration entière, mais UNE snak qualifiée du main snak type = claim.snaktype end return type == 'value' end local function hasTargetValue(claim, targets) -- retourne true si la valeur est dans la liste des target, ou si c'est une valeur spéciale filtrée séparément par excludespecial local id = wd.getMainId(claim) local targets = wd.splitStr(targets) return wd.isHere(targets, id) or wd.isSpecial(claim.mainsnak) end local function excludeValues(claim, values) -- true si la valeur n'est pas dans la liste, ou si c'est une valeur spéciale (filtrée à part par excludespecial) return wd.isSpecial(claim.mainsnak) or not ( hasTargetValue(claim, values) ) end local function hasTargetClass(claim, targets, maxdepth) -- retourne true si la valeur est une instance d'une classe dans la liste des target, ou si c'est une valeur spéciale filtrée séparément par excludespecial local id = wd.getMainId(claim) local targets = wd.splitStr(targets) local maxdepth = maxdepth or 10 local matchfunction = function(value, target) return wd.isInstance(target, value, maxdepth) end return wd.isHere(targets, id, matchfunction) or wd.isSpecial(claim.mainsnak) end local function excludeClasses(claim, classes) -- true si la valeur n'est pas une instance d'une classe dans la liste, ou si c'est une valeur spéciale (filtrée à part par excludespecial) return wd.isSpecial(claim.mainsnak) or not ( hasTargetClass(claim, classes, maxdepth) ) end local function hasTargetSuperclass(claim, targets, maxdepth) -- retourne true si la valeur est une sous-classe d'une classe dans la liste des target, ou si c'est une valeur spéciale filtrée séparément par excludespecial local id = wd.getMainId(claim) local targets = wd.splitStr(targets) local maxdepth = maxdepth or 10 local matchfunction = function(value, target) return wd.isSubclass(target, value, maxdepth) end return wd.isHere(targets, id, matchfunction) or wd.isSpecial(claim.mainsnak) end local function excludeSuperclasses(claim, classes) -- true si la valeur n'est pas une sous-classe d'une classe dans la liste, ou si c'est une valeur spéciale (filtrée à part par excludespecial) return wd.isSpecial(claim.mainsnak) or not ( hasTargetSuperclass(claim, classes, maxdepth) ) end local function bestRanked(claims) if not claims then return nil end local preferred, normal = {}, {} for i, j in pairs(claims) do if j.rank == 'preferred' then table.insert(preferred, j) elseif j.rank == 'normal' then table.insert(normal, j) end end if #preferred > 0 then return preferred else return normal end end local function withRank(claims, target) if target == 'best' then return bestRanked(claims) end local newclaims = {} for pos, claim in pairs(claims) do if target == 'valid' then if claim.rank ~= 'deprecated' then table.insert(newclaims, claim) end elseif claim.rank == target then table.insert(newclaims, claim) end end return newclaims end function wd.hasQualifier(claim, acceptedqualifs, acceptedvals, excludequalifiervalues) local claimqualifs = claim.qualifiers if (not claimqualifs) then return false end acceptedqualifs = wd.splitStr(acceptedqualifs) acceptedvals = wd.splitStr( acceptedvals) local function ok(qualif) -- vérification pour un qualificatif individuel if not claimqualifs[qualif] then return false end if not (acceptedvals) then -- si aucune valeur spécifique n'est demandée, OK return true end for i, wanted in pairs(acceptedvals) do for j, actual in pairs(claimqualifs[qualif]) do if wd.getId(actual) == wanted then return true end end end end for i, qualif in pairs(acceptedqualifs) do if ok(qualif) then return true end end return false end function wd.hasQualifierNumber(claim, acceptedqualifs, acceptedvals, excludequalifiervalues) local claimqualifs = claim.qualifiers if (not claimqualifs) then return false end acceptedqualifs = wd.splitStr(acceptedqualifs) acceptedvals = wd.splitStr( acceptedvals) local function ok(qualif) -- vérification pour un qualificatif individuel if not claimqualifs[qualif] then return false end if not (acceptedvals) then -- si aucune valeur spécifique n'est demandée, OK return true end for i, wanted in pairs(acceptedvals) do for j, actual in pairs(claimqualifs[qualif]) do if mw.wikibase.renderSnak(actual) == wanted then return true end end end end for i, qualif in pairs(acceptedqualifs) do if ok(qualif) then return true end end return false end local function hasSource(claim, targetsource, sourceproperty) sourceproperty = sourceproperty or 'P248' if targetsource == "-" then return true end if (not claim.references) then return false end local candidates = claim.references[1].snaks[sourceproperty] -- les snaks utilisant la propriété demandée if (not candidates) then return false end if (targetsource == "any") then -- si n'importe quelle valeur est acceptée tant qu'elle utilise en ref la propriété demandée return true end targetsource = wd.splitStr(targetsource) for _, source in pairs(candidates) do local s = wd.getId(source) for i, target in pairs(targetsource) do if s == target then return true end end end return false end local function excludeQualifier(claim, qualifier, qualifiervalues) return not wd.hasQualifier(claim, qualifier, qualifiervalues) end function wd.hasDate(claim) if not claim then return false --error() ? end if wd.getDateFromQualif(claim, 'P585') or wd.getDateFromQualif(claim, 'P580') or wd.getDateFromQualif(claim, 'P582') then return true end return false end local function hasLink(claim, site, lang) if (claim.mainsnak.snaktype ~= 'value') then -- ne pas supprimer les valeurs spéciales, il y a une fonction dédiée pour ça return true end local id = wd.getMainId(claim) local link = wd.siteLink(id, site, lang) if link then return true end end local function isInLanguage(claim, lang) -- ne fonctionne que pour les monolingualtext / étendre aux autres types en utilisant les qualifiers ? if type(lang) == 'table' then -- si c'est une table de language séparées par des virgules, on les accepte toutes for i, l in pairs(lang) do local v = isInLanguage(claim, l) if v then return true end end end if type(lang) ~= ('string') then return --? end if (lang == '-') then return true end if (lang == 'locallang') then lang = mw.getContentLanguage():getCode() end -- pour les monolingual text local snak = claim.mainsnak or claim if snak.snaktype == 'value' and snak.datavalue.type == 'monolingualtext' then if snak.datavalue.value.language == lang then return true end return false end -- pour les autres types de données : recherche dans les qualificatifs if (lang == 'fr') then lang = 'Q150' elseif (lang == 'en') then lang = 'Q1860' else lang = databases.invertedlangcodes[lang] end if claim.qualifiers and claim.qualifiers.P407 then if wd.hasQualifier(claim, {'P407'}, {lang}) then return true else return false end end return true -- si on ne ne sait pas la langue, on condière que c'est bon end local function firstVals(claims, numval) -- retourn les numval premières valeurs de la table claims local numval = tonumber(numval) or 0 -- raise a error if numval is not a positive integer ? if not claims then return nil end while (#claims > numval) do table.remove(claims) end return claims end local function lastVals(claims, numval2) -- retourn les valeurs de la table claims à partir de numval2 local numval2 = tonumber(numval2) or 0 -- raise a error if numval is not a positive integer ? if not claims then return nil end for i=1,numval2 do table.remove(claims, 1) end return claims end -- retourne les valeurs de la table claims à partir de removedupesdate, -- sans les dates en doublons avec conversion entre les calendrier julien et grégorien, -- ou uniquement en catégorisant si le paramètre removedupesdate est égale à 'cat' local function removeDupesDate(claims, removedupesdate) if not claims or #claims < 2 then return claims, '' end local cat = '' local newClaims = {} local newIsos = {} local function findIndex(searchset, val) -- similaire à wd.isHere mais retourne l'index de la valeur trouvée for i, j in pairs(searchset) do if val == j then return i end end return -1 end for _, claim in ipairs( claims ) do local snak = claim.mainsnak or claim if (snak.snaktype == 'value') and (snak.datatype == 'time') and snak.datavalue.value.precision >= 11 then -- s'il s'agit d'un time et que la précision est au moins l'année local iso = snak.datavalue.value.time _, _, iso = string.find(iso, "(+%d+-%d+-%d+T)") local deleteIfDuplicate = false if snak.datavalue.value.calendarmodel == 'http://www.wikidata.org/entity/Q1985727' then -- si la date est grégorienne if modules.formatDate.before('+1582', iso) then -- si avant 1582 on calcule la date julienne _, _, y, m, d = string.find(iso, "+(%d+)-(%d+)-(%d+)T") y, m , d = modules.datemodule.gregorianToJulian(y, m , d) if m < 10 then m = '0' .. m end if d < 10 then d = '0' .. d end iso = '+' .. y .. '-' .. m .. '-' .. d .. 'T' deleteIfDuplicate = true end local index = findIndex(newIsos, iso) if index >= 0 then -- si la date est déjà présente cat = cat .. '[[Catégorie:Article avec des dates identiques venant de wikidata dans le code de l\'infobox]]' if removedupesdate == "cat" then -- ne faire que catégoriser table.insert(newIsos, iso) table.insert(newClaims, claim) elseif not deleteIfDuplicate then -- supprimer l'autre date si la date courante n'a pas été convertie newClaims[index] = claim end -- sinon supprimer la date courante else -- pas de doublon table.insert(newIsos, iso) table.insert(newClaims, claim) end elseif snak.datavalue.value.calendarmodel == 'http://www.wikidata.org/entity/Q1985786' then -- si date julienne if not modules.formatDate.before('+1582', iso) then -- si après 1582 on calcule la date grégorienne _, _, y, m, d = string.find(iso, "+(%d+)-(%d+)-(%d+)T") y, m , d = modules.datemodule.julianToGregorian(y, m , d) if m < 10 then m = '0' .. m end if d < 10 then d = '0' .. d end iso = '+' .. y .. '-' .. m .. '-' .. d .. 'T' deleteIfDuplicate = true end local index = findIndex(newIsos, iso) if index >= 0 then -- si date déjà présente cat = cat .. '[[Catégorie:Article avec des dates identiques venant de wikidata dans le code de l\'infobox]]' if removedupesdate == "cat" then -- ne faire que catégoriser table.insert(newIsos, iso) table.insert(newClaims, claim) elseif not deleteIfDuplicate then -- supprimer l'autre date si la date courante n'a pas été convertie newClaims[index] = claim end -- sinon supprimer la date courante else -- pas de doublon table.insert(newIsos, iso) table.insert(newClaims, claim) end else -- autre calendrier table.insert(newIsos, iso) table.insert(newClaims, claim) end else -- précision insuffisante table.insert(newIsos, iso) table.insert(newClaims, claim) end end return newClaims, cat end local function timeFromQualifs(claim, qualifs) local claimqualifs = claim.qualifiers if not claimqualifs then return nil end for i, qualif in ipairs(qualifs or datequalifiers) do local vals = claimqualifs[qualif] if vals and (vals[1].snaktype == 'value') then return vals[1].datavalue.value.time, vals[1].datavalue.value.precision end end end local function atDate(claim, mydate) if mydate == "today" then mydate = os.date("!%Y-%m-%dT%TZ") end -- determines required precision depending on the atdate format local d = mw.text.split(mydate, "-") local myprecision if d[3] then myprecision = 11 -- day elseif d[2] then myprecision = 10 -- month else myprecision = 9 -- year end -- with point in time local d, storedprecision = timeFromQualifs(claim, {'P585'}) if d then return modules.formatDate.equal(mydate, d, math.min(myprecision, storedprecision)) end -- with start or end date -- TODO: precision local mindate = timeFromQualifs(claim, {'P580'}) local maxdate = timeFromQualifs(claim, {'P582'}) if modules.formatDate.before(mydate, mindate) and modules.formatDate.before(maxdate, mydate) then return true end return false end local function check(claim, condition) if type(condition) == 'function' then -- cas standard return condition(claim) end return formatError('invalid type', 'function', type(condition)) end local function minPrecision(claim, minprecision) local snak if claim.qualifiers then -- si une date est donnée en qualificatif, c'est elle qu'on utilise de préférence au mainsnak for i, j in ipairs(datequalifiers) do if claim.qualifiers[j] then snak = claim.qualifiers[j][1] break end end end if not snak then snak = claim.mainsnak or claim end if (snak.snaktype == 'value') and (snak.datatype == 'time') and (snak.datavalue.value.precision < minprecision) then return false end return true end function wd.sortClaims(claims, sorttype) if not claims then return nil end if wd.isHere({'chronological', 'order', 'inverted', 'age', 'ageinverted'}, sorttype) then return wd.chronoSort(claims, sorttype) elseif sorttype == 'ascending' then return wd.quantitySort(claims) elseif sorttype == 'descending' then return wd.quantitySort(claims, true) elseif type(sorttype) == 'function' then table.sort(claims, sorttype) return claims elseif type(sorttype) == 'string' and sorttype:sub(1, 1) == 'P' then return wd.numericPropertySort(claims, sorttype) end return claims end function wd.filterClaims(claims, args) --retire de la tables de claims celles qui sont éliminés par un des filters de la table des filters local function filter(condition, filterfunction, funargs) if not args[condition] then return end for i = #claims, 1, -1 do if not( filterfunction(claims[i], args[funargs[1]], args[funargs[2]], args[funargs[3]]) ) then table.remove(claims, i) end end end filter('isinlang', isInLanguage, {'isinlang'} ) filter('excludespecial', notSpecial, {} ) filter('condition', check, {'condition'} ) if claims[1] and claims[1].mainsnak then filter('targetvalue', hasTargetValue, {'targetvalue'} ) filter('targetclass', hasTargetClass, {'targetclass'} ) filter('targetsuperclass', hasTargetSuperclass, {'targetsuperclass'} ) filter('atdate', atDate, {'atdate'} ) filter('qualifier', wd.hasQualifier, {'qualifier', 'qualifiervalue'} ) filter('qualifiernumber', wd.hasQualifierNumber, {'qualifiernumber', 'qualifiernumbervalue'} ) filter('excludequalifier', excludeQualifier, {'excludequalifier', 'excludequalifiervalue'} ) filter('withsource', hasSource, {'withsource', 'sourceproperty'} ) filter('withdate', wd.hasDate, {} ) filter('excludevalues', excludeValues, {'excludevalues'}) filter('excludeclasses', excludeClasses, {'excludeclasses'}) filter('excludesuperclasses', excludeSuperclasses, {'excludesuperclasses'}) filter('withlink', hasLink, {'withlink', 'linklang'} ) filter('minprecision', minPrecision, {'minprecision'} ) claims = withRank(claims, args.rank or 'best') end if #claims == 0 then return nil end if args.sorttype then claims = wd.sortClaims(claims, args.sorttype) end if args.numval2 then claims = lastVals(claims, args.numval2) end if args.numval then claims = firstVals(claims, args.numval) end return claims end function wd.loadEntity(entity, cache) if type(entity) ~= 'table' then if cache then if not cache[entity] then cache[entity] = mw.wikibase.getEntity(entity) mw.log("cached") end return cache[entity] else if entity == '' or (entity == '-') then entity = nil end return mw.wikibase.getEntity(entity) end else return entity end end function wd.getClaims( args ) -- returns a table of the claims matching some conditions given in args if args.claims then -- if claims have already been set, return them return args.claims end local properties = args.property if type(properties) == 'string' then properties = wd.splitStr(string.upper(args.property)) end if not properties then return formatError( 'property-param-not-provided' ) end --Get entity local entity = args.entity if type(entity) == 'string' then if entity == '' then entity = nil end elseif type(entity) == 'table' then entity = entity.id end if (not entity) then entity = mw.wikibase.getEntityIdForCurrentPage() end if (not entity) or (entity == '-') or (entity == wd.translate('somevalue')) or (entity == modules.linguistic.ucfirst(wd.translate('somevalue'))) then return nil end if args.labelformat and args.labelformat == 'gendered' then local longgender = {m = 'male', f = 'female'} args.labelformat = longgender[wd.getgender(entity)] end local claims = {} if #properties == 1 then claims = mw.wikibase.getAllStatements(entity, properties[1]) -- do not use mw.wikibase.getBestStatements at this stage, as it may remove the best ranked values that match other criteria in the query else for i, prop in ipairs(properties) do local newclaims = mw.wikibase.getAllStatements(entity, prop) if newclaims and #newclaims > 0 then for j, claim in ipairs(newclaims) do table.insert(claims, claim) end end end end if (not claims) or (#claims == 0) then return nil end return wd.filterClaims(claims, args) end --=== ENTITY FORMATTING === function wd.getLabel(entity, lang1, lang2) if (not entity) then return nil -- ou option de gestion des erreurs ? end entity = entity.id or ( type(entity) == "string" and entity) if not(type(entity) == 'string') then return nil end lang1 = lang1 or defaultlang local str, lang --str : texte rendu, lang : langue de celui-ci if lang1 == defaultlang then -- le plus économique str, lang = mw.wikibase.getLabelWithLang(entity) -- le libellé peut être en français ou en anglais else str = mw.wikibase.getLabelByLang(entity, lang1) if str then lang = lang1 end end if str and (lang == lang1 or lang == "mul") then --pas de catégorie "à traduire" si on a obtenu un texte dans la langue désirée (normalement fr) ou multilingue return str end if lang2 then -- langue secondaire, avec catégorie "à traduire" str2 = mw.wikibase.getLabelByLang(entity, lang2) if str2 then lang = lang2 str = str2 end end if not str then --si ni lang1, ni lang2 ni l'anglais ne sont présents, parcours de la hiérarchie des langues for _, trylang in ipairs(databases.langhierarchy.codes) do str = mw.wikibase.getLabelByLang(entity, trylang) if str then lang = trylang break end end end if str then local translationCat = databases.i18n['to translate'] translationCat = translationCat .. (databases.langhierarchy.cattext[lang] or '') translationCat = addCat(translationCat) return str, translationCat end end function wd.formatEntity( entity, params ) if (not entity) then return nil --formatError('entity-not-found') end local id = entity if type(id) == 'table' then id = id.id end params = params or {} local lang = params.lang or defaultlang local speciallabels = params.speciallabels local displayformat = params.displayformat local labelformat = params.labelformat local labelformat2 = params.labelformat2 local defaultlabel = params.defaultlabel or id local linktype = params.link local defaultlink = params.defaultlink local defaultlinkquery = params.defaultlinkquery if speciallabels and speciallabels[id] then --speciallabels override the standard label + link combination return speciallabels[id] end if params.displayformat == 'raw' then return id end if params.labelformat == 'male' then labelformat = function(objectid) return wd.genderedlabel(objectid, 'm') end end if params.labelformat == 'female' then labelformat = function(objectid) return wd.genderedlabel(objectid, 'f') end end local label, translationCat if type(labelformat) == 'function' then -- sert à des cas particuliers label, translationCat = labelformat(entity) end if not label then label, translationCat = wd.getLabel(entity, lang, params.wikidatalang) end if type(labelformat2) == 'function' and label then -- sert à des cas particuliers label = labelformat2(label) end translationCat = translationCat or "" -- sera toujours ajoutée au résultat mais sera vide si la catégorie de maintenance n'est pas nécessaire if not label then if (defaultlabel == '-') then return nil end local link = wd.siteLink(id, 'wikidata') return '[[' .. link .. '|' .. id .. ']]' .. translationCat -- si pas de libellé, on met un lien vers Wikidata pour qu'on comprenne à quoi ça fait référence end -- détermination du fait qu'on soit ou non en train de rendre l'élément sur la page de son article local rendering_entity_on_its_page = wd.isPageOfQId(id) if (linktype == '-') or rendering_entity_on_its_page then return label .. translationCat end local link = wd.siteLink(entity, linktype, lang) -- defaultlinkquery will try to link to another page on this Wiki if (not link) and defaultlinkquery then if type(defaultlinkquery) == 'string' then defaultlinkquery = {property = defaultlinkquery} end defaultlinkquery.excludespecial = true defaultlinkquery.entity = entity local claims = wd.getClaims(defaultlinkquery) if claims then for i, j in pairs(claims) do local id = wd.getMainId(j) link = wd.siteLink(id, linktype, lang) if link then break end end end end if link then if link:match('^Category:') or link:match('^Catégorie:') then -- attention, le « é » est multibyte -- lier vers une catégorie au lieu de catégoriser link = ':' .. link end return '[[' .. link .. '|' .. label .. ']]' .. translationCat end -- if not link, you can use defaultlink: a sidelink to another Wikimedia project if (not defaultlink) then defaultlink = {'enwiki'} end if defaultlink and (defaultlink ~= '-') then local linktype local sidelink, site, langcode if type(defaultlink) == 'string' then defaultlink = {defaultlink} end for i, j in ipairs(defaultlink) do sidelink, site, langcode = wd.siteLink(entity, j, lang) if sidelink then break end end if not sidelink then sidelink, site = wd.siteLink(entity, 'wikidata') end local icon, class, title = site, nil, nil -- le texte affiché du lien if site == 'wiki' then icon, class, title = langcode, "indicateur-langue", wd.translate('see-another-language', mw.language.fetchLanguageName(langcode, defaultlang)) elseif site == 'wikidata' then icon, class, title = 'd', "indicateur-langue", wd.translate('see-wikidata') else title = wd.translate('see-another-project', site) end local val = '[[' .. sidelink .. '|' .. '<span class = "' .. (class or '').. '" title = "' .. (title or '') .. '">' .. icon .. '</span>]]' return label .. ' <small>(' .. val .. ')</small>' .. translationCat end return label .. translationCat end function wd.addTrackingCat(prop, cat) -- doit parfois être appelé par d'autres modules if type(prop) == 'table' then prop = prop[1] -- devrait logiquement toutes les ajouter end if not prop and not cat then return formatError("property-param-not-provided") end if not cat then cat = wd.translate('trackingcat', prop or 'P??') end return addCat(cat ) end local function unknownValue(snak, label) local str = label if type(str) == "function" then str = str(snak) end if (not str) then if snak.datatype == 'time' then str = wd.translate('sometime') else str = wd.translate('somevalue') end end if type(str) ~= "string" then return formatError(snak.datatype) end return str end local function noValue(displayformat) if not displayformat then return wd.translate('novalue') end if type(displayformat) == 'string' then return displayformat end return formatError() end local function getLangCode(entityid) return databases.langcodes[tonumber(entityid:sub(2))] end local function showLang(statement) -- retourne le code langue entre parenthèses avant la valeur (par exemple pour les biblios et liens externes) local mainsnak = statement.mainsnak if mainsnak.snaktype ~= 'value' then return nil end local langlist = {} if mainsnak.datavalue.type == 'monolingualtext' then langlist = {mainsnak.datavalue.value.language} elseif (not statement.qualifiers) or (not statement.qualifiers.P407) then return else for i, j in pairs( statement.qualifiers.P407 ) do if j.snaktype == 'value' then local langentity = wd.getId(j) local langcode = getLangCode(langentity) table.insert(langlist, langcode) end end end if (#langlist > 1) or (#langlist == 1 and langlist[1] ~= defaultlang) then -- si c'est en français, pas besoin de le dire langlist.maxLang = 3 return modules.langmodule.indicationMultilingue(langlist) end end -- === DATE HANDLING === local function fuzzydate(str, precision) -- ajoute le qualificatif "vers" à une date if not str then return nil end if (precision >= 11) or (precision == 7) or (precision == 6) then --dates avec jour, siècles, millénaires return "vers le " .. str end if (precision == 8) then --décennies ("années ...") return "vers les " .. str end return "vers " .. str end function wd.addStandardQualifs(str, statement, onlygeneral) -- qualificateurs de date ou de lieu approximatif ou d'info globalement incertaine ; onlygenereal=true pour rerstreindre à ces derniers if (not statement) or (not statement.qualifiers) then return str end if not str then return error()-- what's that ? end if statement.qualifiers.P1480 then for i, j in pairs(statement.qualifiers.P1480) do local v = wd.getId(j) if (v == "Q21818619") and not onlygeneral then --"à proximité de" str = wd.translate('approximate-place', str) elseif (v == "Q18122778") or (v == "Q18912752") or (v == "Q56644435") or (v == "Q30230067") then --"présumé", "controversé", "probablement", "possible" str = wd.translate('uncertain-information', str) elseif (v == "Q5727902") and not onlygeneral and statement.mainsnak.datatype == 'time' then --date approximative local datevalue = statement.mainsnak.datavalue if datevalue then str = fuzzydate(str, datevalue.value.precision) end end end end return str end function wd.getDateFromQualif(statement, qualif) if (not statement) or (not statement.qualifiers) or not (statement.qualifiers[qualif]) then return nil end local v = statement.qualifiers[qualif][1] if v.snaktype ~= 'value' then -- que faire dans ce cas ? return nil end return modules.formatDate.dateObject(v.datavalue.value) end function wd.getDate(statement) local period = wd.getDateFromQualif(statement, 'P585') -- retourne un dateobject if period then return period end local begin, ending = wd.getDateFromQualif(statement, 'P580'), wd.getDateFromQualif(statement, 'P582') if begin or ending then return modules.formatDate.rangeObject(begin, ending) -- retourne un rangeobject fait de deux dateobject end return nil end function wd.getFormattedDate(statement, params) if not statement then return nil end local str --cherche la date avec les qualifs P580/P582 local datetable = wd.getDate(statement) if datetable then str = modules.formatDate.objectToText(datetable, params) end -- puis limite intérieur / supérieur if not str then local start, ending = wd.getDateFromQualif(statement, 'P1319'), wd.getDateFromQualif(statement, 'P1326') str = modules.formatDate.between(start, ending, params) end local fromqualif = false if str then fromqualif = true end --si la date est tirée des qualificateurs, on n'y ajoute pas l'éventuel "vers ..." -- sinon, le mainsnak, pour les données de type time if (not str) and (statement.mainsnak.datatype == 'time') then local mainsnak = statement.mainsnak if (mainsnak.snaktype == 'value') or (mainsnak.snaktype == 'somevalue') then str = wd.formatSnak(mainsnak, params) end end if str and params and (params.addstandardqualifs ~= '-') then str = wd.addStandardQualifs(str, statement, fromqualif) end return str end wd.compare.by_quantity = function(c1, c2) local v1 = wd.getDataValue(c1.mainsnak) local v2 = wd.getDataValue(c2.mainsnak) if not (v1 and v2) then return true end return v1 < v2 end --[[ tri chronologique générique : retourne une fonction de tri de liste de déclaration en fonction d’une fonction qui calcule la clé de tri et d’une fonction qui compare les clés de tri paramètres nommés: (appel type wikidata.compare.chrono_key_sort{sortKey="nom clé"}) sortKey (optionnel) : chaine, le nom de la clé utilisée pour un tri (pour éviter de rentrer en collision avec "dateSortKey" utilisé par chronoSort au besoin) snak_key_get_function : fonction qui calcule la valeur de la clé à partir d’un snak ou d’une déclaration, (obligatoire) le résultat n’est calculé qu’une fois et est stocké en cache dans claim[sortKey] key_compare_function : fonction de comparaison des clés calculées par snak_key_get_function (optionnel) --]] function wd.chrono_key_sort(arg) local snak_key_get_function = arg.snak_key_get_function local sortKey = arg.sortKey or "dateSortKey" local key_compare_function = arg.key_compare_function or function(c1, c2) return c1 < c2 end return function(claims) for _, claim in ipairs( claims ) do if not claim[sortKey] then local key = snak_key_get_function(claim) if key then claim[sortKey] = wd.compare.get_claim_date(key) else claim[sortKey] = 0 end end end table.sort( claims, function(c1, c2) return key_compare_function(c1[sortKey], c2[sortKey]) end ) return claims end end function wd.quantitySort(claims, inverted) local function sort(c1, c2) local v1 = wd.getDataValue(c1.mainsnak) local v2 = wd.getDataValue(c2.mainsnak) if not (v1 and v2) then return true end if inverted then return v2 < v1 end return v1 < v2 end table.sort(claims, sort ) return claims end function wd.compare.get_claim_date(claim, datetype) -- rend une date au format numérique pour faire des comparaisons local snak = claim.mainsnak or claim if datetype and datetype == 'personbirthdate' then -- fonctionne avec un claim dont la valeur est une personne dont on va rendre la date de naissance if (snak.snaktype == 'value') and (snak.datatype == 'wikibase-item') then local personid = wd.getId(snak) local birthclaims = wd.getClaims({ entity = personid, property = 'P569', numval = 1}) if birthclaims then return wd.compare.get_claim_date(birthclaims[1] or birthclaims) else return math.huge end else return math.huge end -- en cas de donnée manquante, valeur infinie qui entraîne le classement en fin de liste end local iso, datequalif, isonumber if (snak.snaktype == 'value') and (snak.datatype == 'time') then iso = snak.datavalue.value.time else for i, dqualif in ipairs(datequalifiers) do iso = timeFromQualifs(claim, {dqualif}) if iso then datequalif = dqualif break end end if not iso then return math.huge end end -- transformation en nombre (indication de la base car gsub retourne deux valeurs) isonumber = tonumber( iso:gsub( '(%d)%D', '%1' ), 10 ) -- ajustement de la date tenant compte du qualificatif dont elle est issue : un fait se terminant à une date est antérieur à un autre commençant à cette date if datequalif == 'P582' then --date de fin isonumber = isonumber - 2 elseif datequalif == 'P1326' then -- date au plus tard isonumber = isonumber - 1 elseif datequalif == 'P1319' then -- date au plus tôt isonumber = isonumber + 1 elseif datequalif == 'P571' or datequalif == 'P580' then -- date de début et date de création isonumber = isonumber + 2 end return isonumber end function wd.compare.chronoCompare(c1, c2) return wd.compare.get_claim_date(c1) < wd.compare.get_claim_date(c2) end -- fonction pour renverser l’ordre d’une autre fonction function wd.compare.rev(comp_criteria) return function(c1, c2) -- attention les tris en lua attendent des fonctions de comparaison strictement inférieur, on doit -- vérifier la non égalité quand on inverse l’ordre d’un critère, d’ou "and comp_criteria(c2,c1)" return not(comp_criteria(c1,c2)) and comp_criteria(c2,c1) end end -- Fonction qui trie des Claims de type time selon l'ordre chronologique -- Une clé de tri nomée « dateSortKey » est ajouté à chaque claim. -- Si des clés de tri de ce nom existent déjà, elles sont utilisées sans modification. function wd.chronoSort( claims, sorttype ) for _, claim in ipairs( claims ) do if not claim.dateSortKey then if sorttype and (sorttype == 'age' or sorttype == 'ageinverted') then claim.dateSortKey = wd.compare.get_claim_date(claim, 'personbirthdate') else claim.dateSortKey = wd.compare.get_claim_date(claim) end if sorttype and (sorttype == 'inverted' or sorttype == 'ageinverted') and claim.dateSortKey == math.huge then claim.dateSortKey = -math.huge -- quand la donnée est manquante on lui assigne la valeur qui entraîne le classement en fin de liste end end end table.sort( claims, function ( c1, c2 ) if sorttype and (sorttype == 'inverted' or sorttype == 'ageinverted') then return c2.dateSortKey < c1.dateSortKey end return c1.dateSortKey < c2.dateSortKey end ) return claims end local function get_numeric_claim_value(claim, propertySort) local val local claimqualifs = claim.qualifiers if claimqualifs then local vals = claimqualifs[propertySort] if vals and vals[1].snaktype == 'value' then val = vals[1].datavalue.value end end return tonumber(val or 0) end function wd.compare.numeric(propertySort) return function(c1, c2) return get_numeric_claim_value(c1, propertySort) < get_numeric_claim_value(c2, propertySort) end end -- Fonction qui trie des Claims de type value selon l'ordre de la propriété fournit -- Une clé de tri nomée « dateSortKey » est ajouté à chaque claim. -- Si des clés de tri de ce nom existent déjà, elles sont utilisées sans modification. function wd.numericPropertySort( claims, propertySort ) for _, claim in ipairs( claims ) do if not claim.dateSortKey then local val = get_numeric_claim_value(claim, propertySort) claim.dateSortKey = tonumber(val or 0) end end table.sort( claims, function ( c1, c2 ) return c1.dateSortKey < c2.dateSortKey end ) return claims end --[[ test possible en console pour la fonction précédente : = p.formatStatements{entity = "Q375946", property = 'P50', sorttype = 'P1545', linkback = "true"} --]] -- =================== function wd.getReferences(statement) local refdata = statement.references if not refdata then return nil end local refs = {} local hashes = {} for i, ref in pairs(refdata) do local s local function hasValue(prop) -- checks that the prop is here with valid value if ref.snaks[prop] and ref.snaks[prop][1].snaktype == 'value' then return true end return false end if ref.snaks.P248 then -- cas lorsque P248 (affirmé dans) est utilisé for j, source in pairs(ref.snaks.P248) do if source.snaktype == 'value' then local page, accessdate, quotation if hasValue('P304') then -- page page = wd.formatSnak(ref.snaks.P304[1]) end if hasValue('P813') then -- date de consultation accessdate = wd.formatSnak(ref.snaks.P813[1]) end if hasValue('P1683') then -- citation quotation = wd.formatSnak(ref.snaks.P1683[1]) end local sourceId = wd.getId(source) s = modules.reference.citeitem(sourceId, {['page'] = page, ['accessdate'] = accessdate, ['citation'] = quotation}) table.insert(refs, s) table.insert(hashes, ref.hash .. sourceId) end end elseif hasValue('P8091') or hasValue('P854') then -- cas lorsque P8091 (Archival Resource Key) ou P854 (URL de la référence)est utilisé local arkKey, url, title, author, publisher, accessdate, publishdate, publishlang, quotation, description if hasValue('P8091') then arkKey = wd.formatSnak(ref.snaks.P8091[1], {text = "-"}) url = 'https://n2t.net/' .. arkKey if hasValue('P1476') then title = wd.formatSnak(ref.snaks.P1476[1]) else title = arkKey end elseif hasValue('P854') then url = wd.formatSnak(ref.snaks.P854[1], {text = "-"}) if hasValue('P1476') then title = wd.formatSnak(ref.snaks.P1476[1]) else title = mw.ustring.gsub(url, '^[Hh][Tt][Tt][Pp]([Ss]?):(/?)([^/])', 'http%1://%3') end end --todo : handle multiple values for author, etc. if hasValue('P1810') then -- sous le nom description = 'sous le nom ' .. wd.formatSnak(ref.snaks.P1810[1]) end if hasValue('P813') then -- date de consultation accessdate = wd.formatSnak(ref.snaks.P813[1]) end if hasValue('P50') then -- author (item type) author = wd.formatSnak(ref.snaks.P50[1]) elseif hasValue('P2093') then -- author (string type) author = wd.formatSnak(ref.snaks.P2093[1]) end if hasValue('P123') then -- éditeur publisher = wd.formatSnak(ref.snaks.P123[1]) end if hasValue('P1683') then -- citation quotation = wd.formatSnak(ref.snaks.P1683[1]) end if hasValue('P577') then -- date de publication publishdate = wd.formatSnak(ref.snaks.P577[1]) end if hasValue('P407') then -- langue de l'œuvre local id = wd.getId(ref.snaks.P407[1]) publishlang = getLangCode(id) end s = modules.cite.lienWeb{titre = title, url = url, auteur = author, editeur = publisher, langue = publishlang, ['en ligne le'] = publishdate, ['consulté le'] = accessdate, ['citation'] = quotation, ['description'] = description} table.insert(hashes, ref.hash) table.insert(refs, s) elseif ref.snaks.P854 and ref.snaks.P854[1].snaktype == 'value' then s = wd.formatSnak(ref.snaks.P854[1], {text = "-"}) table.insert(hashes, ref.snaks.P854[1].hash) table.insert(refs, s) end end if #refs > 0 then if #hashes == #refs then return refs, hashes end return refs end end function wd.sourceStr(sources, hashes) if not sources or (#sources == 0) then return nil end local useHashes = hashes and #hashes == #sources for i, j in ipairs(sources) do local refArgs = {name = 'ref', content = j} if useHashes and hashes[i] ~= '-' then refArgs.args = {name = 'wikidata-' .. hashes[i]} end sources[i] = mw.getCurrentFrame():extensionTag(refArgs) end return table.concat(sources, '<sup class="reference cite_virgule">,</sup>') end function wd.getDataValue(snak, params) if not params then params = {} end local speciallabels = params.speciallabels -- parfois on a besoin de faire une liste d'éléments pour lequel le libellé doit être changé, pas très pratique d'utiliser une fonction pour ça if snak.snaktype ~= 'value' then return nil end local datatype = snak.datatype local value = snak.datavalue.value local displayformat = params.displayformat if type(displayformat) == 'function' then return displayformat(snak, params) end if datatype == 'wikibase-item' then return wd.formatEntity(wd.getId(snak), params) end if datatype == 'url' then if params.displayformat == 'raw' then return value else return modules.weblink.makelink(value, params.text) end end if datatype == 'math' then return mw.getCurrentFrame():extensionTag( "math", value) end if datatype == 'tabular-data' then return mw.ustring.sub(value, 6, 100) -- returns the name of the file, without the "Data:" prefix end if (datatype == 'string') or (datatype == 'external-id') or (datatype == 'commonsMedia') then -- toutes les données de type string sauf "math" if params.urlpattern then local urlpattern = params.urlpattern if type(urlpattern) == 'function' then urlpattern = urlpattern(value) end -- encodage de l'identifiant qui se retrouve dans le path de l'URL, à l'exception des slashes parfois rencontrés, qui sont des séparateurs à ne pas encoder local encodedValue = mw.uri.encode(value, 'PATH'):gsub('%%2F', '/') -- les parenthèses autour du encodedValue:gsub() sont nécessaires, sinon sa 2e valeur de retour est aussi passée en argument au mw.ustring.gsub() parent local url = mw.ustring.gsub(urlpattern, '$1', (encodedValue:gsub('%%', '%%%%'))) value = '[' .. url .. ' ' .. (params.text or value) .. ']' end return value end if datatype == 'time' then -- format example: +00000001809-02-12T00:00:00Z if displayformat == 'raw' then return value.time else local dateobject = modules.formatDate.dateObject(value, {precision = params.precision}) return modules.formatDate.objectToText(dateobject, params) end end if datatype == 'globe-coordinate' then -- retourne une table avec clés latitude, longitude, précision et globe à formater par un autre module (à changer ?) if displayformat == 'latitude' then return value.latitude elseif displayformat == 'longitude' then return value.longitude else local coordvalue = mw.clone( value ) coordvalue.globe = databases.globes[value.globe] -- transforme l'ID du globe en nom anglais utilisable par geohack return coordvalue -- note : les coordonnées Wikidata peuvent être utilisée depuis Module:Coordinates. Faut-il aussi autoriser à appeler Module:Coordiantes ici ? end end if datatype == 'quantity' then -- todo : gérer les paramètres précision local amount, unit = value.amount, value.unit if unit then unit = unit:match('Q%d+') end if not unit then unit = 'dimensionless' end local raw if displayformat == "raw" then raw = true end return modules.formatNum.displayvalue(amount, unit, {targetunit = params.targetunit, raw = raw, rounding = params.rounding, showunit = params.showunit or 'short', showlink = params.showlink} ) end if datatype == 'monolingualtext' then if value.language == defaultlang then return value.text else return modules.langmodule.langue({value.language, value.text, nocat=true}) end end return formatError('unknown-datavalue-type' ) end function wd.stringTable(args) -- like getClaims, but get a list of string rather than a list of snaks, for easier manipulation local claims = args.claims local cat = '' if not claims then claims = wd.getClaims(args) end if not claims or claims == {} then return {}, {}, cat end if args.removedupesdate and (args.removedupesdate ~= '-') then claims, cat = removeDupesDate(claims, args.removedupesdate) end local props = {} -- liste des propriétés associété à chaque string pour catégorisation et linkback for i, j in pairs(claims) do claims[i] = wd.formatStatement(j, args) table.insert(props, j.mainsnak.property) end if args.removedupes and (args.removedupes ~= '-') then claims = wd.addNewValues({}, claims) -- devrait aussi supprimer de props celles qui ne sont pas utilisées end return claims, props, cat end function wd.getQualifiers(statement, qualifs, params) if not statement.qualifiers then return nil end local vals = {} if type(qualifs) == 'string' then qualifs = wd.splitStr(qualifs) end for i, j in pairs(qualifs) do if statement.qualifiers[j] then for k, l in pairs(statement.qualifiers[j]) do table.insert(vals, l) end end end if #vals == 0 then return nil end return vals end function wd.getFormattedQualifiers(statement, qualifs, params) if not params then params = {} end local qualiftable = wd.getQualifiers(statement, qualifs) if not qualiftable then return nil end qualiftable = wd.filterClaims(qualiftable, params) or {} for i, j in pairs(qualiftable) do qualiftable[i] = wd.formatSnak(j, params) end return modules.linguistic.conj(qualiftable, params.conjtype) end function wd.showQualifiers(str, statement, args) local qualifs = args.showqualifiers if not qualifs then return str -- or error ? end if type(qualifs) == 'string' then qualifs = wd.splitStr(qualifs) end local qualifargs = args.qualifargs or {} -- formatage des qualificatifs = args commençant par "qualif", ou à défaut, les mêmes que pour la valeur principale qualifargs.displayformat = args.qualifdisplayformat or args.displayformat qualifargs.labelformat = args.qualiflabelformat or args.labelformat qualifargs.labelformat2 = args.qualiflabelformat2 or args.labelformat2 qualifargs.link = args.qualiflink or args.link qualifargs.linktopic = args.qualiflinktopic or args.linktopic qualifargs.conjtype = args.qualifconjtype qualifargs.precision = args.qualifprecision qualifargs.targetunit = args.qualiftargetunit qualifargs.defaultlink = args.qualifdefaultlink or args.defaultlink qualifargs.defaultlinkquery = args.qualifdefaultlinkquery or args.defaultlinkquery local formattedqualifs if args.qualifformat and type (args.qualifformat) == 'function' then formattedqualifs = args.qualifformat(statement, qualifs, qualifargs) else formattedqualifs = wd.getFormattedQualifiers(statement, qualifs, qualifargs) end if formattedqualifs and formattedqualifs ~= "" then str = str .. " (" .. formattedqualifs .. ")" end return str end function wd.formatSnak( snak, params ) if not params then params = {} end -- pour faciliter l'appel depuis d'autres modules if snak.snaktype == 'somevalue' then return unknownValue(snak, params.unknownlabel) elseif snak.snaktype == 'novalue' then return noValue(params.novaluelabel) elseif snak.snaktype == 'value' then return wd.getDataValue( snak, params) else return formatError( 'unknown-snak-type' ) end end function wd.formatStatement( statement, args ) -- FONCTION A REORGANISER (pas très lisible) if not args then args = {} end if not statement.type or statement.type ~= 'statement' then return formatError( 'unknown-claim-type' ) end local prop = statement.mainsnak.property local str -- special displayformat f if args.statementformat and (type(args.statementformat) == 'function') then str = args.statementformat(statement, args) elseif (statement.mainsnak.datatype == 'time') and (statement.mainsnak.dateformat ~= '-') then if args.displayformat == 'raw' and statement.mainsnak.snaktype == 'value' then str = statement.mainsnak.datavalue.value.time else str = wd.getFormattedDate(statement, args) end elseif args.showonlyqualifier and (args.showonlyqualifier ~= '') then str = wd.getFormattedQualifiers(statement, args.showonlyqualifier, args) if not str then return nil end if args.addstandardqualifs ~= '-' then str = wd.addStandardQualifs(str, statement, true) end else str = wd.formatSnak( statement.mainsnak, args ) if (args.addstandardqualifs ~= '-') and (args.displayformat ~= 'raw') then str = wd.addStandardQualifs(str, statement) end end -- ajouts divers if args.showlang == true then local indicateur = showLang(statement) if indicateur then str = indicateur .. ' ' .. 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