Module:Wikidata

Un livre de Wikilivres.
Sauter à la navigation Sauter à la recherche
Lua-logo-nolabel.svg Documentation du module

Ce module devrait-être utilisé à partir de Module:Interface Wikidata

Il contient les sous-modules suivant :

Fonctions utilisables depuis un autre module[modifier le wikicode]

Dans ce qui suit, args designe une table de paramètres nommés dont les plus importants sont entity, l'identifiant de l'entité Wikidata à utiliser, et property l'identifiant la propriété, ou une table contenant des identifiants de propriété, à utiliser. Pour une liste exhaustive, voir w:Projet:Wikidata/Atelier/Manuel

getClaims(args)[modifier le wikicode]

Retourne une liste de déclarations Wikidata répondants aux critères définis dans la table args. Ces affirmations ne sont pas transformées, et conservent donc la structure définie sur wikidata:Help:Wikidata datamodel.

stringTable(args)[modifier le wikicode]

Retourne les mêmes affirmations que getClaims, sauf éventuellement celles supprimées par le paramètre removedeups. Chacune est rendue sous forme d'une chaîne en Wikitexte directement utilisable. Les paramètres de mise en forme sous également contenus dans la table args.

formatStatements(args)[modifier le wikicode]

Retourne les mêmes valeurs que stringTable, mais concaténées sous forme de chaîne unique. La méthode de concaténation est définie par le paramètre conjtype qui utilise Module:Linguistique. Si les paramètres, linkback et addcat sont activés, un rétrolien et une catégorie de maintenance peuvent également être ajoutés.

formatAndCat(args)[modifier le wikicode]

Presque équivalent a formatStatements avec linkback, et addcat activés. La seule différence est que la catégorie et le rétrolien sont appliqués même lorsque le paramètre value est non-vide.

formatStatement(statement, args)[modifier le wikicode]

Transforme une déclaration individuelle en chaîne wikitexte, selon les mêmes paramètres que stringTable.

showQualifier(statement, args)[modifier le wikicode]

Même logique que formatStatement, mais affiche un qualificatif au lieu d'une déclaration entière

formatSnak(snak, args)[modifier le wikicode]

Même logique que formatStatement, mais concerne seulement un snak.

formatEntity(id, args)[modifier le wikicode]

Transforme un identifiant d'entité Wikidata en une chaîne de Wikitexte, en utilisant les arguments s'appliquant aux données de type entity (link et label notamment).
--script that retrieves basic data stored in Wikidata, for the datamodel, see https://www.mediawiki.org/wiki/Extension:Wikibase_Client/Lua

local p = {}
local linguistic = require('Module:Linguistique')
local dates = require('Module:Wikidata/Dates')
local langmodule = require('Module:Langue')
local convertlangcode = require('Module:Dictionnaire Wikidata/Codes langue')
local formatText = require('Module:Format')
lang = 'fr' -- peut-être écrasé par args.lang

local i18n = {
	["errors"] = {
		["property-param-not-provided"] = "property parameter missing",
		["qualifier-param-not-provided"] = "qualifier parameter missing",
		["entity-not-found"] = "entity not found",
		["unknown-claim-type"] = "unknown claim type",
		["unknown-snak-typeg"] = "unknown snak type",
		["unknown-datavalue-type"] = "unknown datavalue type.",
		["unknown-entity-type"] = "unknown entity type",
		["invalid-id"] = "invalid ID",
		},
	["no-label"] = "pas de libellé",
	['no description'] = "pas description",
	["novalue"] = "not applicable",
	["somevalue"] = "inconnu",
	['to translate'] = 'Page utilisant des données de Wikidata à traduire',
	["trackingcat"] = 'Page utilisant des données de Wikidata'
}

local function formatError( key )
    return error(i18n.errors[key])
end

