Aller au contenu

Module:Wikidata

De Vouiquipèdia, l’enciclopèdia abada.

La documentation pour ce module peut être créée à Module:Wikidata/doc

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

local wd = {}

-- creation of a subobject to store comparison funtions, used for sorting claims
-- to be able to build more complex sorts like topological sorts

wd.compare = {}

local databases = { }
local modules = { }
local databasesNames = { -- modulos de balyês statiques que pôvont étre apelâs avouéc mw.loadData(), que demandont pas require()
	i18n = 'Module:Wikidata/I18n',
	globes = 'Module:Wikidata/Globes',
	langhierarchy = 'Module:Wikidata/Hièrarchia de lengoues',
	langcodes = 'Module:Diccionèro Wikidata/Codes lengoua', -- big, infrequently used
	invertedlangcodes = 'Module:Diccionèro Wikidata/Codes lengoua/envèrsâ'
}
local modulesNames = {
	reference = 'Module:Wikidata/Rèferences',
	linguistic = 'Module:Lengouistico',
	datemodule = 'Module:Dâta',
	formatDate = 'Module:Dâta complèxa',
	formatNum = 'Module:Convèrsion',
	langmodule = 'Module:Lengoua',
	cite = 'Module:Biblio',
	weblink = 'Module:Weblink'
}

local function loadDatabase( t, key )
	if databasesNames[key] then
		local m = mw.loadData( databasesNames[key] )
		t[key] = m
		return m
	end
end
local function loadModule( t, key )
	if modulesNames[key] then
		local m = require( modulesNames[key] )
		t[key] = m
		return m
	end
end
setmetatable( databases, { __index = loadDatabase } )
setmetatable( modules, { __index = loadModule } ) -- d’ense lo require() serat opèrâ solament se nècèssèro per modulos.(nom du modulo)

local datequalifiers = {'P585', 'P571', 'P580', 'P582', 'P1319', 'P1326'}

-- === I18n ===
local defaultlang = mw.getContentLanguage():getCode()

function wd.translate(str, rep1, rep2)
	str = databases.i18n[str] or str
	if rep1 and (type (rep1) == 'string') then
		str = str:gsub('$1', rep1)
	end
	if rep2 and (type (rep2) == 'string')then
		str = str:gsub('$2', rep2)
	end
	return str
end

local function addCat(cat, sortkey)
	if sortkey then
		return '[[Category:' .. cat .. '|' .. sortkey .. ']]'
	end
	return '[[Category:' .. cat .. ']]'
end

