Module:Sandbox/AdrianoRutzWikidataIB

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

local wd = {}

-- creation of a subobject to store comparison functions, used for sorting claims

-- to be able to build more complex sorts like topological sorts

wd.compare = {}

local modules = { }

local modulesNames = {

reference = 'Module:Wikidata/Références',

i18n = 'Module:Wikidata/I18n',

globes = 'Module:Wikidata/Globes',

langhierarchy = 'Module:Wikidata/Hiérarchie des langues',

langcodes = 'Module:Dictionnaire Wikidata/Codes langue', -- big, infrequently useda

invertedlangcodes = 'Module:Dictionnaire Wikidata/Codes langue/inversé',

linguistic = 'Module:Linguistique',

datemodule = 'Module:Date',

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 '' .. 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

function wd.hasQualifierNumber(claim, acceptedqualifs, acceptedvals, excludequalifiervalues)

local claimqualifs = claim.qualifiers

if (not claimqualifs) then

return false

end

acceptedqualifs = wd.splitStr(acceptedqualifs)

acceptedvals = wd.splitStr( acceptedvals)

local function ok(qualif) -- vérification pour un qualificatif individuel

if not claimqualifs[qualif] then

return false

end

if not (acceptedvals) then -- si aucune valeur spécifique n'est demandée, OK

return true

end

for i, wanted in pairs(acceptedvals) do

for j, actual in pairs(claimqualifs[qualif]) do

if mw.wikibase.renderSnak(actual) == wanted then

return true

end

end

end

end

for i, qualif in pairs(acceptedqualifs) do

if ok(qualif) then

return true

end

end

return false

end

local function hasSource(claim, targetsource, sourceproperty)

sourceproperty = sourceproperty or 'P248'

if targetsource == "-" then

return true

end

if (not claim.references) then return

false

end

local candidates = claim.references[1].snaks[sourceproperty] -- les snaks utilisant la propriété demandée

if (not candidates) then

return false

end

if (targetsource == "any") then -- si n'importe quelle valeur est acceptée tant qu'elle utilise en ref la propriété demandée

return true

end

targetsource = wd.splitStr(targetsource)

for _, source in pairs(candidates) do

local s = wd.getId(source)

for i, target in pairs(targetsource) do

if s == target then return true end

end

end

return false

end

local function excludeQualifier(claim, qualifier, qualifiervalues)

return not wd.hasQualifier(claim, qualifier, qualifiervalues)

end

function wd.hasDate(claim)

if not claim then

return false --error() ?

end

if wd.getDateFromQualif(claim, 'P585') or wd.getDateFromQualif(claim, 'P580') or wd.getDateFromQualif(claim, 'P582') then

return true

end

return false

end

local function hasLink(claim, site, lang)

if (claim.mainsnak.snaktype ~= 'value') then -- ne pas supprimer les valeurs spéciales, il y a une fonction dédiée pour ça

return true

end

local id = wd.getMainId(claim)

local link = wd.siteLink(id, site, lang)

if link then

return true

end

end

local function isInLanguage(claim, lang) -- ne fonctionne que pour les monolingualtext / étendre aux autres types en utilisant les qualifiers ?

if type(lang) == 'table' then -- si c'est une table de language séparées par des virgules, on les accepte toutes

for i, l in pairs(lang) do

local v = isInLanguage(claim, l)

if v then

return true

end

end

end

if type(lang) ~= ('string') then

return --?

end

if (lang == '-') then

return true

end

if (lang == 'locallang') then

lang = mw.getContentLanguage():getCode()

end

-- pour les monolingual text

local snak = claim.mainsnak or claim

if snak.snaktype == 'value' and snak.datavalue.type == 'monolingualtext' then

if snak.datavalue.value.language == lang then

return true

end

return false

end

-- pour les autres types de données : recherche dans les qualificatifs

if (lang == 'fr') then

lang = 'Q150'

elseif (lang == 'en') then

lang = 'Q1860'

else

lang = invertedlangcodes[lang]

end

if claim.qualifiers and claim.qualifiers.P407 then

if wd.hasQualifier(claim, {'P407'}, {lang}) then

return true

else

return false

end

end

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

end

local function firstVals(claims, numval) -- retourn les numval premières valeurs de la table claims

local numval = tonumber(numval) or 0 -- raise a error if numval is not a positive integer ?

if not claims then

return nil

end

while (#claims > numval) do

table.remove(claims)

end

return claims

end

local function lastVals(claims, numval2) -- retourn les valeurs de la table claims à partir de numval2

local numval2 = tonumber(numval2) or 0 -- raise a error if numval is not a positive integer ?

if not claims then

return nil

end

for i=1,numval2 do

table.remove(claims, 1)

end

return claims

end

-- retourne les valeurs de la table claims à partir de removedupesdate,

-- sans les dates en doublons avec conversion entre les calendrier julien et grégorien,

-- ou uniquement en catégorisant si le paramètre removedupesdate est égale à 'cat'

local function removeDupesDate(claims, removedupesdate)

if not claims or #claims < 2 then

return claims, ''

end

local cat = ''

local newClaims = {}

local newIsos = {}

local function findIndex(searchset, val) -- similaire à wd.isHere mais retourne l'index de la valeur trouvée

for i, j in pairs(searchset) do

if val == j then

return i

end

end

return -1

end

for _, claim in ipairs( claims ) do

local snack = claim.mainsnak or claim

if (snack.snaktype == 'value') and (snack.datatype == 'time') and snack.datavalue.value.precision >= 11 then -- s'il s'agit d'un time et que la précision est au moins l'année

local iso = snack.datavalue.value.time

_, _, iso = string.find(iso, "(+%d+-%d+-%d+T)")

local deleteIfDuplicate = false

if snack.datavalue.value.calendarmodel == 'http://www.wikidata.org/entity/Q1985727' then -- si la date est grégorienne

if modules.formatDate.before('+1582', iso) then -- si avant 1582 on calcule la date julienne

_, _, y, m, d = string.find(iso, "+(%d+)-(%d+)-(%d+)T")

y, m , d = modules.datemodule.gregorianToJulian(y, m , d)

if m < 10 then m = '0' .. m end

if d < 10 then d = '0' .. d end

iso = '+' .. y .. '-' .. m .. '-' .. d .. 'T'

deleteIfDuplicate = true

end

local index = findIndex(newIsos, iso)

if index >= 0 then -- si la date est déjà présente

cat = cat .. 'Catégorie:Article avec des dates identiques venant de wikidata dans le code de l\'infobox'

if removedupesdate == "cat" then -- ne faire que catégoriser

table.insert(newIsos, iso)

table.insert(newClaims, claim)

elseif not deleteIfDuplicate then -- supprimer l'autre date si la date courante n'a pas été convertie

newClaims[index] = claim

end -- sinon supprimer la date courante

else -- pas de doublon

table.insert(newIsos, iso)

table.insert(newClaims, claim)

end

elseif snack.datavalue.value.calendarmodel == 'http://www.wikidata.org/entity/Q1985786' then -- si date julienne

if not modules.formatDate.before('+1582', iso) then -- si après 1582 on calcule la date grégorienne

_, _, y, m, d = string.find(iso, "+(%d+)-(%d+)-(%d+)T")

y, m , d = modules.datemodule.julianToGregorian(y, m , d)

if m < 10 then m = '0' .. m end

if d < 10 then d = '0' .. d end

iso = '+' .. y .. '-' .. m .. '-' .. d .. 'T'

deleteIfDuplicate = true

end

local index = findIndex(newIsos, iso)

if index >= 0 then -- si date déjà présente

cat = cat .. 'Catégorie:Article avec des dates identiques venant de wikidata dans le code de l\'infobox'

if removedupesdate == "cat" then -- ne faire que catégoriser

table.insert(newIsos, iso)

table.insert(newClaims, claim)

elseif not deleteIfDuplicate then -- supprimer l'autre date si la date courante n'a pas été convertie

newClaims[index] = claim

end -- sinon supprimer la date courante

else -- pas de doublon

table.insert(newIsos, iso)

table.insert(newClaims, claim)

end

else -- autre calendrier

table.insert(newIsos, iso)

table.insert(newClaims, claim)

end

else -- précision insuffisante

table.insert(newIsos, iso)

table.insert(newClaims, claim)

end

end

return newClaims, cat

end

local function timeFromQualifs(claim, qualifs)

local claimqualifs = claim.qualifiers

if not claimqualifs then

return nil

end

for i, qualif in pairs(qualifs or timequalifiers) do

local vals = claimqualifs[qualif]

if vals and (vals[1].snaktype == 'value') then

return vals[1].datavalue.value.time, vals[1].datavalue.value.precision

end

end

end

local function atDate(claim, mydate)

if mydate == "today" then

mydate = os.date("!%Y-%m-%dT%TZ")

end

-- determines required precision depending on the atdate format

local d = mw.text.split(mydate, "-")

local myprecision

if d[3] then

myprecision = 11 -- day

elseif d[2] then

myprecision = 10 -- month

else

myprecision = 9 -- year

end

-- with point in time

local d, storedprecision = timeFromQualifs(claim, {'P585'})

if d then

return modules.formatDate.equal(mydate, d, math.min(myprecision, storedprecision))

end

-- with start or end date -- TODO: precision

local mindate = timeFromQualifs(claim, {'P580'})

local maxdate = timeFromQualifs(claim, {'P582'})

if modules.formatDate.before(mydate, mindate) and modules.formatDate.before(maxdate, mydate) then

return true

end

return false

end

local function check(claim, condition)

if type(condition) == 'function' then -- cas standard

return condition(claim)

end

return formatError('invalid type', 'function', type(condition))

end

local function minPrecision(claim, minprecision)

local 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 sorttype == 'ascending' then

return wd.quantitySort(claims)

elseif sorttype == 'descending' then

return wd.quantitySort(claims, true)

elseif type(sorttype) == 'function' then

table.sort(claims, sorttype)

return claims

elseif type(sorttype) == 'string' and sorttype:sub(1, 1) == 'P' then

return wd.numericPropertySort(claims, sorttype)

end

return claims

end

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

local function filter(condition, filterfunction, funargs)

if not args[condition] then

return

end

for i = #claims, 1, -1 do

if not( filterfunction(claims[i], args[funargs[1]], args[funargs[2]], args[funargs[3]]) ) then

table.remove(claims, i)

end

end

end

filter('isinlang', isInLanguage, {'isinlang'} )

filter('excludespecial', notSpecial, {} )

filter('condition', check, {'condition'} )

if claims[1] and claims[1].mainsnak then

filter('targetvalue', hasTargetValue, {'targetvalue'} )

filter('atdate', atDate, {'atdate'} )

filter('qualifier', wd.hasQualifier, {'qualifier', 'qualifiervalue'} )

filter('qualifiernumber', wd.hasQualifierNumber, {'qualifiernumber', 'qualifiernumbervalue'} )

filter('excludequalifier', excludeQualifier, {'excludequalifier', 'excludequalifiervalue'} )

filter('withsource', hasSource, {'withsource', 'sourceproperty'} )

filter('withdate', wd.hasDate, {} )

filter('excludevalues', excludeValues, {'excludevalues'})

filter('withlink', hasLink, {'withlink', 'linklang'} )

filter('minprecision', minPrecision, {'minprecision'} )

claims = withRank(claims, args.rank or 'best')

end

if #claims == 0 then

return nil

end

if args.sorttype then

claims = wd.sortClaims(claims, args.sorttype)

end

if args.numval2 then

claims = lastVals(claims, args.numval2)

end

if args.numval then

claims = firstVals(claims, args.numval)

end

return claims

end

function wd.loadEntity(entity, cache)

if type(entity) ~= 'table' then

if cache then

if not cache[entity] then

cache[entity] = mw.wikibase.getEntity(entity)

mw.log("cached")

end

return cache[entity]

else

if entity == '' or (entity == '-') then

entity = nil

end

return mw.wikibase.getEntity(entity)

end

else

return entity

end

end

function wd.getClaims( args ) -- returns a table of the claims matching some conditions given in args

if args.claims then -- if claims have already been set, return them

return args.claims

end

local properties = args.property

if type(properties) == 'string' then

properties = wd.splitStr(string.upper(args.property))

end

if not properties then

return formatError( 'property-param-not-provided' )

end

--Get entity

local entity = args.entity

if type(entity) == 'string' then

if entity == '' then

entity = nil

end

elseif type(entity) == 'table' then

entity = entity.id

end

if (not entity) then

entity = mw.wikibase.getEntityIdForCurrentPage()

end

if (not entity) or (entity == '-') 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, lang1, lang2)

if (not entity) then

return nil -- ou option de gestion des erreurs ?

end

entity = entity.id or ( type(entity) == "string" and entity)

if not(type(entity) == 'string') then return nil end

lang1 = lang1 or defaultlang

local str, lang --str : texte rendu, lang : langue de celui-ci

if lang1 == defaultlang then -- le plus économique

str, lang = mw.wikibase.getLabelWithLang(entity) -- le libellé peut être en français ou en anglais

else

str = mw.wikibase.getLabelByLang(entity, lang1)

if str then lang = lang1 end

end

if str and (lang == lang1) then --pas de catégorie "à traduire" si on a obtenu un texte dans la langue désirée (normalement fr)

return str

end

if lang2 then -- langue secondaire, avec catégorie "à traduire"

str2 = mw.wikibase.getLabelByLang(entity, lang2)

if str2 then

lang = lang2

str = str2

end

end

if not str then --si ni lang1, ni lang2 ni l'anglais ne sont présents, parcours de la hiérarchie des langues

for _, trylang in ipairs(modules.langhierarchy.codes) do

str = mw.wikibase.getLabelByLang(entity, trylang)

if str then

lang = trylang

break

end

end

end

if str then

local translationCat = modules.i18n['to translate']

translationCat = translationCat .. (modules.langhierarchy.cattext[lang] or '')

translationCat = addCat(translationCat)

return str, translationCat

end

end

function wd.formatEntity( entity, params )

if (not entity) then

return nil --formatError('entity-not-found')

end

local id = entity

if type(id) == 'table' then

id = id.id

end

params = params or {}

local lang = params.lang or defaultlang

local speciallabels = params.speciallabels

local displayformat = params.displayformat

local labelformat = params.labelformat

local labelformat2 = params.labelformat2

local defaultlabel = params.defaultlabel or id

local linktype = params.link

local defaultlink = params.defaultlink

local defaultlinkquery = params.defaultlinkquery

if speciallabels and speciallabels[id] then --speciallabels override the standard label + link combination

return speciallabels[id]

end

if params.displayformat == 'raw' then

return id

end

local link, label, translationCat

if type(labelformat) == 'function' then -- sert à des cas particuliers

label, translationCat = labelformat(entity)

end

if not label then label, translationCat = wd.getLabel(entity, lang, params.wikidatalang) end

translationCat = translationCat or "" -- sera toujours ajoutée au résultat mais sera vide si la catégorie de maintenance n'est pas nécessaire

if type(labelformat2) == 'function' then -- sert à des cas particuliers

label = labelformat2(label)

end

-- détermination du fait qu'on soit ou non en train de rendre l'élément sur la page de son article

local rendering_entity_on_its_page = wd.isPageOfQId(id)

if not label then

if (defaultlabel == '-') then

return nil

end

link = wd.siteLink(id, 'wikidata')

return '' .. id .. '' .. translationCat

-- 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 .. translationCat

end

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

-- defaultlinkquery will try to link to another page on this Wiki

if (not link) and defaultlinkquery then

if type(defaultlinkquery) == 'string' then

defaultlinkquery = {property = defaultlinkquery}

end

defaultlinkquery.excludespecial = true

defaultlinkquery.entity = entity

local claims = wd.getClaims(defaultlinkquery)

if claims then

for i, j in pairs(claims) do

local id = wd.getMainId(j)

link = wd.siteLink(id, linktype, lang)

if link then

break

end

end

end

end

if link then

if mw.ustring.sub(link, 1, 9) == "Category:" or mw.ustring.sub(link, 1, 10) == "Catégorie:" then

link = ":" .. link --lier vers une catégorie au lieu de catégoriser

end

return '' .. label .. '' .. translationCat

end

-- if not link, you can use defaultlink: a sidelink to another Wikimedia project

if (not defaultlink) then

defaultlink = {'enwiki'}

end

if defaultlink and (defaultlink ~= '-') then

local linktype

local sidelink, site, langcode

if type(defaultlink) == 'string' then

defaultlink = {defaultlink}

end

for i, j in ipairs(defaultlink) do

sidelink, site, langcode = wd.siteLink(entity, j, lang)

if sidelink then

break

end

end

if not sidelink then

sidelink, site = wd.siteLink(entity, 'wikidata')

end

local icon, class, title = site, nil, nil -- le texte affiché du lien

if site == 'wiki' then

icon, class, title = langcode, "indicateur-langue", wd.translate('see-another-language', mw.language.fetchLanguageName(langcode, defaultlang))

elseif site == 'wikidata' then

icon, class, title = 'd', "indicateur-langue", wd.translate('see-wikidata')

else

title = wd.translate('see-another-project', site)

end

local val = '' .. '' .. icon .. ''

return label .. ' (' .. val .. ')' .. translationCat

end

return label .. translationCat

end

function wd.addTrackingCat(prop, cat) -- doit parfois être appelé par d'autres modules

if type(prop) == 'table' then

prop = prop[1] -- devrait logiquement toutes les ajouter

end

if not prop and not cat then

return formatError("property-param-not-provided")

end

if not cat then

cat = wd.translate('trackingcat', prop or 'P??')

end

return addCat(cat )

end

local function unknownValue(snak, label)

local str = label

if type(str) == "function" then

str = str(snak)

end

if (not str) then

if snak.datatype == 'time' then

str = wd.translate('sometime')

else

str = wd.translate('somevalue')

end

end

if type(str) ~= "string" then

return formatError(snak.datatype)

end

return str

end

local function noValue(displayformat)

if not displayformat then

return wd.translate('novalue')

end

if type(displayformat) == 'string' then

return displayformat

end

return formatError()

end

local function getLangCode(entityid)

return modules.langcodes[tonumber(entityid:sub(2))]

end

local function showLang(statement, maxLang) -- 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

langlist.maxLang = maxLang

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

function wd.getDateFromQualif(statement, qualif)

if (not statement) or (not statement.qualifiers) or not (statement.qualifiers[qualif]) then

return nil

end

local v = statement.qualifiers[qualif][1]

if v.snaktype ~= 'value' then -- que faire dans ce cas ?

return nil

end

return dateObject(v.datavalue.value)

end

function wd.getDate(statement)

local period = wd.getDateFromQualif(statement, 'P585') -- retourne un dateobject

if period then

return period

end

local begin, ending = wd.getDateFromQualif(statement, 'P580'), wd.getDateFromQualif(statement, 'P582')

if begin or ending then

return rangeObject(begin, ending) -- retourne un rangeobject fait de deux dateobject

end

return nil

end

function wd.getFormattedDate(statement, params)

if not statement then

return nil

end

local str

--cherche la date avec les qualifs P580/P582

local datetable = wd.getDate(statement)

if datetable then

str = objectToText(datetable, params)

end

-- puis limite intérieur / supérieur

if not str then

local start, ending = wd.getDateFromQualif(statement, 'P1319'), wd.getDateFromQualif(statement, 'P1326')

str = modules.formatDate.between(start, ending, params)

end

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

wd.compare.by_quantity = function(c1, c2)

local v1 = wd.getDataValue(c1.mainsnak)

local v2 = wd.getDataValue(c2.mainsnak)

if not (v1 and v2) then

return true

end

return v1 < v2

end

--[[ tri chronologique générique :

retourne une fonction de tri de liste de déclaration

en fonction d’une fonction qui calcule la clé de tri

et d’une fonction qui compare les clés de tri

paramètres nommés: (appel type wikidata.compare.chrono_key_sort{sortKey="nom clé"})

sortKey (optionnel) : chaine, le nom de la clé utilisée pour un tri

(pour éviter de rentrer en collision avec "dateSortKey"

utilisé par chronoSort au besoin)

snak_key_get_function : fonction qui calcule la valeur de la clé à partir d’un snak ou d’une déclaration,

(obligatoire) le résultat n’est calculé qu’une fois et est stocké en cache dans claim[sortKey]

key_compare_function : fonction de comparaison des clés calculées par snak_key_get_function

(optionnel)

--]]

function wd.chrono_key_sort(arg)

local snak_key_get_function = arg.snak_key_get_function

local sortKey = arg.sortKey or "dateSortKey"

local key_compare_function = arg.key_compare_function or

function(c1, c2) return c1 < c2 end

return function(claims)

for _, claim in ipairs( claims ) do

if not claim[sortKey] then

local key = snak_key_get_function(claim)

if key then

claim[sortKey] = wd.compare.get_claim_date(key)

else

claim[sortKey] = 0

end

end

end

table.sort(

claims,

function(c1, c2)

return key_compare_function(c1[sortKey], c2[sortKey])

end

)

return claims

end

end

function wd.quantitySort(claims, inverted)

local function sort(c1, c2)

local v1 = wd.getDataValue(c1.mainsnak)

local v2 = wd.getDataValue(c2.mainsnak)

if not (v1 and v2) then

return true

end

if inverted then

return v2 < v1

end

return v1 < v2

end

table.sort(claims, sort )

return claims

end

function wd.compare.get_claim_date(claim)

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)