local function addtrackingcat(prop, cat)
	if not prop and not cat then
		return error("no property provided")
	end
	if not cat then
		cat = i18n.trackingcat .. '/' .. string.upper(prop)
	end
	return '[[Category:' .. cat .. ']]'
end

local function removeblanks(args)
	for i, j in pairs(args) do
		if j == '' then args[i] = nil end
	end
	return args
end

local function formatTheUnknown() -- voir si on peut accorder/adapter l'usage de "inconnu"
	return i18n.somevalue
end

local function samevalue(snak, target)
	if snak.snaktype == 'value' and p.getRawvalue(snak) == target then
		return true
	end
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 val =   convertlangcode[j.datavalue.value['numeric-id']]
				table.insert(langlist, val)
			end
		end
	end
	if (#langlist > 1) or (#langlist == 1 and langlist[1] ~= 'fr') then -- si c'est en français, pas besoin de le dire
		return langmodule.indicationMultilingue(langlist)
	end
end

local function getEntity( val )
	if type(val) == 'table' then
		return val
	end
	return mw.wikibase.getEntityObject(val)
end

local function formattable(statements, params) -- transform a table of claims into a table of formatted values
    for i, j in pairs(statements) do
        j = p.formatStatement(j, params)
    end
    return statements
end

local function tableToText(values, params) -- takes a list of already formatted values and make them a text
    if not values then
        return nil
    end
    return linguistic.quickconj( values, params.conjtype)--linguistic.conj( values, params.lang, params.conjtype )
end

function p.getDate(statement)
--[[
returns an object containing a timestamp for easy sorting, and other data
	2 types of object
		dateobject
			{timestamp = string, year = number, month = number, day = number, calendar = string}
		rangeobject
			{timestamp = string, begin = dateobject, ending = dateobject}
]]--
	local q = statement.qualifiers
	if not q or not (q.P585 or q.P580 or q.P582) then
		return nil
	end

	-- si p585 : dateobject
	if q.P585 and q.P585[1].snaktype == 'value' then -- P585: punctual date
		return dates.dateobject(q.P585[1].datavalue.value)
	end

	-- si p580 ou P582 : rangeobject avec une date de début et/ou une date de fin
	if q.P582 and q.P582[1].snaktype == 'value' then
		ending = dates.dateobject(q.P582[1].datavalue.value)
	end
	if q.P580 and q.P580[1].snaktype == 'value' then
		begin = dates.dateobject(q.P580[1].datavalue.value)
	end
	return dates.rangeobject(begin, ending)
end

function p.getFormattedDate(statement, params)
	local datetable = p.getDate(statement)
	if not datetable then
		return nil
	end
	return dates.objecttotext(datetable, params)
end

local function hastargetvalue(claim, target)
	if target == nil then
		return true
	end
	return samevalue(claim.mainsnak, target)
end

local function hasrank(claim, target)
	if target == 'valid' then
		return hasrank(claim, 'preferred') or hasrank(claim, 'normal')
	end
	if claim.rank == target then
		return true
	end
	return false
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 hasqualifier(claim, qualifier, qualifiervalues)
    qualifier = string.upper(qualifier)

    if not qualifier then -- si aucun qualificatif est demandé, ça passe
    	return true
    end
   	if not claim.qualifiers or not claim.qualifiers[qualifier] then
   		return false
   	end

   	if (not qualifiervalues) or (qualifiervalues == {}) then
   		return true -- si aucune valeur spécifique n'est exigée
   	end

   if type(qualifiervalues) == 'string' then
   		qualifiervalues = {qualifiervalues}
   	end

   	for i, j in pairs(claim.qualifiers[qualifier]) do
   		local val = p.getRawvalue(j)
   		for k, l in pairs(qualifiervalues) do
   			if l == val then
   				return true
   			end
   		end
   end
   return false
 end

local function hassource(statement, source, sourceproperty)
    sourceproperty = string.upper(sourceproperty or 'P248')
    local sourcevalue = string.upper(source or '')
	if not statement.references or not statement.references[sourceproperty] then
		return false
	end
	if not source then -- si toutes les sources sont valides, du moment qu'elles utilisent sourceproperty
		return true
	end
	for i, j in pairs(statement.references[sourceproperty]) do
		if p.getRawvalue(j) == source then
			return true
		end
	end
	return true
end

local function hasdate(statement)
    if statement.qualifiers and (statement.qualifiers['P585'] or statement.qualifiers['P580'] or statement.qualifiers['P582']) then
    	return true
    end
	return false
end

local function isinlanguage(snak, lang) -- ne fonctionne que pour les monolingualtext / étendre aux autres types en utilisant les qualifiers ?
	if snak.snaktype == 'value' and snak.datavalue.type == 'monolingualtext' and snak.datavalue.value.language == lang then
		return true
	end
	return false
end

local function isSpecial(snak)
	if snak.snaktype == 'value' then
		return false
	end
	return true
end

local function numval(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 #claims <= numval then
    	return claims
    end
    local newclaims = {}
    while #newclaims < numval do
    	table.insert(newclaims, claims[#newclaims + 1])
    end
    return newclaims
end

local function comparedate(a, b) -- returns true if a is earlier than B or if a has a date but not b
	if a and b then
		return a.timestamp < b.timestamp
	elseif a then
		return true
	end
end

local function chronosort(claims, inverted)
	table.sort(claims, function(a,b)
		local timeA = p.getDate(a)
		local timeB = p.getDate(b)
		if inverted then
			return comparedate(timeB, timeA)
		else
			return comparedate(timeA, timeB)
		end
		end
	)
	return claims
end

function p.sortclaims(claims, sorttype)
	if sorttype == 'chronological' then
		return chronosort(claims)
	elseif sorttype == 'inverted' then
		return chronosort(claims, true)
	elseif type(sorttype) == 'function' then
		table.sort(claims, sorttype)
		return claims
	end
	return claims
end

function p.getRawvalue(snak)
    return p.getDatavalue(snak, {format = 'raw'})
end

function p.getDatavalue(snak, params)
	if not params then
		params = {}
	end
	local formatting = params.formatting
    if snak.snaktype ~= 'value' then
        return nil
    end

    local datatype = snak.datavalue.type
    local value = snak.datavalue.value
    local displayformat = params.format or params.displayformat -- params.format is deprecated
    if datatype == 'wikibase-entityid' then
        if displayformat == 'raw' then
            return "Q" .. tostring(value['numeric-id'])
        elseif type(displayformat) == 'function' then
        	return displayformat(snak, params)
        else
            return p.formatEntity('Q' .. value['numeric-id'], params)
        end

    elseif datatype == 'string' then
        if params.displayformat == 'weblink' then
            return require('Module:Weblink').makelink(value, params.showntext)
        elseif params.urlpattern then
        	value = '[' .. mw.ustring.gsub(params.urlpattern, '$1', value) .. ' ' .. (params.text or value) .. ']'
        end
        return value

	elseif datatype == 'time' then -- format example: +00000001809-02-12T00:00:00Z
		local precision = params.precision -- degré de précision à afficher ('day', 'month', 'year'), inférieur ou égal à value.precision
		if displayformat == 'raw' then
			return value.time
		else
			return dates.objecttotext(dates.dateobject(value, {precision = precision}), {linktopic = params.linktopic})
		end

	elseif datatype == 'globecoordinate' then
		-- retourne une table avec clés latitude, longitude, précision et globe à formater par un autre module (à changer ?)
		value.globe = require('Module:Wikidata/Globes')[value.globe] -- transforme l'ID du globe en nom anglais utilisable par geohack
		if formatting == 'latitude' then
			return value.latitude
		elseif formatting == 'longitude' then
			return value.longitude
		else
			return value -- note : les coordonnées Wikidata peuvent être utilisée depuis Module:Coordinates. Faut-il aussi autoriser à appeler Module:Coordiantes ici ?
		end


    elseif datatype == 'quantity' then -- todo : gérer les paramètre précision
        if displayformat == 'raw' then
            return value.amount
        else
            local str = string.sub(value.amount,2) --
            return formatText.do_formatnum({str})
        end
	elseif datatype == 'monolingualtext' then
		return langmodule.langue({value.language, value.text})
    else
        return formatError('unknown-datavalue-type' )
    end
end

local function getMultipleClaims(args)
	local newargs = args
	local claims = {}
	for i, j in pairs(args.property) do
		newargs.property = j
		local newclaims = p.getClaims(args)
		for k, l in pairs(newclaims) do
			table.insert(claims, l)
		end
	end
	return claims
end

function p.getClaims( args ) -- returns a table of the claims matching some conditions given in args
	args = removeblanks(args)
    if not args.property then
        return formatError( 'property-param-not-provided' )
    end
    if type(args.property) == 'table' then
    	return getMultipleClaims(args)
    end
    --Get entity
    if args.item then args.entity = args.item end -- synonyms
    local entity = args.entity
    if type(entity) ~= 'table' then
        entity = getEntity( args.entity )
    end
	if (not entity) or (not entity.claims) then
		return nil
	end
	local property = string.upper(args.property)
	if not entity.claims[property] then
		return nil
	end
	if not args.rank then
		args.rank = 'best'
	end
	local claims = {}
    -- ~= '' lorsque le paramètre est écrit mais laissé blanc dans une fonction frame
    for i, statement in pairs(entity.claims[property]) do
    	if
    		(
    		not args.excludespecial
    		or
    		not (isSpecial(statement.mainsnak))
    		)
    	and
    		(
			not args.targetvalue
			or
			hastargetvalue(statement, args.targetvalue)
			)
    	and
    		(
    		not args.qualifier
    		or
    		hasqualifier(statement, args.qualifier, args.qualifiervalue or args.qualifiervalues)
    		)
		and
			(
			not args.source
			or
			hassource(statement, args.source, args.sourceproperty)
			)
		and
			not args.isinlanguage
			or
			isinlanguage(statement.mainsnak, args.isinlanguage)
		and
			args.rank == 'best' -- rank == best est traité à a fin
			or
			hasrank(statement, rank)
		then
			table.insert(claims, statement)
		end
	end
	if #claims == 0 then
		return nil
	end
    if args.rank == 'best' then
    	claims = bestranked(claims)
    end
    if args.sorttype then
    	claims = p.sortclaims(claims, args.sorttype)
    end

	if args.numval then
		return numval(claims, args.numval)
	end
	return claims
end

function p.formatClaimList(claims, args)
    if not claims then
        return nil
    end
	for i, j in pairs(claims) do
        claims[i] = p.formatStatement(j, args)
    end
    return claims
end

function p.stringTable(args) -- like getClaims, but get a list of string rather than a list of snaks, for easier manipulation
    local claims = p.getClaims(args)
    return p.formatClaimList(claims, args)
end


local function getQualifiers(statement, qualifs, params)
	if not statement.qualifiers then
		return nil
	end
	local vals = {}
	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 p.getFormattedQualifiers(statement, qualifs, params)
	if not params then params = {} end
	local qualiftable = getQualifiers(statement, qualifs)
	if not qualiftable then
		return nil
	end
	for i, j in pairs(qualiftable) do
		qualiftable[i] = p.formatSnak(j, params)
	end
	return linguistic.conj(qualiftable)
end

function p.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 str = p.formatSnak( statement.mainsnak, args )
    if args.showlang == true then
    	str = (showlang(statement) or '') .. str
    end
    if args.showqualifiers then
    	local qualifs =  args.showqualifiers
    	if type(qualifs) == 'string' then
    		qualifs = mw.text.split(qualifs, ',')
    	end
    	local foundvalues = p.getFormattedQualifiers(statement, qualifs, args)
    	if foundvalues then
    		str = str .. linguistic.inparentheses(foundvalues, lang)
		end
	end

    if args.showdate then -- when "showdate and chronosort are both set, date retrieval is performed twice
    	local timedata = p.getDate(statement)
    	if timedata then
    		local formatteddate = dates.objecttotext(timedata, args)
    		formattteddate = linguistic.inparentheses(formatteddate, lang)
    		str = str .. '<small>' .. formattteddate ..'</small>'
    	end
    end

    if args.showsource and statement.references then --[[needs abritrary access
    	local sourcestring = ''
        for i, ref in pairs(statement.references) do
            if ref.snaks.P248 then
                for j, source in pairs(ref.snaks.P248) do
                    if source.snaktype == 'value' then
                    	local page
                    	if ref.snaks.P304 and ref.snaks.P304[1].snaktype == 'value' then
                    		page = ref.snaks.P304[1].datavalue.value
                    	end
                        local s = require('Module:Cite/sandbox').citeitem('Q' .. source.datavalue.value['numeric-id'], lang, page)
                        s = mw.getCurrentFrame():extensionTag( 'ref', s )
                        sourcestring = sourcestring .. s
                    end
                end
            elseif ref.snaks.P854 and ref.snaks.P854[1].snaktype == 'value' then
            	s = mw.getCurrentFrame():extensionTag( 'ref', formatLink(ref.snaks.P854[1].datavalue.value))
            	sourcestring = sourcestring .. s
            end
        end
        str = str .. sourcestring ]]--
    end
    return str
end

function p.formatSnak( snak, params )
    if not args then args = {} end -- pour faciliter l'appel depuis d'autres modules
    if snak.snaktype == 'somevalue' then
        return formatTheUnknown()
    elseif snak.snaktype == 'novalue' then
        return i18n['novalue'] --todo
    elseif snak.snaktype == 'value' then
        return p.getDatavalue( snak, params)
    else
        return formatError( 'unknown-snak-type' )
    end
end

function p._getLabel(entity, default, inlanguage)
	local label
	if not entity then
		return nil
	end
	if inlanguage then -- cherche dans l'élément complet s'il est déjà chargé, ou s'il faut une libellé non-français, inacessible par  mw.wikibase.label
		entity = getEntity(entity)
	end
	if type(entity) == 'table' then
		if entity and entity.labels and entity.labels[lang] then
			label = entity.labels[lang].value
		end
	else
		label = mw.wikibase.label(entity)
	end
	if label then
		return label
	end

    if default == 'nolabel' then
        return i18n['no-label']
    end
    return entity.id
end

function p._getDescription(entity, lang)
	if type(entity) ~= 'table' then
		entity = getEntity(entity)
	end
    if not entity.descriptions then
    	return i18n['no description']
    end
    local descriptions = entity.descriptions
    if not descriptions then
    	return nil
    end
    if descriptions[lang] then
    	return descriptions[lang].value
    end
    local fblist = require('Module:Fallback').fblist(lang) -- list of fallback languages in no label in the desired language
    for i, j in pairs (mw.language.getFallbacksFor(lang)) do
        if descriptions.lang then
        	return descriptions[lang].value
        end
    end
    if default == 'nolabel' then
        return i18n['no-label']
    end
    return entity.id
end

local function formattedLabel(label, entity, args)
	if not args then
		args = {}
	end
    if args.link== '-' then
        return label
    end
    local link = mw.wikibase.sitelink( entity )
	if not link then
		link = 'd:' .. entity
	end
	return '[[' .. link .. '|' .. label .. ']]'
end



function p.getmainid(claim)
	if claim and claim.mainsnak.snaktype == 'value' then
		return 'Q' .. claim.mainsnak.datavalue.value['numeric-id']
	end
end

function p.formatEntity( entity, args )
	local label = p._getLabel(entity, lang)
	if not label then
		label = entity
		return formattedLabel(label, entity, args) ..  '[[Category:' .. i18n['to translate'] .. ']]'
	end
	return formattedLabel(label, entity, args)
end

function p.getLabel(frame) -- simple for simple templates like {{Q|}}}
	local args = frame.args
    local entity = args.entity
    local lang = lang
    if args.lang and args.lang ~= '' then
		lang = args.lang
	end
	if string.sub(entity, 1, 10) ==  'Property:P' then
		entity = string.sub(entity, 10)
	elseif (string.sub(entity, 1, 1) ~= 'P' and string.sub(entity, 1, 1) ~= 'Q') or (not tonumber(string.sub(entity, 2))) then
		return i18n.errors['invalid-id']
	end
	if not args.link or args.link == '' then -- by default: no link
		args.link = '-'
	end
	if args.link == '-' then
 		return p._getLabel(entity, lang) or  i18n.errors['invalid-id']
	else
		lang = lang
		return p.formatEntity(entity, args)
	end
end

function p._formatStatements( args )--Format statement and concat them cleanly
	if args.value == '-' then
		return nil
	end
	--If a value is already set, use it
	if args.value and args.value ~= '' then
		return args.value
	end
    local valuetable = p.stringTable(args)
    return tableToText(valuetable, args)
end

function p.showQualifier( args )
	local qualifs = args.qualifiers or args.qualifier
	if type(qualifs) == 'string' then
		qualifs = mw.text.split(qualifs, ',')
	end
	if not qualifs then
		return formatError( 'property-param-not-provided' )
	end
	local claims = p.getClaims(args)
	if not claims then
		return nil
	end
	local str = ''
	for i, j in pairs(claims) do
		local new = p.getFormattedQualifiers(j, qualifs, args) or ''
		str = str .. new
	end
	return str
end

function p._formatAndCat(args)
	local val = p._formatStatements( args )
	if val then
		return val .. addtrackingcat(args.property)
	end
end

function p.getTheDate(args)
	local claims = p.getClaims(args)
	if not claims then
		return nil
	end
	local formattedvalues = {}
	for i, j in pairs(claims) do
		table.insert(formattedvalues, p.getFormattedDate(j))
	end
	local val = linguistic.conj(formattedvalues)
	if val and args.addcat == true then
		return val .. addtrackingcat(args.property)
	else
		return val
	end
end
---FONCTIONS depuis le FRAME
function p.getaDate(frame)
	return p.getTheDate(frame.args)
end

function p.getQualifier( frame )
	return p.showQualifier(frame.args)
end

function p.getDescription(frame) -- simple for simple templates like {{Q|}}}
    local entity = frame.args.entity
    if frame.args.lang then
    	lang = frame.args.lang
    end
    if (string.sub(entity, 1, 1) ~= 'P' and string.sub(entity, 1, 1) ~= 'Q') or (not tonumber(string.sub(entity, 2))) then
        return i18n.errors['invalid-id']
    end
    return p._getDescription(entity, lang) or  i18n.errors['invalid-id']
end

function p.numOfClaims(frame)
	local claims = p.getClaims(frame.args)
	if claims then
		return #claims
	else
		return 0
    end
end

function p.formatStatements( frame )
	local args = {}
	if frame == mw.getCurrentFrame() then
		args = frame:getParent().args -- paramètres du modèle appelant (est-ce vraiment une bonne idée ?)
		for k, v in pairs(frame.args) do
			args[k] = v
		end
	else
		args = frame
	end
	return p._formatStatements( args )
end

function p.formatAndCat(frame)
	local args = {}
	if frame == mw.getCurrentFrame() then
		args = frame:getParent().args -- paramètres du modèle appelant (est-ce vraiment une bonne idée ?)
		for k, v in pairs(frame.args) do
			args[k] = v
		end
	else
		args = frame
	end
	return p._formatAndCat( args )
end

return p