local function formatError( key , category, debug)
    if debug then
        return error(databases.i18n[key] or key)
    end
    if category then
        return addCat(category, key)
    else
        return addCat(databases.i18n['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 snak.datavalue.value['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) -- transfôrme en grelye les chênes que vegnont du _Vouiquitèxto qu’emplèyont de virgules de sèparacion
	if type(val) == 'string' then
		val = mw.text.split(val, ",")
	end
	return val
end

function wd.isHere(searchset, val, matchfunction)
	for i, j in pairs(searchset) do
		if matchfunction then
			if matchfunction(val,j) then
				return true
			end
		else
			if val == j then
				return true
			end
		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 chargiér lo morsél entiér
		local link = mw.wikibase.getSitelink(entity)
		if link then
			local test_redirect = mw.title.new(link) -- remplacement de les redirèccions (reteriér se trop chier)
			if test_redirect.isRedirect and test_redirect.redirectTarget then
				link = test_redirect.redirectTarget.fullText
				if link == mw.title.getCurrentTitle().text then return nil end --redirection vers l'article de départ : pas de lien plutôt qu'une mise en gras
			end
		end
		return link, 'wiki', defaultlang
	end
	if project == 'wikidata' then
		return wikidataLink(entity), 'wikidata'
	end
	local projects = {
		-- nom = {prèfixo dessus Wikidata, prèfixo por los lims dessus Vouiquipèdia, apondre prèfixo de lengoua}
		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
		-- ètat rèspèctâ quand showonlyqualifier est un paramètro rensègnê
		-- dens cél câs, claim est pas na dècllaracion entiére, mas NA snak qualifiâye du main snak
		type = claim.snaktype
	end

	return type == 'value'
end

local function hasTargetValue(claim, targets) -- retôrne true se la valor est dens la lista des target, ou ben s’o est na valor spèciâla filtrâye a pârt per 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 se la valor est pas dens la lista, ou ben s’o est na valor spèciâla (filtrâye a pârt per excludespecial)
	return wd.isSpecial(claim.mainsnak) or not ( hasTargetValue(claim, values) )
end

local function hasTargetClass(claim, targets, maxdepth) -- retôrne true se la valor est n’enstance d’una cllâsse dens la lista des target, ou ben s’o est na valor spèciâla filtrâye a pârt per excludespecial
	local id = wd.getMainId(claim)
	local targets = wd.splitStr(targets)
	local maxdepth = maxdepth or 10
	local matchfunction = function(value, target) return wd.isInstance(target, value, maxdepth) end
	return wd.isHere(targets, id, matchfunction) or wd.isSpecial(claim.mainsnak)
end

local function excludeClasses(claim, classes, maxdepth) -- true se la valor est n’enstance d’una cllâsse dens la lista, ou ben s’o est na valor spèciâla (filtrâye a pârt per excludespecial)
	return wd.isSpecial(claim.mainsnak) or not ( hasTargetClass(claim, classes, maxdepth) )
end

local function hasTargetSuperclass(claim, targets, maxdepth) -- retôrne true se la valor est na sot-cllâsse d’una cllâsse dens la lista des target, ou ben s’o est na valor spèciâla filtrâye a pârt per excludespecial
	local id = wd.getMainId(claim)
	local targets = wd.splitStr(targets)
	local maxdepth = maxdepth or 10
	local matchfunction = function(value, target) return wd.isSubclass(target, value, maxdepth) end
	return wd.isHere(targets, id, matchfunction) or wd.isSpecial(claim.mainsnak)
end

local function excludeSuperclasses(claim, classes, maxdepth) -- true se la valor est na sot-cllâsse d’una cllâsse dens la lista, ou ben s’o est na valor spèciâla (filtrâye a pârt per excludespecial)
	return wd.isSpecial(claim.mainsnak) or not ( hasTargetSuperclass(claim, classes, maxdepth) )
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) -- contrôlo por un qualificatif endividuèl
		if not claimqualifs[qualif] then
			return false
		end
		if not (acceptedvals) then -- se gins de valor spècefica est demandâye, OK
			return true
		end
		for i, wanted in pairs(acceptedvals) do
			for j, actual in pairs(claimqualifs[qualif]) do
				--On regarde si la valeur de l'élément correspond à un Qid cherché, puis à une chaîne de caractères cherchée.
				--Cela suppose qu'un élément n'a pas comme valeur le Qid recherché exactement, mais c'est improbable.
				if wd.getId(actual) == wanted or wd.getDataValue(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] -- los snaks qu’emplèyont la propriètât demandâye
	if (not candidates) then
		return false
	end
	if (targetsource == "any") then -- se na sé-quinta valor est accèptâye tant qu’emplèye en ref la propriètât demandâye
		return true
	end
	targetsource = wd.splitStr(targetsource)
	for _, source in pairs(candidates) do
		local s = wd.getId(source)
		for i, target in pairs(targetsource) do
			if s == target then return true end
		end
	end
	return false
end

local function excludeQualifier(claim, qualifier, qualifiervalues)
	return not wd.hasQualifier(claim, qualifier, qualifiervalues)
end

local function hasLink(claim, site, lang)
	if (claim.mainsnak.snaktype ~= 'value') then -- pas enlevar les valors spèciâles, y at na fonccion consacrâye por cen
		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)
	if type(lang) == 'table' then -- s’o est na grelye de language sèparâyes per des virgules, les accèptont totes
		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

	-- por los ôtros tipos de balyês : rechèrche dens los qualificatifs
	if (lang == 'frp') then
		lang = 'Q150'
	elseif (lang == 'en') then
		lang = 'Q1860'
	else
		lang = databases.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 -- se sâvont pas la lengoua, ... qu’o est bon
