Module:Wikidata

From Wikidata
Jump to navigation Jump to search
Lua
CodeDiscussionLinksLink count SubpagesDocumentationTestsResultsSandboxLive code All modules

Module permitting basic data retrieval from items. It is mostly useful for other modules or for {{Data}}

A copy is available on test Wikidata

Functions callable from Lua

p.getClaims returns claims in a particular item that match a particular query

  • item (required): its Qid
  • property (required) property that the claims should have
  • qualifier = qualifiers that the statement should have
  • withrank = rank of the statement ; 'preferred', 'normal', 'deprecated' or 'valid' (ie. normal and preferred). By default = preferred.
  • sourceproperty = this property should be used in the source
  • withsource = the source that should be provided in the statement (if sourceproperty is not provided, the property used is stated in (P248))
  • excludespecial = set to true if you do not want to get "novalue" and "somevalue".
  • numval = if you want to set a maximum number of values to be returned
  • sorttype = set to "chronological" to get the statements in chronological order using the start time (P580), end time (P582) and point in time (P585) qualifiers. Set to "inverted" for chronological order. From a Lua module, you can also define your own sorting criteria.
  • showsource = set to "true" if you want the source of the statement to be displayed.

p.formatStatements(args): returns a string containing the statements given in the table args. Same keys as getClaims, plus formatting arguments:

  • lang (required) for the desired language
  • displayformat = the format in which the args should be returned. For example, for a string-type property displayformat = "weblink" returns a formatted weblink.
  • conj = the conjunction separating the statements. For example, conj = '<br />' will make a new line between each statement.
  • showqualifiers = the qualifiers that should be shown along with the mainsnak value

p.getLabel get the label of an entity

  • entity = entity ID with its Q or P
  • lang

Functions callable from wikitext

  • p.formatStatementsE same as p.formatStatements, except that "lang" is not required. It is most conveniently used from {{Data}} that takes exactly the same arguments.
  • p.getLabel get the label of an entity

Examples