return tonumber( iso:gsub( '(%d)%D', '%1' ), 10 )

end

function wd.compare.chronoCompare(c1, c2)

return wd.compare.get_claim_date(c1) < wd.compare.get_claim_date(c2)

end

-- fonction pour renverser l’ordre d’une autre fonction

function wd.compare.rev(comp_criteria)

return function(c1, c2)

-- attention les tris en lua attendent des fonctions de comparaison strictement inférieur, on doit

-- vérifier la non égalité quand on inverse l’ordre d’un critère, d’ou "and comp_criteria(c2,c1)"

return not(comp_criteria(c1,c2)) and comp_criteria(c2,c1)

end

end

-- Fonction qui trie des Claims de type time selon l'ordre chronologique

-- Une clé de tri nomée « dateSortKey » est ajouté à chaque claim.

-- Si des clés de tri de ce nom existent déjà, elles sont utilisées sans modification.

function wd.chronoSort( claims, inverted )

for _, claim in ipairs( claims ) do

if not claim.dateSortKey then

claim.dateSortKey = wd.compare.get_claim_date(claim)

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

local function get_numeric_claim_value(claim, propertySort)

local val

local claimqualifs = claim.qualifiers

if claimqualifs then

local vals = claimqualifs[propertySort]

if vals and vals[1].snaktype == 'value' then