end

local function firstVals(claims, numval) -- retôrne les numval premiéres valors de la grelye 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) -- retôrne les valors de la grelye claims dês 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

-- retôrne les valors de la grelye claims dês removedupesdate,
-- sen les dâtes en droblos avouéc convèrsion entre los calendriér jelien et grègorien,
-- ou ben justo en catègorisent se lo paramètro removedupesdate est pariér a '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) -- pariér a wd.isHere mas retôrne l’endèxo de la valor trovâye
		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 snak = claim.mainsnak or claim
		if (snak.snaktype == 'value') and (snak.datatype == 'time') and snak.datavalue.value.precision >= 11 then -- por un time et que la prècision est u muens l’an
			local iso = snak.datavalue.value.time
			_, _, iso = string.find(iso, "(+%d+-%d+-%d+T)")
			local deleteIfDuplicate = false
			if snak.datavalue.value.calendarmodel == 'http://www.wikidata.org/entity/Q1985727' then -- se la dâta est grègorièna
				if modules.formatDate.before('+1582', iso) then -- se devant 1582 carculont la dâta jelièna
					_, _, 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 -- se la dâta est ja presenta
					cat = cat .. '[[Catègorie:Articllo avouéc des dâtes pariéres que vegnont de wikidata dens lo code de l’enfocajon]]'
					if removedupesdate == "cat" then -- fâre ren que catègorisar
						table.insert(newIsos, iso)
						table.insert(newClaims, claim)
					elseif not deleteIfDuplicate then -- enlevar l’ôtra dâta se la dâta corenta est pas étâye convèrtia
						newClaims[index] = claim
					end -- ôtrament enlevar la dâta corenta
				else -- gins de droblo
					table.insert(newIsos, iso)
					table.insert(newClaims, claim)
				end
			elseif snak.datavalue.value.calendarmodel == 'http://www.wikidata.org/entity/Q1985786' then -- se dâta jelièna
				if not modules.formatDate.before('+1582', iso) then -- s’aprés 1582 carculont la dâta grègorièna
					_, _, 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 -- se la dâta est ja presenta
					cat = cat .. '[[Catègorie:Articllo avouéc des dâtes pariéres que vegnont de wikidata dens lo code de l’enfocajon]]'
					if removedupesdate == "cat" then -- fâre ren que catègorisar
						table.insert(newIsos, iso)
						table.insert(newClaims, claim)
					elseif not deleteIfDuplicate then -- enlevar l’ôtra dâta se la dâta corenta est pas étâye convèrtia
						newClaims[index] = claim
					end -- ôtrament enlevar la dâta corenta
				else -- gins de droblo
					table.insert(newIsos, iso)
					table.insert(newClaims, claim)
				end
			else -- ôtro calendriér
				table.insert(newIsos, iso)
				table.insert(newClaims, claim)
			end
		else -- prècision ensufisenta
			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 ipairs(qualifs or datequalifiers) 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 -- câs estandârd
		return condition(claim)
	end
	return formatError('invalid type', 'function', type(condition))
end

local function minPrecision(claim, minprecision)
	local snak
	if claim.qualifiers then -- se na dâta est balyêe en qualificatif, o est lyé qu’emplèyont de prèference u mainsnak
		for i, j in ipairs(datequalifiers) do
			if claim.qualifiers[j] then
				snak = claim.qualifiers[j][1]
				break
			end
		end
	end
	if not snak then
		snak = claim.mainsnak or claim
	end
	if (snak.snaktype == 'value') and (snak.datatype == 'time') and (snak.datavalue.value.precision < minprecision) then
		return false
	end
	return true
end

