--script that retrieves basic data stored in Wikidata, for the datamodel, see https://www.mediawiki.org/wiki/Extension:Wikibase_Client/Lua
local wd = {}
local modules = { }
local modulesNames = {
reference = 'Module:Wikidata/Références',
i18n = 'Module:Wikidata/I18n',
globes = 'Module:Wikidata/Globes',
langcodes = 'Module:Dictionnaire Wikidata/Codes langue', -- big, infrequently useda
invertedlangcodes = 'Module:Dictionnaire Wikidata/Codes langue/inversé',
linguistic = 'Module:Linguistique',
formatDate = 'Module:Date complexe2',
formatNum = 'Module:Conversion',
langmodule = 'Module:Langue',
cite = 'Module:Biblio',
weblink = 'Module:Weblink'
}
local function loadModule( t, key )
if modulesNames[key] then
local m = require( modulesNames[key] )
t[key] = m
return m
end
end
setmetatable( modules, { __index = loadModule } )
local datequalifiers = {'P585', 'P571', 'P580', 'P582', 'P1319', 'P1326'}
-- === I18n ===
local defaultlang = mw.getContentLanguage():getCode()
function wd.translate(str, rep1, rep2)
str = modules.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(modules.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)
for i, j in pairs(searchset) do
if val == j then
return true
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
return mw.wikibase.sitelink(entity), '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 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
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
local function hasDate(claim)
local claimqualifs = claims.qualifiers
if not claimqualifs then
return false
end
for _, qualif in pairs(claimqualifs) do
if claimsqualifs[qualif] and claimsqualifs[qualif][1].snaktype == 'value' then
return true
end
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 = 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
local function timeFromQualifs(claim, qualifs)
local claimqualifs = claim.qualifiers
if not claimqualifs then
return nil
end
for i, qualif in pairs(qualifs or timequalifiers) do
local vals = claimqualifs[qualif]
if vals and (vals[1].snaktype == 'value') then
return vals[1].datavalue.value.time
end
end
end
local function atDate(claim, mydate)
if mydate == "today" then
mydate = os.date("!%Y-%m-%dT%TZ")
end
local newclaims = {}
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
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 snack
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
snack = claim.qualifiers[j][1]
break
end
end
end
if not snack then
snack = claim.mainsnak or claim
end
if (snack.snaktype == 'value') and (snack.datatype == 'time') and (snack.datavalue.value.precision < minprecision) then
return false
end
return true
end
function wd.sortClaims(claims, sorttype)
if not claims then
return nil
end
if sorttype == 'chronological' then
return wd.chronoSort(claims)
elseif sorttype == 'inverted' then
return wd.chronoSort(claims, true)
elseif sorttype == 'order' then
return wd.chronoSort(claims)
elseif type(sorttype) == 'function' then
table.sort(claims, sorttype)
return claims
end
return claims
end
local alreadyFiltered = false
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('atdate', atDate, {'atdate'} )
filter('qualifier', wd.hasQualifier, {'qualifier', 'qualifiervalue'} )
filter('excludequalifier', excludeQualifier, {'excludequalifier', 'excludequalifiervalue'} )
filter('withsource', hasSource, {'withsource', 'sourceproperty'} )
filter('withdate', hasDate, {} )
filter('excludevalues', excludeValues, {'excludevalues'})
filter('withlink', hasLink, {'withlink', 'linklang'} )
filter('minprecision', minPrecision, {'minprecision'} )
claims = withRank(claims, args.rank or 'best')
end
if args.sorttype then
claims = wd.sortClaims(claims, args.sorttype)
end
if #claims == 0 then
return nil
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 == '-') then
return nil
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, lang, labelformat)
if (not entity) then
return nil -- ou option de gestion des erreurs ?
end
lang = lang or defaultlang
if type(labelformat) == 'function' then
return labelformat(entity)
end
entity = entity.id or ( type(entity) == "string" and entity)
if (type(entity) == 'string') and (lang == defaultlang) then -- le plus économique
local str = mw.wikibase.label(entity)
if str then -- mw.wikibase.label() ne fonctionne pas avec les redirect https://phabricator.wikimedia.org/T157868
return str
end
end
return mw.wikibase.getLabelByLang(entity, lang)
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 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
local link, label
label = wd.getLabel(entity, lang, labelformat)
-- 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 not label then
if (defaultlabel == '-') then
return nil
end
link = wd.siteLink(id, 'wikidata')
return '[[' .. link .. '|' .. id .. ']]' .. addCat(modules.i18n['to translate'])
-- si pas de libellé, on met un lien vers Wikidata pour qu'on comprenne à quoi ça fait référence
end
if (linktype == '-') or rendering_entity_on_its_page then
return label
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
return '[[' .. link .. '|' .. label .. ']]'
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>'
end
return label
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 modules.langcodes[tonumber(entityid:sub(2))]
end
local function showLang(statement) -- retourne le code langue entre paranthèse 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
return modules.langmodule.indicationMultilingue(langlist)
end
end
-- === DATE HANDLING ===
function wd.addStandardQualifs(str, statement)
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") then
str = wd.translate('approximate-place', str)
elseif (v == "Q18122778") or (v == "Q18912752") then
str = wd.translate('uncertain-information', str)
elseif (v == "Q5727902") then
if (statement.mainsnak.datatype == 'time') then
str = modules.formatDate.fuzzydate(str)
else
str = wd.translate('approximate-value', str)
end
end
end
end
return str
end
local function rangeObject(begin, ending, params)
--[[
objet comportant un timestamp pour le classement chronologique et deux dateobject (begin et ending)
]]--
local timestamp
if begin then
timestamp = begin.timestamp
else
timestamp = ending.timestamp
end
return {begin = begin, ending = ending, timestamp = timestamp, type = 'rangeobject'}
end
local function dateObject(orig, params)
--[[ transforme un snak en un nouvel objet utilisable par Module:Date complexe
{type = 'dateobject', timestamp = str, era = '+' ou '-', year = number, month = number, day = number, calendar = calendar}
]]--
if not params then
params = {}
end
local newobj = modules.formatDate.splitDate(orig.time, orig.calendarmodel)
newobj.precision = params.precision or orig.precision
newobj.type = 'dateobject'
return newobj
end
local function objectToText(obj, params)
if obj.type == 'dateobject' then
return modules.formatDate.simplestring(obj, params)
elseif obj.type == 'rangeobject' then
return modules.formatDate.daterange(obj.begin, obj.ending, params)
end
end
local function 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 dateObject(v.datavalue.value)
end
function wd.getDate(statement)
local period = getDateFromQualif(statement, 'P585') -- retourne un dateobject
if period then
return period
end
local begin, ending = getDateFromQualif(statement, 'P580'), getDateFromQualif(statement, 'P582')
if begin or ending then
return rangeObject(begin, ending) -- retourne un rangeobject fait de deux dateobject
end
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 = objectToText(datetable, params)
end
-- puis limite intérieur / supérieur
if not str then
local start, ending = getDateFromQualif(statement, 'P1319'), getDateFromQualif(statement, 'P1326')
str = modules.formatDate.between(start, ending, params)
end
-- 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)
end
return str
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, inverted )
for _, claim in ipairs( claims ) do
if not claim.dateSortKey then
local snack = claim.mainsnak or claim
local iso
if (snack.snaktype == 'value') and (snack.datatype == 'time') then
iso = snack.datavalue.value.time
else
iso = timeFromQualifs(claim, datequalifiers) or '0'
end
-- transformation en nombre (indication de la base car gsub retourne deux valeurs)
iso = tonumber( iso:gsub( '(%d)%D', '%1' ), 10 )
claim.dateSortKey = iso
end
end
table.sort(
claims,
function ( c1, c2 )
if inverted then
return c2.dateSortKey < c1.dateSortKey
end
return c1.dateSortKey < c2.dateSortKey
end
)
return claims
end
-- ===================
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('P854') then -- cas lorsque P854 (URL de la référence) est utilisé
local url, title, author, publisher, accessdate, publishdate, publishlang, quotation
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
--todo : handle multiple values for author, etc.
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}
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
return modules.weblink.makelink(value, params.text)
end
if datatype == 'math' then
return mw.getCurrentFrame():extensionTag( "math", value)
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
local url = mw.ustring.gsub(urlpattern, '$1', (value:gsub('%%', '%%%%'))):gsub(' ', '%%20')
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 = dateObject(value, {precision = params.precision})
return 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 = modules.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
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})
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
if not claims then
claims = wd.getClaims(args)
end
if not claims or claims == {} then
return nil
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
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.link = args.qualiflink or args.link
qualifargs.linktopic = args.qualiflinktopic or args.linktopic
qualifargs.conjtype = args.qualifconjtype
qualifargs.defaultlink = args.qualifdefaultlink or args.defaultlink
qualifargs.defaultlinkquery = args.qualifdefaultlinkquery or args.defaultlinkquery
local formattedqualifs = wd.getFormattedQualifiers(statement, qualifs, qualifargs)
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 )
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)
end
else
str = wd.formatSnak( statement.mainsnak, args )
if args.addstandardqualifs ~= '-' 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, alrady added by wd.formatStatement
if period then
str = str .. " <small>(" .. period .. ")</small>"
end
end
if args.showsource then
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 then
id = wd.getEntityIdForCurrentPage()
end
if not id then
return str
end
if type(property) == 'table' then
property = property[1]
end
id = 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')
: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, "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)
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à formtées
local props -- les prorpriétés réellement utilisées (dans certainse cas, ce ne sont pas toutes celles de ags.property
if not valuetable then -- cas le plus courant
valuetable, props = wd.stringTable(args)
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)
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
-- === 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, j in pairs(v) do
table.insert(a, 1, j)
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.descriptionl(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