val = vals[1].datavalue.value

end

end

return tonumber(val or 0)

end

function wd.compare.numeric(propertySort)

return function(c1, c2)

return get_numeric_claim_value(c1, propertySort) < get_numeric_claim_value(c2, propertySort)

end

end

-- Fonction qui trie des Claims de type value selon l'ordre de la propriété fournit

-- Une clé de tri nomée « dateSortKey » est ajouté à chaque claim.

-- Si des clés de tri de ce nom existent déjà, elles sont utilisées sans modification.

function wd.numericPropertySort( claims, propertySort )

for _, claim in ipairs( claims ) do

if not claim.dateSortKey then

local val = get_numeric_claim_value(claim, propertySort)

claim.dateSortKey = tonumber(val or 0)

end

end

table.sort(

claims,

function ( c1, c2 )

return c1.dateSortKey < c2.dateSortKey

end

)

return claims

end

--[[

test possible en console pour la fonction précédente :

= p.formatStatements{entity = "Q375946", property = 'P50', sorttype = 'P1545', linkback = "true"}

--]]

-- ===================

function wd.getReferences(statement)

local refdata = statement.references

if not refdata then

return nil

end

local refs = {}

local hashes = {}

for i, ref in pairs(refdata) do

local s

local function hasValue(prop) -- checks that the prop is here with valid value