function wd.sortClaims(claims, sorttype)
	if not claims then
		return nil
	end
	if wd.isHere({'chronological', 'order', 'inverted', 'age', 'ageinverted'}, sorttype) then
		return wd.chronoSort(claims, sorttype)
	elseif sorttype == 'ascending' then
		return wd.quantitySort(claims)
	elseif sorttype == 'descending' then
		return wd.quantitySort(claims, true)
	elseif sorttype == 'alphabetical' then
		return wd.alphabetSort(claims)
	elseif sorttype == "language" then
		return wd.sortByLanguage(claims)
	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) --enléve de la grelye de claims celes que sont èliminâs per yon des filters de la grelye des filters
	
	args.excludevalues = args.excludevalues or args.excludevalue --support du singulier pour des arguments normalement au pluriel
	args.excludeclasses = args.excludeclasses or args.excludeclass
	args.excludesuperclasses = args.excludesuperclasses or args.excludesuperclass
	
	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','maxdepth'} )
		filter('targetclass', hasTargetClass, {'targetclass','maxdepth'} )
		filter('targetsuperclass', hasTargetSuperclass, {'targetsuperclass'} )
		filter('atdate', atDate, {'atdate'} )
		filter('qualifier', wd.hasQualifier, {'qualifier', 'qualifiervalue'} )
		filter('excludequalifier', excludeQualifier, {'excludequalifier', 'excludequalifiervalue'} )
		filter('withsource', hasSource, {'withsource', 'sourceproperty'} )
		filter('excludevalues', excludeValues, {'excludevalues'})
		filter('excludeclasses', excludeClasses, {'excludeclasses','maxdepth'})
		filter('excludesuperclasses', excludeSuperclasses, {'excludesuperclasses','maxdepth'})
		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 == '-') or (entity == wd.translate('somevalue')) or (entity == modules.linguistic.ucfirst(wd.translate('somevalue'))) then
		return nil
	end
	if args.labelformat and args.labelformat == 'gendered' then
		args.labelformat = wd.getgender(entity)
	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 ben chouèx de maneyance de les fôtes ?
	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 : tèxto rendu, lang : lengoua de ceti
	if lang1 == defaultlang then -- lo ples èconomico
		str, lang = mw.wikibase.getLabelWithLang(entity) -- lo libèlâ pôt étre en arpetan, en francês ou ben en angllès
	else
		str = mw.wikibase.getLabelByLang(entity, lang1)
		if str then lang = lang1 end
	end
	if str and (lang == lang1 or lang == "mul") then --gins de catègoria "a traduire" s’ils ant avu un tèxto dens la lengoua dèsirâye (de môda frp) ou ben multilengouo
		return str
	end
	if lang2 then -- lengoua secondèra, avouéc catègoria "a traduire"
		str2 = mw.wikibase.getLabelByLang(entity, lang2)
		if str2 then
			lang = lang2
			str = str2
		end
	end
	if not str then --se ni lang1, ni lang2 ni l’angllès sont pas presents, parcôrs de la hièrarchia de les lengoues
		for _, trylang in ipairs(databases.langhierarchy.codes) do
			str = mw.wikibase.getLabelByLang(entity, trylang)
			if str then
				lang = trylang
				break
			end
		end
	end

	if str then
		local translationCat = databases.i18n['to translate']
		translationCat = translationCat .. (databases.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
	if wd.isGender[params.labelformat] then
		labelformat = function(objectid) return wd.genderedlabel(objectid, params.labelformat) end
	end

	local label, translationCat

	if type(labelformat) == 'function' then -- sèrvét a des câs particuliérs
		label, translationCat = labelformat(entity)
	end

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

	if labelformat == 'bold' or labelformat2 == 'bold' then --mises en forme typographiques simples accessibles depius le wikicode
		labelformat2 = function(str) if label then return "'''" .. str .. "'''" end end
	end
	if labelformat == 'italic' or labelformat2 == 'italic' then
		labelformat2 = function(str) if label then return "''" .. str .. "''" end end
	end
	if type(labelformat2) == 'function' and label then
		label = labelformat2(label)
	end

	translationCat = translationCat or "" -- serat adés apondua u rèsultat mas serat voueda se la catègoria d’entretin est pas nècèssèra

	if not label then
		if (defaultlabel == '-') then
			return nil
		end
		local link = wd.siteLink(id, 'wikidata')
		return '[[' .. link .. '|' .. id .. ']]' .. translationCat
-- se gins de libèlâ, bètont un lim de vers Wikidata por que comprègnont a què cen en apèle
	end
	
	if params.ucfirst_entity then
		label =  modules.linguistic.ucfirst(label)
	end

	-- dètèrmenacion du fêt que seyont ou ben pas aprés rendre lo morsél sus la pâge de son articllo
	local rendering_entity_on_its_page = wd.isPageOfQId(id)

	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 link:match('^Category:') or link:match('^Catègorie:') then -- attention, le « é » est multibyte
			-- liyér vers na catègoria nan pas catègorisar
			link = ':' .. link
		end
		return '[[' .. link .. '|' .. 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 -- lo tèxto montrâ du lim
		if site == 'wiki' then
			icon, class, title = langcode, "endiquior-lengoua", wd.translate('see-another-language', mw.language.fetchLanguageName(langcode, defaultlang))
		elseif site == 'wikidata' then
			icon, class, title = 'd', "endiquior-lengoua", wd.translate('see-wikidata')
		else
			title = wd.translate('see-another-project', site)
		end
		local val = '[[' .. sidelink .. '|' .. '<span class = "' .. (class or '').. '" title = "' .. (title or '') .. '">' .. icon .. '</span>]]'
		return label .. ' <small>(' .. val .. ')</small>' .. translationCat
	end
	return label .. translationCat
end

function wd.addTrackingCat(prop, cat) -- dêt des côps étre apelâ per d’ôtros modulos
	if type(prop) == 'table' then
		local catTable = {}
		for i, v in ipairs(prop) do
		    catTable[i] = wd.addTrackingCat(v,cat)
		end
		return table.concat(catTable)
	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 databases.langcodes[tonumber(entityid:sub(2))]
end

local function showLang(statement) -- retôrne lo code lengoua entre-mié parentèsa devant la valor (per ègzemplo por les biblios et los lims de defôr)
	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 -- s’o est en arpetan, pas fôta de lo dére
		langlist.maxLang = 3
		return modules.langmodule.indicationMultilingue(langlist)
	end
end


-- === DATE HANDLING ===

local function fuzzydate(str, precision) -- apond lo qualificatif "vers" a na dâta
	if not str then
		return nil
	end
	if (precision >= 11) or (precision == 7) or (precision == 6) then --dâtes avouéc jorns, siècllos, milènèros
		return "vers lo " .. str
	end
	if (precision == 8) then --dècènies ("ans ...")
		return "vers los " .. str
	end
	return "vers " .. str
end

function wd.addStandardQualifs(str, statement, onlygeneral)
	-- qualificateurs de date ou de lieu approximatif ou d'info globalement incertaine ; onlygenereal=true pour rerstreindre à ces derniers
	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") and not onlygeneral then --"à proximité de"
				str = wd.translate('approximate-place', str)
			elseif (v == "Q18122778") or (v == "Q18912752") or (v == "Q56644435") or (v == "Q30230067") then --"présumé", "controversé", "probablement", "possible"
				str = wd.translate('uncertain-information', str)
			elseif (v == "Q5727902") and not onlygeneral and statement.mainsnak.datatype == 'time' then --date approximative
				local datevalue = statement.mainsnak.datavalue
				if datevalue then str = fuzzydate(str, datevalue.value.precision) end
			end
		end
	end
	return str
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 fâre dens cél câs ?
		return nil
	end
	return modules.formatDate.dateObject(v.datavalue.value)
end

function wd.getDate(statement)
	local period = wd.getDateFromQualif(statement, 'P585') -- retôrne 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 modules.formatDate.rangeObject(begin, ending) -- retôrne un rangeobject fêt de doux dateobject
	end
	return nil
end

function wd.getFormattedDate(statement, params)
	if not statement then
		return nil
	end
	local str

	--chèrche la dâta avouéc los qualifs P580/P582
	local datetable = wd.getDate(statement)
	if datetable then
		str = modules.formatDate.objectToText(datetable, params)
	end

	-- et pués limita de dedens / d’en-dessus
	if not str then
		local start, ending = wd.getDateFromQualif(statement, 'P1319'), wd.getDateFromQualif(statement, 'P1326')
		str = modules.formatDate.between(start, ending, params)
	end

	local fromqualif = false
	if str then fromqualif = true end --si la date est tirée des qualificateurs, on n'y ajoute pas l'éventuel "vers ..."

	 -- ôtrament, lo mainsnak, por les balyês de tipo 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, fromqualif)
	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.alphabetSort(claims)
	local function sort(c1, c2)
		local v1 = wd.getDataValue(c1.mainsnak, {link = '-', displayformat = {monolingualtext = 'raw'}})
		local v2 = wd.getDataValue(c2.mainsnak, {link = '-', displayformat = {monolingualtext = 'raw'}})
		if not (v1 and v2) then
			return true
		end
		return modules.linguistic.makeSortkey(v1) < modules.linguistic.makeSortkey(v2)
	end
	table.sort(claims, sort )
	return claims
