Module:Wikidata

De Bibliothèque Lucas Lhardi
Aller à la navigation Aller à la recherche

--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 complexe', 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 end return 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 end return entity.id 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 entity_on_its_page = false local self_entity = mw.wikibase.getEntity() if self_entity ~= nil and qid == wd.entityId(self_entity) then entity_on_its_page = true end return entity_on_its_page 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éparatin 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} }

entity = wd.getEntity(entity) if not entity then return nil end

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 = entity:getSitelink(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 snack = claim.mainsnak or snack return snack.snaktype == '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 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 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.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 = wd.splitStr(args.property)

if not properties then return formatError( 'property-param-not-provided' ) end

--Get entity local entity = args.entity

	entity = wd.loadEntity(args.entity, args.cache)
   

if (not entity) or (not entity.claims) then return nil end local claims = {} for i, prop in pairs(properties) do prop = string.upper(prop) for j, claim in pairs(entity.claims[prop] or {}) do table.insert(claims, claim) end end

if (#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

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

if type(entity) == 'string' then entity = mw.wikibase.getEntity(entity) end

if entity and entity.labels and entity.labels[lang] then return entity.labels[lang].value, true 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 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 local str = -- l'intégralité du text à retourner


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 str .. '' .. 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 str .. 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 str .. '' .. 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 = '' .. '' .. icon .. '' return str .. label .. ' (' .. val .. ')' end return str .. 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 = {} 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 for j, source in pairs(ref.snaks.P248) do if source.snaktype == 'value' then local page, accessdate if hasValue('P304') then page = wd.formatSnak(ref.snaks.P304[1]) end if hasValue('P813') then accessdate = wd.formatSnak(ref.snaks.P813[1]) end s = modules.reference.citeitem(wd.getId(source), {['page'] = page, ['accessdate'] = accessdate}) table.insert(refs, s) end end

elseif hasValue('P854') and hasValue('P1476') then local url, title, accessdate, publishdate, publishlang url, title = wd.formatSnak(ref.snaks.P854[1], {text = "-"}), wd.formatSnak(ref.snaks.P1476[1]) if hasValue('P813') then accessdate = wd.formatSnak(ref.snaks.P813[1]) end -- publishdate avec P577 ? (ne semble pas vraiment correspondre) if hasValue('P407') then local id = wd.getId(ref.snaks.P407[1]) publishlang = getLangCode(id) end s = modules.cite.lienWeb{titre = title, url = url, langue = publishlang, ['en ligne le'] = publishdate, ['consulté le'] = accessdate} 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(refs, s) end end if #refs > 0 then return refs end end

function wd.sourceStr(sources) if not sources or (#sources == 0) then return nil end for i, j in pairs(sources) do sources[i] = mw.getCurrentFrame():extensionTag( "ref", j) end return table.concat(sources, ',') 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ètre 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.conjtype = args.qualifconjtype

local formattedqualifs = wd.getFormattedQualifiers(statement, qualifs, qualifargs) if formattedqualifs and str then str = str .. modules.linguistic.inparentheses(formattedqualifs, defaultlang) 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 .. " (" .. period ..")" end end

if args.showsource then local sources = wd.getReferences(statement) if sources then local source = wd.sourceStr(sources) str = str .. (source or "") end end

return str end

function wd.addLinkBack(str, id, property) if not id then id = wd.getEntity() end if not id then return str end if type(property) == 'table' then property = property[1] end if type(id) == 'table' then id = id.id end

local class = if property then class = 'wd_' .. string.lower(property) end local icon = '%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') :css('padding-left', '0.5em') :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 not qualifstr then return str end return str .. " " .. modules.linguistic.inparentheses(qualifstr) 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 not datestr then return str end return str .. "" .. modules.linguistic.inparentheses(datestr) .. "" 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 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 = wd.getReferences(claim) if refs then for i, j in pairs(refs) do if not dupeRef(sources, j) then table.insert(sources, j) end end end end return str .. (wd.sourceStr(sources) 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 local valueexpl = wd.translate("activate-query") --If a value is already set, use it if args.value and (args.value ~= ) and (args.value ~= valueexpl) then return args.value end -- if args.expl: return something only one if explicitly required in Wikitext if args.expl and (args.value ~= valueexpl) then return nil end args.entity = wd.getEntity(args.entity)

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 nil 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 "

"..mw.dumpObject(entity).."

"

end

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


return wd