if ref.snaks[prop] and ref.snaks[prop][1].snaktype == 'value' then

return true

end

return false

end

if ref.snaks.P248 then -- cas lorsque P248 (affirmé dans) est utilisé

for j, source in pairs(ref.snaks.P248) do

if source.snaktype == 'value' then

local page, accessdate, quotation

if hasValue('P304') then -- page

page = wd.formatSnak(ref.snaks.P304[1])

end

if hasValue('P813') then -- date de consultation

accessdate = wd.formatSnak(ref.snaks.P813[1])

end

if hasValue('P1683') then -- citation

quotation = wd.formatSnak(ref.snaks.P1683[1])

end

local sourceId = wd.getId(source)

s = modules.reference.citeitem(sourceId, {['page'] = page, ['accessdate'] = accessdate, ['citation'] = quotation})

table.insert(refs, s)

table.insert(hashes, ref.hash .. sourceId)

end

end

elseif hasValue('P8091') or hasValue('P854') then -- cas lorsque P8091 (Archival Resource Key) ou P854 (URL de la référence)est utilisé

local arkKey, url, title, author, publisher, accessdate, publishdate, publishlang, quotation, description

if hasValue('P8091') then

arkKey = wd.formatSnak(ref.snaks.P8091[1], {text = "-"})