end

function wd.compare.get_claim_date(claim, datetype) -- rend une date au format numérique pour faire des comparaisons
	local snak = claim.mainsnak or claim

	if datetype and datetype == 'personbirthdate' then -- fonctionne avec un claim dont la valeur est une personne dont on va rendre la date de naissance
		if (snak.snaktype == 'value') and (snak.datatype == 'wikibase-item') then
			local personid = wd.getId(snak)
			local birthclaims = wd.getClaims({ entity = personid, property = 'P569', numval = 1})
			if birthclaims then
				return wd.compare.get_claim_date(birthclaims[1] or birthclaims)
			else return math.huge end
		else return math.huge end -- en cas de donnée manquante, valeur infinie qui entraîne le classement en fin de liste
	end

	local iso, datequalif, isonumber
	if (snak.snaktype == 'value') and (snak.datatype == 'time') then
		iso = snak.datavalue.value.time
	else
		for i, dqualif in ipairs(datequalifiers) do
			iso = timeFromQualifs(claim, {dqualif})
			if iso then
				datequalif = dqualif
				break
			end
		end
		if not iso then return math.huge end
	end
	-- transformation en nombre (indication de la base car gsub retourne deux valeurs)
	isonumber = tonumber( iso:gsub( '(%d)%D', '%1' ), 10 )

	-- ajustement de la date tenant compte du qualificatif dont elle est issue : un fait se terminant à une date est antérieur à un autre commençant à cette date
	if datequalif == 'P582' then --date de fin
		isonumber = isonumber - 2
	elseif datequalif == 'P1326' then -- date au plus tard
		isonumber = isonumber - 1
	elseif datequalif == 'P1319' then -- date au plus tôt
		isonumber = isonumber + 1
	elseif datequalif == 'P571' or datequalif == 'P580' then -- date de début et date de création
		isonumber = isonumber + 2
	end

	return isonumber
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, sorttype )
	for _, claim in ipairs( claims ) do
		if not claim.dateSortKey then
			if sorttype and (sorttype == 'age' or sorttype == 'ageinverted') then
				claim.dateSortKey = wd.compare.get_claim_date(claim, 'personbirthdate')
			else
				claim.dateSortKey = wd.compare.get_claim_date(claim)
			end
			if sorttype and (sorttype == 'inverted' or sorttype == 'ageinverted') and claim.dateSortKey == math.huge then
				claim.dateSortKey = -math.huge -- quand la donnée est manquante on lui assigne la valeur qui entraîne le classement en fin de liste
			end
		end
	end
	table.sort(
		claims,
		function ( c1, c2 )
			if sorttype and (sorttype == 'inverted' or sorttype == 'ageinverted') then
				return c2.dateSortKey < c1.dateSortKey
			end
			return c1.dateSortKey < c2.dateSortKey
		end
	)
	return claims