Code Render Comment
{{#invoke:Wikidata|getLabel|entity=Q42}} Douglas Adams Douglas Adams (Q42)
{{#invoke:Wikidata|getLabel|entity=Q42|link=wikipedia}} Douglas Adams
{{#invoke:Wikidata|getLabel|entity=Q42|link=-}} Douglas Adams
{{#invoke:Wikidata|getLabel|entity=Q42|link=wikipedia|lang=ja}} ダグラス・アダムズ
{{#invoke:Wikidata|getLabel|entity=Q42|link=wikidata}} Douglas Adams
{{#invoke:Wikidata|getLabel|entity=Q42|link=wikidata|lang=ja}} ダグラス・アダムズ
{{#invoke:Wikidata|formatStatementsE|item=Q42|property=p31}} human Douglas Adams (Q42) instance of (P31)
{{#invoke:Wikidata|formatStatementsE|item=Q42|property=p31|lang=ja}} ヒト
{{#invoke:Wikidata|formatStatementsE|item=Q42|property=p569}} Douglas Adams (Q42) date of birth (P569)
{{#invoke:Wikidata|formatStatementsE|item=Q42|property=p569|lang=ja}}
{{#invoke:Wikidata|formatStatementsE|item=Q42|property=p569|displayformat=raw}} +1952-03-11T00:00:00Z
{{#invoke:Wikidata|formatStatementsE|item=Q12418|property=p186}} oil paint and poplar panel Mona Lisa (Q12418) made from material (P186)
{{#invoke:Wikidata|formatStatementsE|item=Q12418|property=p186|displayformat=raw}} Q296955 and Q106857865
{{#invoke:Wikidata|formatStatementsE|item=Q12418|property=p186|lang=ja}} 油絵具およびポプラ板
{{#invoke:Wikidata|formatStatementsE|item=Q12418|property=p186|rank=valid}} oil paint, poplar panel and wood rank = "valid" accepts both "preferred" and "normal" values
{{#invoke:Wikidata|formatStatementsE|item=Q12418|property=p186|numval=1}} oil paint returns the number of values numval (priority to those with "rank= preferred", if there are not enough of them, also accepts "rank = normal")
{{#invoke:Wikidata|formatStatementsE|item=Q12418|property=p186|qualifier=p518}} poplar panel Mona Lisa (Q12418) made from material (P186) applies to part, aspect, or form (P518) should only display values that have a p518 qualifier
{{#invoke:Wikidata|formatStatementsE|item=Q12418|property=p186|showqualifiers=p518}} oil paint and poplar panel (painting support) shows the value of the p518 qualifier (if any) in addition to the main value
{{#invoke:Wikidata|formatStatementsE|item=Q83259|property=p669|showqualifiers=p670|delimiter=&#32;}} École Normale Supérieure (Q83259) located on street (P669) house number (P670) shows the value of the P670 qualifier (if any) in addition to the main value separated by a space
{{#invoke:Wikidata|formatStatementsE|item=Q12418|property=P276|sourceproperty=P854}} Salle des États, Louvre Mona Lisa (Q12418) location (P276) reference URL (P854)
{{#invoke:Wikidata|formatStatementsE|item=Q11879536|property=P460|withsource=Q1645493}} Lisa del Giocondo person depicted in Mona Lisa (Q11879536) said to be the same as (P460) Lives of the Most Excellent Painters, Sculptors, and Architects (Q1645493)
{{#invoke:Wikidata|formatStatementsE|item=Q11879536|property=P460|withsource=Q1645493|showsource=true}} Lisa del Giocondo[1][2]
{{#invoke:Wikidata|formatStatementsE|item=Q153|property=P231|showsource=true}} 64-17-5[3][4][5] ethanol (Q153) CAS Registry Number (P231)
{{#invoke:Wikidata|formatStatementsE|item=Q205309|property=P793|sorttype=inverted}} closure, demolition, renovation, renovation, first match and construction Arsenal Stadium (Q205309) significant event (P793)

See also

Code

  1. Giorgio Vasari, Le vite de piu eccellenti architetti, pittori, et scultori italiani, da Cimabue insino a' tempi nostri, and
  2. https://web.archive.org/web/20110508121954/http://www.ub.uni-heidelberg.de/Englisch/news/monalisa.html
  3. ChEBI (title not provided in Wikidata), European Bioinformatics Institute, Creative Commons Attribution 4.0 International
  4. Global Substance Registration System (title not provided in Wikidata)
  5. CAS Common Chemistry
--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:Linguistic')
--local formatDate = require('Module:Complex date') only loaded when needed to save memory in large pages like Wikidata:List of properties/all
local fb = require('Module:Fallback')
local i18nmessages = mw.loadData('Module:i18n/wikidata')

-- Wiki-specific parameters
local defaultlang = mw.getCurrentFrame():preprocess("{{int:lang}}")
local defaultlink = 'wikidata'

local function i18n(str)
	local message = i18nmessages[str]
	if type(message) == 'string' then
		return message
	end
	return fb._langSwitch(message, defaultlang) .. ''
end

local function formatError( key, text )
	return error(i18n(key) .. (text or ''))
end

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

local function removeBlanks(args)
	for i, j in pairs(args) do -- does not work ??
		if (j == '') or (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 isSpecial(snak)
	return snak.snaktype ~= 'value'
end

local function sameValue(snak, target)
	return not isSpecial(snak) and p.getRawvalue(snak) == target
end

local function showLang(statement, str) -- TODO (not yet in proper format)
	--adds a lang indication at the start of the string, based on data in statement
	local mainsnak = statement.mainsnak
	if isSpecial(mainsnak) then
		return str
	end

	local langlist = {}
	if mainsnak.datavalue.type == 'monolingualtext' then
		langlist = {mainsnak.datavalue.value.language}
	elseif statement.qualifiers and statement.qualifiers.P407 then
		local convertlangcode = mw.loadData('Module:Dictionary/lang codes')
		for i, j in pairs( statement.qualifiers.P407 ) do
			if not isSpecial(j) then
				local val = convertlangcode[j.datavalue.value['numeric-id']]
				table.insert(langlist, val)
			end
		end
	end
	if #langlist == 0 then
		return str
	else
		return '(' .. table.concat(langlist) .. ')' .. str
	end
end

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

-- DATE FUNCTIONS
local function splitTimestamp(timestamp, calendar)
	local pattern = "(%W)(%d+)%-(%d+)%-(%d+)"
	local era, year, month, day = timestamp:match(pattern)

	if calendar == 'julian' then
	--todo  year, month, day = formatdate.gregorianToJulian( era .. year, month, day )
	end

	return {day = day, month = month, year = year, era = era, timestamp = timestamp, type = 'dateobject'}
end

local function rangeObject(begin, ending)
	local timestamp
	if begin then
		timestamp = begin.timestamp
	elseif ending then
		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
	if not params then
		params = {}
	end

	local newobj = splitTimestamp(orig.time, orig.calendar) -- initalise l'object en mettant la valeur des dates

	newobj.precision = params.precision or orig.precision
	newobj.type = 'dateobject'
	return newobj
end

local function formatDatepoint(obj, params) -- TO IMPROVE
	if not obj then
		return nil
	end
	local formatDate = require('Module:Complex date')
	local lang = params.lang or defaultlang
	local precision = math.min(obj.precision, params.precision or 15) -- if we don't want to show the value to its full detail
	if precision >= 11 then
		return formatDate.complex_date{args={date1 = obj.year .. '-' .. obj.month .. '-' .. obj.day, lang= lang}}
	elseif precision == 10 then
		return formatDate.complex_date{args={date1 = obj.year .. '-' .. obj.month, lang= lang}}
	elseif precision == 9 then
		return formatDate.complex_date{args={date1 = tostring(obj.year), lang= lang}}
	elseif precision == 8 then
		return formatDate.complex_date{args={date1 = string.sub(tostring(obj.year), 1, 3) .. '0', lang = lang, precision = 'decade'}}
	elseif precision == 7 then
		return formatDate.complex_date{args={date1 = string.sub(tostring(obj.year + 100), 1, 2), lang = lang, precision = 'century'}}
	end
	return nil
end

local function formatDaterange(obj, params) --TODO
	local begin = formatDatepoint(obj.begin, params) or ''
	local ending = formatDatepoint(obj.ending, params) or ''
	return begin .. '-' .. ending
end

local function objectToText(obj, params)
	if obj.type == 'dateobject' then
		return formatDatepoint(obj, params)
	elseif obj.type == 'rangeobject' then
		return formatDaterange(obj, params)
	end
	return nil
end

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

function p.getDate(obj)
--[[
returns an object containing a timestamp for easy sorting, and other data
	possible types of object:
		dateobject
			{timestamp = string, year = number, month = number, day = number, calendar = string}
		rangeobject
			{timestamp = string, begin = dateobject, ending = dateobject}
]]--
	if not obj then
		return nil
	end
	if type(obj) == 'string' then
		obj = p.getEntity(obj)
	end

	-- if obj is a statement with date, get it
	if obj.mainsnak and not isSpecial(obj.mainsnak) and obj.mainsnak.datatype == 'time' then
		return dateObject(obj.mainsnak.datavalue.value)
	end

	-- else preload relevant data
	local qualifs = obj.qualifiers -- when obj is a statement, look in qualifiers
	local claims = obj.claims -- when obj is an item, look in claims

	local pointprop = {'P585', 'P571'} -- dates corresponding to a punctual fact
	local beginprop = {'P580', 'P569'} -- start date, birth date == start of a date range
	local endingprop = {'P582', 'P570'}

	local function getval(prop)
		local val
		if claims and claims[prop] and not isSpecial(claims[prop][1].mainsnak) then
			val = claims[prop][1].mainsnak.datavalue.value
		elseif qualifs and qualifs[prop] and not isSpecial(qualifs[prop][1]) then
			val = qualifs[prop][1].datavalue.value
		end
		if val then
			return dateObject(val)
		end
		return nil
	end

	for i, prop in pairs(pointprop) do
		local val = getval(prop)
		if val then return val end
	end
	--if no date has not been found, look for startdate or enddate
	local begin, ending
	for i, prop in pairs(beginprop) do
		begin = getval(prop)
		if begin then
			break
		end
	end
	for i, prop in pairs(endingprop) do
		ending = getval(prop)
		if ending then
			break
		end
	end
	if begin or ending then
		return rangeObject(begin, ending)
	end
	return nil
end

function p.getFormattedDate(statement, params)
	local datetable = p.getDate(statement)
	if not datetable then
		return nil
	end
	return 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')
	else
		return claim.rank == target
	end
end

local function bestRanked(claims)
	if not claims then
		return nil
	end
	local preferred, normal = {}, {}
	for _, j in ipairs(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)
	if not qualifier then -- si aucun qualificatif est demandé, ça passe
		return true
	end

	qualifier = string.upper(qualifier)
	if not claim.qualifiers or not claim.qualifiers[qualifier] then
		return false
	end

	if type(qualifiervalues) == 'string' then
		qualifiervalues = mw.text.split(qualifiervalues, ',')
	end

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

	for _, j in ipairs(claim.qualifiers[qualifier]) do
		for _, l in ipairs(qualifiervalues) do
			if sameValue(j, l) then
				return true
			end
		end
	end
	return false
 end

local function hasSource(statement, source, sourceproperty)
	if not statement.references then
		return false
	end
	sourceproperty = string.upper(sourceproperty or 'P248')
	local sourcevalue = string.upper(source or '')
	for _, ref in ipairs(statement.references) do
		for prop, content in pairs(ref.snaks) do
			if prop == sourceproperty then
				if sourcevalue == '' then
					return true
				else
					for _, k in ipairs(content) do
						if sameValue(k, source) then
							return true
						end
					end
				end
			end
		end
	end
	return false
end

local function hasDate(statement)
	if not statement.qualifiers then
		return false
	end
	local dateprops = {'P580', 'P585', 'P582'}
	for i, prop in pairs(dateprops) do
		if statement.qualifiers[prop] then
			return true
		end
	end
	return false
end

local function isInLanguage(snak, lang) -- ne fonctionne que pour les monolingualtext / étendre aux autres types en utilisant les qualifiers ?
	return not isSpecial(snak) and snak.datavalue.type == 'monolingualtext' and snak.datavalue.value.language == lang
end

local function numval(claims, numval) -- retourn les numval premières valeurs de la table claims
	local numval = tonumber(numval) or 0 -- raise an 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 wikipediaLink(entity, lang)
	local link
	if type(entity) == 'table' then
		link = entity:getSitelink(lang .. 'wiki')
	else
		link = mw.wikibase.getSitelink(entity, lang .. 'wiki')
	end
	if link then
		return ':' .. lang .. ':' .. link
	end
	return nil
end

local function getLink(entity, typelink, lang)
	if typelink == 'wikidata' then
		if type(entity) == 'table' then
			if entity.type == 'property' then
				return 'd:P:' .. entity.id
			elseif entity.type == 'lexeme' then
				return 'd:L:' .. entity.id
			else
				return 'd:' .. entity.id
			end
		else
			if string.sub(entity, 1, 1) == 'P' then
				return 'd:P:' .. entity
			elseif string.sub(entity, 1, 1) == 'L' then
				return 'd:L:' .. entity
			else
				return 'd:' .. entity
			end
		end

	elseif typelink == 'wikipedia' then
		return wikipediaLink(entity, lang or defaultlang)

	elseif typelink == 'anywikipedia' then
		for _, lg in ipairs(fb.fblist(lang or defaultlang, true)) do
			local link = wikipediaLink(entity, lg)
			if link then
				return link
			end
		end
	end
	return nil
end

function p.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
	return false
end

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

	return objs
end

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

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

function p.showentity(entity, lang)
	if not entity then
		return nil
	end
	local label, link, id = p._getLabel(entity, lang), getLink(entity, 'wikidata')
	if type(entity) == 'table' then
		id = entity.id
	else
		id = entity
	end
	return '[[' .. link .. '|' .. label .. ']] <small>(' .. id .. ')</small>'
end

function p.getDatavalue(snak, params)
	if isSpecial(snak) then
		return nil
	end

	if not params then
		params = {}
	end

	local displayformat = params.displayformat
	local valuetype = snak.datavalue.type
	local value = snak.datavalue.value

	if valuetype == 'wikibase-entityid' then
		if type(displayformat) == 'function' then
			return displayformat(snak, params)
		end
		local id = snak.datavalue.value.id
		if displayformat == 'raw' then
			return id
		elseif displayformat == 'wikidatastyle' then
			return p.showentity(id, params.lang)
		else
			return p.formatEntity(id, params)
		end

	elseif valuetype == 'string' then
		local showntext = params.showntext
		if displayformat == 'weblink' then
			if showntext then
				return '[' .. value .. ' ' .. showntext .. ']'
			else
				return value
			end
		end
		if ({['math'] = 1, ['musical-notation'] = 1})[snak.datatype] == 1 and displayformat ~= 'raw' then
			value = mw.wikibase.formatValue(snak)
		else
			if params.urlpattern then
				showntext = mw.text.nowiki(showntext or value)
				value = mw.ustring.gsub(value, '%%', '%%%%') -- escape '%'
				value = '[' .. mw.ustring.gsub(mw.ustring.gsub(params.urlpattern, '$1', value), ' ', '%%20') .. ' ' .. showntext .. ']'
			elseif params.pattern then
				local pattern = mw.ustring.gsub(params.pattern, '%%', '%%%%')
				value = mw.ustring.gsub(value, '%%', '%%%%')
				value = mw.ustring.gsub(pattern, '$1', value)
			else
				if displayformat ~= 'raw' then
					value = mw.text.nowiki(value)
				end
			end
		end
		return value

	elseif valuetype == 'time' then -- format example: +00000001809-02-12T00:00:00Z
		if displayformat == 'raw' then
			return value.time
		else
			return objectToText(dateObject(value), params)
		end

	elseif valuetype == 'globecoordinate' 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
		elseif displayformat == 'qualifier' then
			local coord = require 'Module:Coordinates'
			value.globe = mw.loadData('Module:Wikidata/Globes')[value.globe]
			value.precision = nil
			return coord._coord(value)
		else
			value.globe = mw.loadData('Module:Wikidata/Globes')[value.globe] -- transforme l'ID du globe en nom anglais utilisable par geohack
			return value -- note : les coordonnées Wikidata peuvent être utilisée depuis Module:Coordinates. Faut-il aussi autoriser à appeler Module:Coordiantes ici ?
		end

	elseif valuetype == 'quantity' then -- todo : gérer les paramètre précision
		if displayformat == 'raw' then
			return tonumber(value.amount)
		else
			local formatNum = require 'Module:Formatnum'
			local number = formatNum.formatNum(value.amount, params.lang)
			local unit = mw.ustring.match(value.unit, '(Q%d+)')
			if unit then
				number = number .. '&nbsp;' .. p.formatEntity(unit, params)
			end
			return number
		end
	elseif valuetype == 'monolingualtext' then
		return '<span lang="' .. value.language .. '">' .. value.text .. '</span>'
	else
		return formatError( 'unknown-datavalue-type', valuetype )
	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)
		if newclaims then
			for k, l in pairs(newclaims) do
				table.insert(claims, l)
			end
		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 -- synonyms
		args.entity = args.item
	end
	local property = string.upper(args.property)
	local allClaims
	local entity = args.entity
	if type(entity) == 'table' then
		allClaims = (entity and entity.claims and entity.claims[property]) or {}
	else
		allClaims = mw.wikibase.getAllStatements(entity, property)
	end
	if #allClaims == 0 then
		return nil
	end

	if not args.rank then
		args.rank = 'best'
	end
	local claims = {}
	for _, statement in ipairs(allClaims) 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.qualifiervalues or args.qualifiervalue)
		)
		and
		(
			not args.withsource or args.withsource == '-'
			or
			hasSource(statement, args.withsource, 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, args.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
		j = string.upper(j)
		if statement.qualifiers[j] then
			local inserted = false
			if statement.qualifiers[j][1].datatype == 'monolingualtext' then
				local in_preferred_lang
				for _, language in ipairs(fb.fblist(params.lang or defaultlang, true)) do
					for _, snak in ipairs(statement.qualifiers[j]) do
						if isInLanguage(snak, language) then
							in_preferred_lang = snak
							break
						end
					end
					if in_preferred_lang then
						break
					end
				end
				if in_preferred_lang then
					table.insert(vals, in_preferred_lang)
					inserted = true
				end
			end
			if not inserted then
				for _, snak in pairs(statement.qualifiers[j]) do
					table.insert(vals, snak)
				end
			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, params)
	if not qualiftable then
		return nil
	end
	for i, j in pairs(qualiftable) do
		local params = params
		if j.datatype == 'globe-coordinate' then
			params.displayformat = 'qualifier'
		end
		qualiftable[i] = p.formatSnak(j, params)
	end
	return linguistic.conj(qualiftable, params.lang or defaultlang, params.conjtype)
end

function p.formatStatement( statement, args )
	if not statement.type or statement.type ~= 'statement' then
		return formatError( 'unknown-claim-type', statement.type )
	end
	if not args then args = {} end
	local lang = args.lang or defaultlang
	local str = p.formatSnak( statement.mainsnak, args )
	if args.showlang == true then
		str = showLang(statement, str)
	end

	local qualifs = args.showqualifiers
	if qualifs then
		if type(qualifs) == 'string' then
			qualifs = mw.text.split(qualifs, ',')
		end
		local foundvalues = p.getFormattedQualifiers(statement, qualifs, args)
		if foundvalues then
			if args.delimiter then
				str = str .. args.delimiter .. foundvalues
			else
				str = str .. linguistic.inparentheses(foundvalues, lang)
			end
		end
	end

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

	if args.showsource and statement.references then
		local cite = require 'Module:Cite'
		local frame = mw.getCurrentFrame()
		local sourcestring = ''
		local s
		for _, ref in ipairs(statement.references) do
			if ref.snaks.P248 then
				for j, source in pairs(ref.snaks.P248) do
					if not isSpecial(source) then
						local page
						if ref.snaks.P304 and not isSpecial(ref.snaks.P304[1]) then
							page = ref.snaks.P304[1].datavalue.value
						end
						s = cite.citeitem(source.datavalue.value.id, lang, page)
						s = frame:extensionTag( 'ref', s )
						sourcestring = sourcestring .. s
					end
				end
			elseif ref.snaks.P854 and not isSpecial(ref.snaks.P854[1]) then
				s = frame:extensionTag( 'ref', p.getDatavalue(ref.snaks.P854[1]) )
				sourcestring = sourcestring .. s
			end
		end
		str = str .. sourcestring
	end
	return str
end

function p.getmainid(claim)
	if claim and not isSpecial(claim.mainsnak) then
		return claim.mainsnak.datavalue.value.id
	end
	return nil
end

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

local function defaultLabel(entity, displayformat) -- label when no label is available
	if displayformat == 'id' then
		if type(entity) ~= 'table' then
			return entity
		else
			return entity.id
		end
	end
	return i18n('no-label')
end

function p._getLabel(entity, lang, default, fallback)
	if not entity then
		return nil
	end
	if not lang then
		lang = defaultlang
	end
	if type(entity) ~= 'table' and lang == defaultlang then
		local label, lg = mw.wikibase.getLabelWithLang(entity)
		if label and (fallback ~= '-' or lg == lang) then
			return label
		end
	else
		entity = p.getEntity(entity)
		if entity and entity.labels then
			if fallback ~= '-' then
				for _, lg in ipairs(fb.fblist(lang, true)) do
					if entity.labels[lg] then
						return entity.labels[lg].value
					end
				end
			else
				if entity.labels[lang] then
					return entity.labels[lang].value
				end
			end
		end
	end
	return defaultLabel(entity, default)
end

function p._getDescription(entity, lang, fallback)
	if not entity then
		return i18n('no description')
	end
	if not lang then
		lang = defaultlang
	end
	if type(entity) ~= 'table' and lang == defaultlang then
		local description, lg = mw.wikibase.getDescriptionWithLang(entity)
		if description and (fallback ~= '-' or lg == lang) then
			return description
		end
	else
		entity = p.getEntity(entity)
		if entity and entity.descriptions then
			if fallback ~= '-' then
				for _, lg in ipairs(fb.fblist(lang, true)) do
					if entity.descriptions[lg] then
						return entity.descriptions[lg].value
					end
				end
			else
				if entity.descriptions[lang] then
					return entity.descriptions[lang].value
				end
			end
		end
	end
	return i18n('no description')
end

local function formattedLabel(label, entity, args)
	local link = getLink(entity, args.link, args.lang)
	if not link then
		link = getLink(entity, defaultlink, args.lang)
	end
	if not link then
		return label
	else
		return '[[' .. link .. '|' .. label .. ']]'
	end
end

function p.formatEntity( entity, args )
	if not entity then
		return nil
	end
	if not args then
		args = {}
	end
	local label = p._getLabel(entity, args.lang, 'id', args.fallback)
	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 = args.lang
	if not entity then
		return i18n('invalid-id')
	end

	if string.sub(entity, 1, 10) == 'Property:P' then
		entity = string.sub(entity, 10)
	elseif string.sub(entity, 1, 8) == 'Lexeme:L' then
		entity = string.sub(entity, 8)
	elseif not ({L = 1, P = 1, Q = 1})[string.sub(entity, 1, 1)] or not tonumber(string.sub(entity, 2)) then
		return i18n('invalid-id')
	end

	if not args.link or args.link == '' or args.link == '-' then -- by default: no link
		if lang == '' then
			lang = defaultlang
		end
		return p._getLabel(entity, lang, args.default, args.fallback)
	else
		return p.formatEntity(entity, args)
	end
end

function p._formatStatements( args )--Format statements 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 = ''
	local new
	for _, cl in ipairs(claims) do
		new = p.getFormattedQualifiers(cl, 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
	return nil
end

function p.getTheDate(args)
	local claims = p.getClaims(args)
	if not claims then
		return nil
	end
	local formattedvalues = {}
	for _, cl in ipairs(claims) do
		table.insert(formattedvalues, p.getFormattedDate(cl))
	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 not entity then
		return i18n('invalid-id')
	end
	local lang = frame.args.lang
	local fallback = frame.args.fallback

	return p._getDescription(entity, lang, fallback)
end

function p.formatStatements( args )
	return p._formatStatements( args )
end

function p.formatStatementsE(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

function p.getEntityFromId(id)
	return p.getEntity(id)
end

-- https://www.wikidata.org/w/index.php?title=Wikidata:Project_chat&oldid=1439583623#Check_if_redirect
-- Not applicable outside Wikidata
function p.isRedirect(frame)
	return mw.title.new(frame.args[1]).isRedirect
end

return p