url = 'https://n2t.net/' .. arkKey

if hasValue('P1476') then

title = wd.formatSnak(ref.snaks.P1476[1])

else

title = arkKey

end

elseif hasValue('P854') then

url = wd.formatSnak(ref.snaks.P854[1], {text = "-"})

if hasValue('P1476') then

title = wd.formatSnak(ref.snaks.P1476[1])

else

title = mw.ustring.gsub(url, '^[Hh][Tt][Tt][Pp]([Ss]?):(/?)([^/])', 'http%1://%3')

end

end

--todo : handle multiple values for author, etc.

if hasValue('P1810') then -- sous le nom

description = 'sous le nom ' .. wd.formatSnak(ref.snaks.P1810[1])

end

if hasValue('P813') then -- date de consultation

accessdate = wd.formatSnak(ref.snaks.P813[1])

end

if hasValue('P50') then -- author (item type)

author = wd.formatSnak(ref.snaks.P50[1])

elseif hasValue('P2093') then -- author (string type)

author = wd.formatSnak(ref.snaks.P2093[1])

end

if hasValue('P123') then -- éditeur

publisher = wd.formatSnak(ref.snaks.P123[1])

end

if hasValue('P1683') then -- citation

quotation = wd.formatSnak(ref.snaks.P1683[1])

end

if hasValue('P577') then -- date de publication

publishdate = wd.formatSnak(ref.snaks.P577[1])

end

if hasValue('P407') then -- langue de l'œuvre

local id = wd.getId(ref.snaks.P407[1])

publishlang = getLangCode(id)

end

s = modules.cite.lienWeb{titre = title, url = url, auteur = author, editeur = publisher, langue = publishlang, ['en ligne le'] = publishdate, ['consulté le'] = accessdate, ['citation'] = quotation, ['description'] = description}

table.insert(hashes, ref.hash)

table.insert(refs, s)

elseif ref.snaks.P854 and ref.snaks.P854[1].snaktype == 'value' then

s = wd.formatSnak(ref.snaks.P854[1], {text = "-"})

table.insert(hashes, ref.snaks.P854[1].hash)

table.insert(refs, s)

end

end

if #refs > 0 then

if #hashes == #refs then

return refs, hashes

end

return refs

end

end

function wd.sourceStr(sources, hashes)