end

-- Function to prioritize certain languages in a multilingual label context
-- prioritize french, mul, roman languages, english if not all values needed
function wd.sortByLanguage(claims)
	-- Arbitrary order for now, ask to make it a parameter if needed
	local priority = { 
		fr=1, mul=2, ["en-gb"]=3, en=4, es=5, it=6, de=7 
	}
	-- maybe something to generalize a bit :
	-- local wikilang = mw.getContentLanguage()
	-- local priority = wikilang:getFallbackLanguages()
	-- priority.insert(priority, 1, wikilang.code)
		
	local function get_priority(lang)
		local attempt = priority[lang]
		if attempt ~= nil then
			return attempt
		else
			return math.huge
		end
	end
	local function get_snak_value(mainsnak)
		if mainsnak.datavalue and 
			mainsnak.datavalue.value then
			return mainsnak.datavalue.value
		end
	end
	-- get the main val as an object
	local function get_main_value(claim)
		if claim.mainsnak then 
			return get_snak_value(claim.mainsnak)
		end
	end
	
	local function sortfunction(v1, v2)
		-- get a value if they are statements or qualifiers as well
		local str1 = get_main_value(v1) or get_snak_value(v1) 
		local str2 = get_main_value(v2) or get_snak_value(v2)

		if str1 ~= nil and str2	~= nil then
			local prio1 = get_priority(str1.language)
			local prio2 = get_priority(str2.language)
			
			return prio1 < prio2
		elseif str1 then
			return true
		else 
			return false
		end
		
	end

	table.sort(claims, sortfunction)
	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, {['pâge'] = page, ['accessdate'] = accessdate, ['citacion'] = 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 = 'desot lo 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.limVouebe{titro = title, url = url, otor = author, editor = publisher, lengoua = publishlang, ['en legne lo'] = publishdate, ['viu lo'] = accessdate, ['citacion'] = quotation, ['dèscripcion'] = 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, '<sup class="reference cite_virgula">,</sup>')
end

function wd.getDataValue(snak, params)
	if not params then
		params = {}
	end
	local speciallabels = params.speciallabels -- parfois on a besoin de faire une liste d'éléments pour lequel le libellé doit être changé, pas très pratique d'utiliser une fonction pour ça

	if snak.snaktype ~= 'value' then
		return nil
	end

	local datatype = snak.datatype
	local value = snak.datavalue.value

	local displayformat = params.displayformat
	if type(displayformat) == 'table' then
		displayformat = displayformat[datatype]
	end
	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 = modules.formatDate.dateObject(value, {precision = params.precision})
			return modules.formatDate.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 = databases.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 or displayformat == 'raw' 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 {}
	
	-- Sorting to prioritize languages
	if(params.showonlyqualifier ~= nil) then
		qualiftable = wd.sortClaims(qualiftable, params.sorttype or params.qualifsorttype)
	end
	
	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
	
	if args.qualiflabelformat == 'objectgender' then
		local objectid = wd.getId(statement.mainsnak)
		qualifargs.labelformat = wd.getgender(objectid)
	end

	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, true)
		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)
		if indicateur then
			str = indicateur .. '&nbsp;' .. 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, already added by wd.formatStatement
		if period then
			str = str .. " <small>(" .. period .. ")</small>"
		end
	end

	if args.showsource and args.showsource ~= '-' and args.showsource ~= "false" then
		if args.showsource == "only" then str="" end -- si showsource="only", alors ne montrer que la (les) source(s),
		                                             -- sans la valeur qui, auparavant, était enregistrée dans str
		                                             -- Utilisé par le modèle {{PH census}}
		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 = mw.text.trim(wd.entityId(id))

	local class = ''
	if property then
		class = 'wd_' .. string.lower(property)
	end
	local icon = '[[Fichiér:Blue pencil.svg|%s|10px|baseline|class=noviewer|link=%s]]'
	local title = wd.translate('see-wikidata-value')
	local url = mw.uri.fullUrl('d:' .. id, 'uselang=frp')
	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 skin-invert')
			: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', "ovra")
			: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 .. " <small>(" .. datestr .. ")</small>"
			end
			return str
			end
		},
		{param = "showsource", fun = function(str, claims)
			-- les sources sont toutes affichées au même endroit, à la fin
			-- si deux affirmations ont la même source, on ne l'affiche qu'une fois
			local sources = {}
			local hashes = {}

			local function dupeRef(old, new)
				for i, j in pairs(old) do
					if j == new then
						return true
					end
				end
			end
			for i, claim in pairs(claims) do
				local refs, refHashes = wd.getReferences(claim)
				if refs then
					for i, j in pairs(refs) do
						if not dupeRef(sources, j) then
							table.insert(sources, j)
							local hash = (refHashes and refHashes[i]) or '-'
							table.insert(hashes, hash)
						end
					end
				end
			end
			return str .. (wd.sourceStr(sources, hashes) or "")
			end
		}
	}

	for i, group in pairs(groupedClaims) do -- bricolage pour utiliser les arguments de formatStatements
		local str = wd.formatEntity(group.id, args)
		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à formatées
	local props -- les propriétés réellement utilisées (dans certains cas, ce ne sont pas toutes celles de args.property
	local cat = ''
	if not valuetable then -- cas le plus courant
		valuetable, props, cat = wd.stringTable(args)
	end

	if args.ucfirst == '-' and args.conjtype == 'new line' then args.conjtype = 'lowercase new line' 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

-- ==== Fonctions sur le genre ====

function wd.getgender(id)
	local vals = {
		['Q6581072'] = 'f', -- féminin
		['Q6581097'] = 'm', -- masculin
		['Q1052281'] = 'f', -- femme transgenre
		['Q2449503'] = 'm', -- homme transgenre
		['Q17148251'] = 'f', -- en:Travesti (gender identity)
		['Q43445'] = 'f', -- femelle
		['Q44148'] = 'm', -- mâle
		default      = '?'
	}
	local gender = wd.formatStatements{entity = id, property = 'P21', displayformat = 'raw', numval = 1}
	return vals[gender] or vals.default
end

wd.isGender = {m = true, male = true, f = true, female = true} --reconnaissance des chaînes de caractères désignant un genre

-- catégories de genre/nombre
function wd.getgendernum(claims)
	local personid, gender
	local anym = false
	local anyf = false
	local anyunknown = false

	for i, claim in pairs(claims) do
		local snak = claim.mainsnak or claim
		if(snak.snaktype == 'value') and (snak.datatype == 'wikibase-item') then
			personid = wd.getId(snak)
			gender = wd.getgender(personid)
			anym = anym or (gender == 'm')
			anyf = anyf or (gender == 'f')
			anyunknown = anyunknown or (gender == '?')
		else
			anyunknown = true
		end
	end

	local gendernum
	if #claims > 1 then
		if anyunknown then
			gendernum = 'p'
		else
			if anym and not anyf then gendernum = 'mp' end
			if anyf and not anym then gendernum = 'fp' end
			if anym and anyf then gendernum = 'mixtep' end
		end
	else
		gendernum = 's'
		if anym then gendernum = 'ms' end
		if anyf then gendernum = 'fs' end
	end

	return gendernum
end


-- récupération des libellés genrés de Wikidata
function wd.genderedlabel(id, labelgender)
	local label
	if not labelgender then return nil end
	if labelgender == 'f' or labelgender == 'female' then -- femme : chercher le libellé dans P2521 (libellé féminin)
		label = wd.formatStatements{entity = id, property = 'P2521', isinlang = 'frp', numval = 1, ucfirst = '-'}
	elseif labelgender == 'm' or labelgender == 'male' then -- homme : chercher le libellé dans P3321 (libellé masculin)
		label = wd.formatStatements{entity = id, property = 'P3321', isinlang = 'frp', numval = 1, ucfirst = '-'}
	end
	if not label then
		label = wd.getLabel(id)
	end
	return label
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 "<pre>"..mw.dumpObject(entity).."</pre>"
end

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


return wd