if not sources or (#sources == 0) then

return nil

end

local useHashes = hashes and #hashes == #sources

for i, j in ipairs(sources) do

local refArgs = {name = 'ref', content = j}

if useHashes and hashes[i] ~= '-' then

refArgs.args = {name = 'wikidata-' .. hashes[i]}

end

sources[i] = mw.getCurrentFrame():extensionTag(refArgs)

end

return table.concat(sources, ',')

end

function wd.getDataValue(snak, params)

if not params then

params = {}

end

local speciallabels = params.speciallabels -- parfois on a besoin de faire une liste d'éléments pour lequel le libellé doit être changé, pas très pratique d'utiliser une fonction pour ça

if snak.snaktype ~= 'value' then

return nil

end

local datatype = snak.datatype

local value = snak.datavalue.value

local displayformat = params.displayformat

if type(displayformat) == 'function' then

return displayformat(snak, params)

end

if datatype == 'wikibase-item' then

return wd.formatEntity(wd.getId(snak), params)

end

if datatype == 'url' then

if params.displayformat == 'raw' then

return value

else

return modules.weblink.makelink(value, params.text)

end

end

if datatype == 'math' then

return mw.getCurrentFrame():extensionTag( "math", value)

end

if datatype == 'tabular-data' then

return mw.ustring.sub(value, 6, 100) -- returns the name of the file, without the "Data:" prefix

end

if (datatype == 'string') or (datatype == 'external-id') or (datatype == 'commonsMedia') then -- toutes les données de type string sauf "math"

if params.urlpattern then

local urlpattern = params.urlpattern

if type(urlpattern) == 'function' then

urlpattern = urlpattern(value)

end

-- encodage de l'identifiant qui se retrouve dans le path de l'URL, à l'exception des slashes parfois rencontrés, qui sont des séparateurs à ne pas encoder

local encodedValue = mw.uri.encode(value, 'PATH'):gsub('%%2F', '/')

-- les parenthèses autour du encodedValue:gsub() sont nécessaires, sinon sa 2e valeur de retour est aussi passée en argument au mw.ustring.gsub() parent

local url = mw.ustring.gsub(urlpattern, '$1', (encodedValue:gsub('%%', '%%%%')))

value = '[' .. url .. ' ' .. (params.text or value) .. ']'

end

return value

end

if datatype == 'time' then -- format example: +00000001809-02-12T00:00:00Z

if displayformat == 'raw' then

return value.time

else

local dateobject = 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

if not unit then

unit = 'dimensionless'

end

local raw

if displayformat == "raw" then

raw = true

end

return modules.formatNum.displayvalue(amount, unit,

{targetunit = params.targetunit, raw = raw, rounding = params.rounding, showunit = params.showunit or 'short', showlink = params.showlink}

)

end

if datatype == 'monolingualtext' then

if value.language == defaultlang then

return value.text

else

return modules.langmodule.langue({value.language, value.text, nocat=true})

end

end

return formatError('unknown-datavalue-type' )

end

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

local claims = args.claims

local cat = ''

if not claims then

claims = wd.getClaims(args)

end

if not claims or claims == {} then

return {}, {}, cat

end

if args.removedupesdate and (args.removedupesdate ~= '-') then

claims, cat = removeDupesDate(claims, args.removedupesdate)

end

local props = {} -- liste des propriétés associété à chaque string pour catégorisation et linkback

for i, j in pairs(claims) do

claims[i] = wd.formatStatement(j, args)

table.insert(props, j.mainsnak.property)

end

if args.removedupes and (args.removedupes ~= '-') then

claims = wd.addNewValues({}, claims) -- devrait aussi supprimer de props celles qui ne sont pas utilisées

end

return claims, props, cat

end

function wd.getQualifiers(statement, qualifs, params)

if not statement.qualifiers then

return nil

end

local vals = {}

if type(qualifs) == 'string' then

qualifs = wd.splitStr(qualifs)

end

for i, j in pairs(qualifs) do

if statement.qualifiers[j] then

for k, l in pairs(statement.qualifiers[j]) do

table.insert(vals, l)

end

end

end

if #vals == 0 then

return nil

end

return vals

end

function wd.getFormattedQualifiers(statement, qualifs, params)

if not params then params = {} end

local qualiftable = wd.getQualifiers(statement, qualifs)

if not qualiftable then

return nil

end

qualiftable = wd.filterClaims(qualiftable, params) or {}

for i, j in pairs(qualiftable) do

qualiftable[i] = wd.formatSnak(j, params)

end

return modules.linguistic.conj(qualiftable, params.conjtype)

end

function wd.showQualifiers(str, statement, args)

local qualifs = args.showqualifiers

if not qualifs then

return str -- or error ?

end

if type(qualifs) == 'string' then

qualifs = wd.splitStr(qualifs)

end

local qualifargs = args.qualifargs or {}

-- formatage des qualificatifs = args commençant par "qualif", ou à défaut, les mêmes que pour la valeur principale

qualifargs.displayformat = args.qualifdisplayformat or args.displayformat

qualifargs.labelformat = args.qualiflabelformat or args.labelformat

qualifargs.labelformat2 = args.qualiflabelformat2 or args.labelformat2

qualifargs.link = args.qualiflink or args.link

qualifargs.linktopic = args.qualiflinktopic or args.linktopic

qualifargs.conjtype = args.qualifconjtype

qualifargs.precision = args.qualifprecision

qualifargs.targetunit = args.qualiftargetunit

qualifargs.defaultlink = args.qualifdefaultlink or args.defaultlink

qualifargs.defaultlinkquery = args.qualifdefaultlinkquery or args.defaultlinkquery

local formattedqualifs

if args.qualifformat and type (args.qualifformat) == 'function' then

formattedqualifs = args.qualifformat(statement, qualifs, qualifargs)

else

formattedqualifs = wd.getFormattedQualifiers(statement, qualifs, qualifargs)

end

if formattedqualifs and formattedqualifs ~= "" then

str = str .. " (" .. formattedqualifs .. ")"

end

return str

end

function wd.formatSnak( snak, params )

if not params then params = {} end -- pour faciliter l'appel depuis d'autres modules

if snak.snaktype == 'somevalue' then

return unknownValue(snak, params.unknownlabel)

elseif snak.snaktype == 'novalue' then

return noValue(params.novaluelabel)

elseif snak.snaktype == 'value' then

return wd.getDataValue( snak, params)

else

return formatError( 'unknown-snak-type' )

end

end

function wd.formatStatement( statement, args ) -- FONCTION A REORGANISER (pas très lisible)

if not args then

args = {}

end

if not statement.type or statement.type ~= 'statement' then

return formatError( 'unknown-claim-type' )

end

local prop = statement.mainsnak.property

local str

-- special displayformat f

if args.statementformat and (type(args.statementformat) == 'function') then

str = args.statementformat(statement, args)

elseif (statement.mainsnak.datatype == 'time') and (statement.mainsnak.dateformat ~= '-') then

if args.displayformat == 'raw' and statement.mainsnak.snaktype == 'value' then

str = statement.mainsnak.datavalue.value.time

else

str = wd.getFormattedDate(statement, args)

end

elseif args.showonlyqualifier and (args.showonlyqualifier ~= '') then

str = wd.getFormattedQualifiers(statement, args.showonlyqualifier, args)

if not str then

return nil

end

if args.addstandardqualifs ~= '-' then

str = wd.addStandardQualifs(str, statement)

end

else

str = wd.formatSnak( statement.mainsnak, args )

if (args.addstandardqualifs ~= '-') and (args.displayformat ~= 'raw') then

str = wd.addStandardQualifs(str, statement)

end

end

-- ajouts divers

if args.showlang == true then

local indicateur = showLang(statement, args.maxLang)

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 and 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 or 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'

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, wd.translate("qualif-separator"))

if qualifstr and qualifstr ~= "" then

str = str .. " (" .. qualifstr .. ")"

end

return str

end

},

{param = "showdate", fun = function(str, claims)

-- toutes les dates sont regroupées à l'intérieur des mêmes parenthèses ex "médaille d'or (1922, 1924)"

local dates = {}

for i, statement in pairs(claims) do

local s = wd.getFormattedDate(statement, args, true)

if statement then table.insert(dates, s) end

end

local datestr = modules.linguistic.conj(dates)

if datestr and datestr ~= "" then

str = str .. " (" .. datestr .. ")"

end

return str

end

},

{param = "showsource", fun = function(str, claims)

-- les sources sont toutes affichées au même endroit, à la fin

-- si deux affirmations ont la même source, on ne l'affiche qu'une fois

local sources = {}

local hashes = {}

local function dupeRef(old, new)

for i, j in pairs(old) do

if j == new then

return true

end

end

end

for i, claim in pairs(claims) do

local refs, refHashes = wd.getReferences(claim)

if refs then

for i, j in pairs(refs) do

if not dupeRef(sources, j) then

table.insert(sources, j)

local hash = (refHashes and refHashes[i]) or '-'

table.insert(hashes, hash)

end

end

end

end

return str .. (wd.sourceStr(sources, hashes) or "")

end

}

}

for i, group in pairs(groupedClaims) do -- bricolage pour utiliser les arguments de formatStatements

local str = wd.formatEntity(group.id, args)

if not str then

str = '???' -- pour éviter erreur Lua si formatEntity a retourné nil

end

for i, fun in pairs(funs) do

if args[fun.param] then

str = fun.fun(str, group.claims, args)

end

end

table.insert(stringTable, str)

end

args.valuetable = stringTable

return wd.formatStatements(args)

end

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

if args.value == '-' then

return nil

end

-- If a value is already set: use it, except if it's the special value {{WD}} (use wikidata)

if args.value and args.value ~= '' then

local valueexpl = wd.translate("activate-query")

if args.value ~= valueexpl then

return args.value

end

-- There is no value set, and args.expl disables wikidata on empty values

elseif args.expl then

return nil

end

if args.grouped and args.grouped ~= '' then

args.grouped = false

return formatStatementsGrouped(args)

end

local valuetable = args.valuetable -- dans le cas où les valeurs sont déjà formtées

local props -- les prorpriétés réellement utilisées (dans certainse cas, ce ne sont pas toutes celles de ags.property

local cat = ''

if not valuetable then -- cas le plus courant

valuetable, props, cat = 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) .. cat

end

if args.linkback and (args.linkback ~= '-') then

str = wd.addLinkBack(str, args.entity, props)

end

if args.returnnumberofvalues then

return str, #valuetable

end

return str

end

function wd.formatAndCat(args)

if not args then

return nil

end

args.linkback = args.linkback or true

args.addcat = true

if args.value then -- do not ignore linkback and addcat, as formatStatements do

if args.value == '-' then

return nil

end

local val = args.value .. wd.addTrackingCat(args.property)

val = wd.addLinkBack(val, args.entity, args.property)

return val

end

return wd.formatStatements( args )

end

function wd.getTheDate(args)

local claims = wd.getClaims(args)

if not claims then

return nil

end

local formattedvalues = {}

for i, j in pairs(claims) do

local v = wd.getFormattedDate(j, args)

if v then

table.insert(formattedvalues, v )

end

end

local val = modules.linguistic.conj(formattedvalues)

if not val then

return nil

end

if args.addcat == true then

val = val .. wd.addTrackingCat(args.property)

end

val = wd.addLinkBack(val, args.entity, args.property)

return val

end

function wd.keyDate (event, item, params)

params = params or {}

params.entity = item

if type(event) == 'table' then

for i, j in pairs(event) do

params.targetvalue = nil -- réinitialisation barbare des paramètres modifiés

local s = wd.keyDate(j, item, params)

if s then

return s

end

end

elseif type(event) ~= 'string' then

return formatError('invalid-datatype', type(event), 'string')

elseif string.sub(event, 1, 1) == 'Q' then -- on demande un élément utilisé dans P:P793 (événement clé)

params.property = 'P793'

params.targetvalue = event

params.addcat = params.addcat or true

return wd.getTheDate(params)

elseif string.sub(event, 1, 1) == 'P' then -- on demande une propriété

params.property = event

return wd.formatAndCat(params)

else

return formatError('invalid-entity-id', event)

end

end

function wd.mainDate(entity)

-- essaye P580/P582

local args = {entity = entity, addcat = true}

args.property = 'P580'

local startpoint = wd.formatStatements(args)

args.property = 'P582'

local endpoint = wd.formatStatements(args)

local str

if (startpoint or endpoint) then

str = modules.formatDate.daterange(startpoint, endpoint, params)

str = wd.addLinkBack(str, entity, 'P582')

return str

end

-- défaut : P585

args.property = {'P585', 'P571'}

args.linkback = true

return wd.formatStatements(args)

end

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

function wd.getIds(item, query)

query.excludespecial = true

query.displayformat = 'raw'

query.entity = item

query.addstandardqualifs = '-'

return wd.stringTable(query)

end

-- recursively adds a list of qid to an existing list, based on the results of a query

function wd.addVals(list, query, maxdepth, maxnodes, stopval)

maxdepth = tonumber(maxdepth) or 10

maxnodes = tonumber(maxnodes) or 100

if (maxdepth < 0) then

return list

end

if stopval and wd.isHere(list, stopval) then

return list

end

local origsize = #list

for i = 1, origsize do

-- tried a "checkpos" param instead of starting to 1 each time, but no impact on performance

local candidates = wd.getIds(list[i], query)

list = wd.addNewValues(list, candidates, maxnodes, stopval)

if list[#list] == stopval then

return list

end

if #list >= maxnodes then

return list

end

end

if (#list == origsize) then

return list

end

return wd.addVals(list, query, maxdepth - 1, maxnodes, stopval, origsize + 1)

end

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

function wd.transitiveVals(item, query, maxdepth, maxnodes, stopval)

maxdepth = tonumber(maxdepth) or 5

if type(query) == "string" then

query = {property = query}

end

-- récupération des valeurs

local vals = wd.getIds(item, query)

if not vals then

return nil

end

local v = wd.addVals(vals, query, maxdepth - 1, maxnodes, stopval)

if not v then

return nil

end

-- réarrangement des valeurs

if query.valorder == "inverted" then

local a = {}

for i = #v, 1, -1 do

a[#a+1] = v[i]

end

v = a

end

return v

end

-- returns true if an item is the value of a query, transitively

function wd.inTransitiveVals(searchedval, sourceval, query, maxdepth, maxnodes )

local vals = wd.transitiveVals(sourceval, query, maxdepth, maxnodes, searchedval )

if (not vals) then

return false

end

for _, val in ipairs(vals) do

if (val == searchedval) then

return true

end

end

return false

end

-- returns true if an item is a superclass of another, based on P279

function wd.isSubclass(class, item, maxdepth)

local query = {property = 'P279'}

if class == item then -- item is a subclass of itself iff it is a class

if wd.getIds(item, query) then

return true

end

return false

end

return wd.inTransitiveVals(class, item, query, maxdepth )

end

-- returns true if one of the best ranked P31 values of an item is the target or a subclass of the target

-- rank = 'valid' would seem to make sense, but it would need to check for date qualifiers as some P31 values have begin or end date

function wd.isInstance(targetclass, item, maxdepth)

maxdepth = maxdepth or 10

local directclasses = wd.transitiveVals(item, {property = 'P31'}, 1)

if not directclasses then

return false

end

for i, class in pairs(directclasses) do

if wd.isSubclass(targetclass, class, maxdepth - 1) then

return true

end

end

return false

end

-- return the first value in a transitive query that belongs to a particular class. For instance find a value of P131 that is a province of Canada

function wd.findVal(sourceitem, targetclass, query, recursion, instancedepth)

if type(query) == "string" then

query = {property = query}

end

local candidates = wd.getIds(sourceitem, query)

if candidates then

for i, j in pairs(candidates) do

if wd.isInstance(targetclass, j, instancedepth) then

return j

end

end

if not recursion then

recursion = 3

else

recursion = recursion - 1

end

if recursion < 0 then

return nil

end

for i, candidate in pairs(candidates) do

return wd.findVal(candidate, targetclass, query, recursion, instancedepth)

end

end

end

-- === VARIA ===

function wd.getDescription(entity, lang)

lang = lang or defaultlang

local description

if lang == defaultlang then

return mw.wikibase.description(qid)

end

if not entity.descriptions then

return wd.translate('no description')

end

local descriptions = entity.descriptions

if not descriptions then

return nil

end

if descriptions[lang] then

return descriptions[delang].value

end

return entity.id

end

function wd.Dump(entity)

entity = wd.getEntity(entity)

if not entity then

return formatError("entity-param-not-provided")

end

return "

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