La documentación para este módulo puede ser creada en Módulo:Wikidata/doc

-- version 20200615 from master @cawiki
-- version 2 edited by katariq --->
local wd = {}

-- Initialization of variables --------------------

local i18n = {						-- internationalisation at [[Module:Wikidata/i18n]]
	["errors"] = {
		["property-not-found"] = "Property not found.",
		["qualifier-not-found"] = "Qualifier not found.",
	},
	
	["datetime"] = {
		-- $1 is a placeholder for the actual number
		["beforenow"] = "$1 BCE",	-- how to format negative numbers for precisions 0 to 5
		["afternow"] = "$1 CE",		-- how to format positive numbers for precisions 0 to 5
		["bc"] = "$1 BCE",			-- how print negative years
		["ad"] = "$1",				-- how print 1st century AD dates
		
		[0] = "$1 billion years",	-- precision: billion years
		[1] = "$100 million years",	-- precision: hundred million years
		[2] = "$10 million years",	-- precision: ten million years
		[3] = "$1 million years",	-- precision: million years
		[4] = "$100000 years",		-- precision: hundred thousand years; thousand separators added afterwards
		[5] = "$10000 years",		-- precision: ten thousand years; thousand separators added afterwards
		[6] = "$1 millennium",		-- precision: millennium
		[7] = "$1 century",			-- precision: century
		[8] = "$1s",				-- precision: decade
		-- the following use the format of #time parser function
		[9] = "Y",					-- precision: year, 
		[10] = "F Y",				-- precision: month
		[11] = "F j, Y",			-- precision: day
		
		["hms"] = {["hours"] = "h", ["minutes"] = "m", ["seconds"] = "s"},	-- duration: xh xm xs
	},
	
	["years-old"] = {
		["singular"] = "",			-- year old, as in {{PLURAL:$1|singular|plural}}
		["plural"] = "",			-- years old
		["paucal"] = "",			-- for languages with 3 plural forms as in {{PLURAL:$1|singular|paucal|plural}}
	},
	
	["cite"] = {					-- cite parameters
		["title"] = "title",
		["author"] = "author",
		["date"] = "date",
		["pages"] = "pages",
		["language"] = "language",
		-- cite web parameters
		["url"] = "url",
		["website"] = "website",
		["access-date"] = "access-date",
		["archive-url"] = "archive-url",
		["archive-date"] = "archive-date",
		["publisher"] = "publisher",
		["quote"] = "quote",
		-- cite journal parameters
		["work"] = "work",
		["issue"] = "issue",
		["issn"] = "issn",
		["doi"] = "doi"
	},
	
	-- local wiki settings
	["addpencil"] = false, -- adds a pencil icon linked to Wikidata statement, planned to overwrite by Wikidata Bridge
	["categorylabels"] = "", -- Category:Pages with Wikidata labels not translated (void for no local category)
	["addfallback"] = {} -- additional fallback language codes
}

local cases = {} -- functions for local grammatical cases defined at [[Module:Wikidata/i18n]]

local required = ... -- variadic arguments from require function
local wiki = 
{
	langcode = mw.language.getContentLanguage().code,
	module_title = required or mw.getCurrentFrame():getTitle()
}

local untranslated -- used in infobox modules: nil or true
local _ -- variable for unused returned values, avoiding globals

-- Module local functions --------------------------------------------

-- Credit to http://stackoverflow.com/a/1283608/2644759, cc-by-sa 3.0
local function tableMerge(t1, t2)
	for k, v in pairs(t2) do
		if type(v) == "table" then
			if type(t1[k] or false) == "table" then
				tableMerge(t1[k] or {}, t2[k] or {})
			else
				t1[k] = v
			end
		else
			t1[k] = v
		end
	end
	return t1
end

local function loadI18n(lang)
	local exist, res = pcall(require, wiki.module_title .. "/i18n")
	if exist and next(res) ~= nil then
		tableMerge(i18n, res.i18n)
		cases = res.cases
	end
	if lang ~= wiki.langcode then
		exist, res = pcall(require, wiki.module_title .. "/i18n/" .. lang)
		if exist and next(res) ~= nil then
			tableMerge(i18n, res.i18n)
			tableMerge(cases, res.cases)
		end
	end
end

-- Table of language codes: requested or default and its fallbacks
local function findLang(langcode)
	if mw.language.isKnownLanguageTag(langcode or '') == false then
		local cframe = mw.getCurrentFrame()
		local pframe = cframe:getParent()
		langcode = pframe and pframe.args.lang
		if mw.language.isKnownLanguageTag(langcode or '') == false then
			if not mw.title.getCurrentTitle().isContentPage then
				langcode = cframe:preprocess('{{int:lang}}')
			end
			if mw.language.isKnownLanguageTag(langcode or '') == false then
				langcode = wiki.langcode
			end
		end
	end
	
	loadI18n(langcode)
	
	local languages = mw.language.getFallbacksFor(langcode)
	table.insert(languages, 1, langcode)
	if langcode == wiki.langcode then
		for _, l in ipairs(i18n.addfallback) do
			table.insert(languages, l)
		end
	end
	
	return languages
end

-- Argument is 'set' when it exists (not nil) or when it is not an empty string.
local function isSet(var)
	return not (var == nil or var == '')
end

-- Set local case to a label
local function case(localcase, label, ...)
	if not isSet(label) then return label end
	
	if localcase == "smallcaps" then
		return '<span style="font-variant: small-caps;">' .. label .. '</span>'
	elseif cases[localcase] then
		return cases[localcase](label, ...)
	end
	
	return label
end

-- get safely a serialized snak
local function getSnak(statement, snaks)
	local ret = statement
	for i, v in ipairs(snaks) do
		if not ret then return end
		ret = ret[v]
	end
	return ret
end

-- mw.wikibase.getLabelWithLang or getLabelByLang with a table of languages
local function getLabelByLangs(id, languages)
	local label
	local lang
	for _, l in ipairs(languages) do
		if l == wiki.langcode then
			-- using getLabelWithLang when possible instead of getLabelByLang, do not solve redirects pending phab:T157868
			label, l = mw.wikibase.getLabelWithLang(id)
		else
			label = mw.wikibase.getLabelByLang(id, l)
		end
		if label then
			lang = l
			break
		end
	end
	return label, lang
end

-- getBestStatements if bestrank=true, else getAllStatements with no deprecated
local function getStatements(entityId, property, bestrank)
	local claims = {}
	if not (entityId and mw.ustring.match(property, "^P%d+$")) then return claims end
	if bestrank then
		claims = mw.wikibase.getBestStatements(entityId, property)
	else
		local allclaims = mw.wikibase.getAllStatements(entityId, property)
		for _, c in ipairs(allclaims) do
			if c.rank ~= "deprecated" then
				table.insert(claims, c)
			end
		end
	end
	return claims
end

-- Is gender femenine? true or false
local function feminineGender(id)
	local claims = mw.wikibase.getBestStatements(id or mw.wikibase.getEntityIdForCurrentPage(),'P21')
	if getSnak(claims, {1, "mainsnak", "datavalue"}) == nil then -- no claim, novalue or somevalue
		return false
	else
		local genderId = claims[1].mainsnak.datavalue.value.id
		if genderId == "Q6581072" or genderId == "Q1052281" or genderId == "Q43445" then -- female, transgender female, female organism
			return true
		end
	end
	return false
end

-- Fetch female form of label
local function feminineForm(id, lang)
	local feminine_claims = getStatements(id, 'P2521')
	for _, feminine_claim in ipairs(feminine_claims) do
		if getSnak(feminine_claim, {'mainsnak', 'datavalue', 'value', 'language'}) == lang then
			return feminine_claim.mainsnak.datavalue.value.text
		end
	end
end

-- Add an icon for no label in requested language
local function addLabelIcon(label_id, lang, uselang, icon)
	local ret_lang, ret_icon = '', ''
	if icon then
		if lang and lang ~= uselang then
			ret_lang = " <sup>(" .. lang .. ")</sup>"
		end
		if label_id and (lang == nil or lang ~= uselang) then
			ret_icon = " [[File:Noun Project label icon 1116097 cc mirror.svg|10px|baseline|"
				.. mw.message.new('Translate-taction-translate'):inLanguage(uselang):plain()
				.. "|link=https://www.wikidata.org/wiki/Special:EntityPage/" .. label_id .. "?uselang=" .. uselang .. "]]"
			untranslated = true
		end
		if isSet(i18n.categorylabels) and lang ~= uselang and uselang == wiki.langcode then
			ret_icon = ret_icon .. '[[' .. i18n.categorylabels .. (lang and ']]' or '/Q]]')
		end
	end
	return ret_lang .. ret_icon
end

-- Add an icon for editing a statement with requirements for Wikidata Bridge
local function addEditIcon(parameters)
	local ret = ''
	if i18n.addpencil and parameters.editicon and parameters.id and parameters.property then
		ret = ' <span data-bridge-edit-flow="single-best-value">'
			.. "[[File:Arbcom ru editing.svg|10px|baseline|"
			.. mw.message.new('Editlink'):inLanguage(parameters.lang[1]):plain()
			.. "|link=https://www.wikidata.org/wiki/" .. parameters.id .. "?uselang=" .. parameters.lang[1] .. "#" .. parameters.property .. "]]"
			.. "</span>"
	end
	return ret
end
-- add edit icon to the last element of a table
local function addEditIconTable(thetable, parameters)
	if #thetable == 0 or not i18n.addpencil then
		return thetable
	end
	local last_element = thetable[#thetable]
	local the_icon = addEditIcon(parameters)
	-- add it before last html closing tags
	local tags = ''
	local rev_element = string.reverse(last_element)
	for tag in string.gmatch(rev_element, '(>%l+/<)') do
		if string.match(rev_element, '^' .. tags .. tag) then
			tags = tags .. tag
		else
			break
		end
	end
	local last_tags = string.reverse(tags)
	local offset = string.find(last_element, last_tags .. '$')
	if offset then
		thetable[#thetable] = string.sub(last_element, 1, offset - 1) .. the_icon .. last_tags
	else
		thetable[#thetable] = last_element .. the_icon
	end
	return thetable
end

-- Escape Lua captures
local function captureEscapes(text)
	return mw.ustring.gsub(text, "(%%%d)", "%%%1")
end

-- expandTemplate or callParserFunction
local function expandBraces(text, formatting)
	if text == nil or formatting == nil then return text end
	-- only expand braces if provided in argument, not included in value as in Q1164668
	if mw.ustring.find(formatting, '{{', 1, true) == nil then return text end
	if type(text) ~= "string" then
		text = tostring(text)
	end
	
	for braces in mw.ustring.gmatch(text, "{{(.-)}}") do
		local parts = mw.text.split(braces, "|")
		local title_part = parts[1]
		local parameters = {}
		for i = 2, #parts do
			local subparts = mw.ustring.find(parts[i], "=")
			if subparts then
				parameters[mw.ustring.sub(parts[i], 1, subparts-1)] = mw.ustring.sub(parts[i], subparts+1, -1)
			else
				table.insert(parameters, parts[i])
			end
		end
		
		local braces_expanded
		if mw.ustring.find(title_part, ":")
			and mw.text.split(title_part, ":")[1] ~= mw.site.namespaces[10].name -- not a prefix Template:
			then
			braces_expanded = mw.getCurrentFrame():callParserFunction{name=title_part, args=parameters}
		else
			braces_expanded = mw.getCurrentFrame():expandTemplate{title=title_part, args=parameters}
		end
		braces = mw.ustring.gsub(braces, "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- escape magic characters
		braces_expanded = captureEscapes(braces_expanded)
		text = mw.ustring.gsub(text, "{{" .. braces .. "}}", braces_expanded)
	end
	
	return text
end

-- Resolve Wikidata redirects, pending phab:T157868
local function resolveEntityId(id)
	if not id or not mw.wikibase.isValidEntityId(id) then return id end
	-- if no label in English, maybe it is a redirect
	-- not using mw.title.new(id).isRedirect as it is expensive
	-- currently getLabelByLang does not follows redirects
	if mw.wikibase.getLabelByLang(id, 'en') == nil then
		local entity = mw.wikibase.getEntity(id) -- expensive function
		if not entity then return nil end
		if id ~= entity.id then
			-- Qid redirected to be fixed
			-- see [[Special:WhatLinksHere/Template:Track/wikidata/redirect]]
			require(wiki.module_title .. '/debug').track('redirect')
			require(wiki.module_title .. '/debug').track('redirect/' .. id)
		else
			-- no redirect and no English label, fix it to avoid expensive functions
			require(wiki.module_title .. '/debug').track('label')
			require(wiki.module_title .. '/debug').track('label/' .. id)
		end
		return entity.id
	end
	return id
end

-- format data type math
local function printDatatypeMath(data)
	return mw.getCurrentFrame():callParserFunction('#tag:math', data)
end

-- format data type musical-notation
local function printDatatypeMusical(data, formatting)
	local attr = {}
	if formatting == 'sound' then
		attr.sound = 1
	end
	return mw.getCurrentFrame():extensionTag('score', data, attr)
end

-- format data type string
local function printDatatypeString(data, parameters)
	if mw.ustring.find((parameters.formatting or ''), '$1', 1, true) then -- formatting = a pattern
		return expandBraces(mw.ustring.gsub(parameters.formatting, '$1', {['$1'] = data}), parameters.formatting)
	elseif parameters.case then
		return case(parameters.case, data, parameters.lang[1])
	end
	local data_number = string.match(data, "^%d+")
	if data_number then -- sort key by initial number and remaining string
		local sortkey = string.format("%019d", data_number * 1000)
		return data, sortkey .. string.sub(data, #data_number + 1)
	end
	return data
end

-- format data type url
local function printDatatypeUrl(data, parameters)
	if parameters.formatting == 'weblink' then
		local label_parts = mw.text.split(string.gsub(data, '/$', ''), '/')
		local label = string.gsub(label_parts[3], '^www%.', '')
		if #label_parts > 3 then
			label = label .. '…'
		end
		return '[' .. data .. ' ' .. label .. ']'
	end
	return printDatatypeString(data, parameters)
end

-- format data type external-id
local function printDatatypeExternal(data, parameters)
	if parameters.formatting == 'externalid' then
		local p_stat = mw.wikibase.getBestStatements(parameters.property, 'P1630') -- formatter URL
		local p_link_pattern = getSnak(p_stat, {1, "mainsnak", "datavalue", "value"})
		if p_link_pattern then
			local p_link = mw.ustring.gsub(p_link_pattern, '$1', {['$1'] = data})
			return '[' .. p_link .. ' ' .. data .. ']'
		end
	end
	return printDatatypeString(data, parameters)
end

-- format data type commonsMedia
local function printDatatypeMedia(data, parameters)
	local icon
	if not string.find((parameters.formatting or ''), '$1', 1, true) then
		icon = "no-icon"
	end
	return printDatatypeString(data, parameters), icon
end

-- format data type globe-coordinate
local function printDatatypeCoordinate(data, formatting)
	local function globes(globe_id)
		local globes = {['Q3134']='callisto',['Q596']='ceres',['Q15040']='dione',['Q2']='earth',['Q3303']='enceladus',
			['Q3143']='europa',['Q17975']='phoebe',['Q3169']='ganymede',['Q3123']='io',['Q17958']='iapetus',
			['Q308']='mercury',['Q15034']='mimas',['Q405']='moon',['Q15050']='rhea',['Q15047']='tethys',
			['Q111']='mars',['Q2565']='titan',['Q3359']='triton',['Q313']='venus',['Q3030']='vesta'}
		return globes[globe_id]
	end
	
	local function roundPrecision(num, prec)
		if prec == nil or prec <= 0 then return num end
		local sig = 10^math.floor(math.log10(prec)+.5) -- significant figure from sexagesimal precision: 0.00123 -> 0.001
		return math.floor(num / sig + 0.5) * sig
	end
	
	local precision = data.precision
	local latitude = roundPrecision(data.latitude, precision)
	local longitude = roundPrecision(data.longitude, precision)
	if formatting and string.find(formatting, '$lat', 1, true) and string.find(formatting, '$lon', 1, true) then
		local ret = mw.ustring.gsub(formatting, '$l[ao][tn]', {['$lat'] = latitude, ['$lon'] = longitude})
		if string.find(formatting, '$globe', 1, true) then
			local myglobe = 'earth'
			if isSet(data.globe) then
				local globenum = mw.text.split(data.globe, 'entity/')[2] -- http://www.wikidata.org/wiki/Q2
				myglobe = globes(globenum) or 'earth'
			end
			ret = mw.ustring.gsub(ret, '$globe', myglobe)
		end
		return expandBraces(ret, formatting)
	elseif formatting == 'latitude' then
		return latitude, "no-icon"
	elseif formatting == 'longitude' then
		return longitude, "no-icon"
	elseif formatting == 'dimension' then
		return data.dimension, "no-icon"
	else --default formatting='globe'
		if isSet(data.globe) == false or data.globe == 'http://www.wikidata.org/entity/Q2' then
			return 'earth', "no-icon"
		else
			local globenum = mw.text.split(data.globe, 'entity/')[2]
			return globes(globenum) or globenum, "no-icon"
		end
	end
end

-- Local functions for data value quantity
local function unitSymbol(id, lang) -- get unit symbol or code
	local unit_symbol = ''
	if lang == wiki.langcode and pcall(require, wiki.module_title .. "/Units") then
		unit_symbol = require(wiki.module_title .. "/Units").getUnit(0, '', id, true)
	end
	if unit_symbol == '' then
		-- fetch it
		local claims = mw.wikibase.getBestStatements(id, 'P5061')
		if #claims > 0 then
			local langclaims = {}
			table.insert(lang, 'mul') -- multilingual as last try
			for _, snak in ipairs(claims) do
				local snak_language = getSnak(snak, {"mainsnak", "datavalue", "value", "language"})
				if snak_language and not langclaims[snak_language] then -- just the first one by language
					langclaims[snak_language] = snak.mainsnak.datavalue.value.text
				end
			end
			for _, l in ipairs(lang) do
				if langclaims[l] then
					return langclaims[l]
				end
			end
		end
	end
	return unit_symbol
end

local function getUnit(amount, id, parameters) -- get unit symbol or name
	local suffix = ''
	if parameters.formatting == "unitcode" then
		-- get unit symbol
		local unit_symbol = unitSymbol(id, parameters.lang)
		if isSet(unit_symbol) then
			suffix = unit_symbol
		end
	end
	if suffix == '' then -- formatting=unit, or formatting=unitcode not found
		-- get unit name
		local unit_label, lang = getLabelByLangs(id, parameters.lang)
		if lang == wiki.langcode and pcall(require, wiki.module_title .. "/Units") then
			suffix = require(wiki.module_title .. "/Units").getUnit(amount, unit_label, id, false)
		else
			suffix = (unit_label or id) .. addLabelIcon(id, lang, parameters.lang[1], parameters.editicon)
		end
	end
	if suffix ~= '' then
		suffix = ' ' .. suffix
	end
	return suffix
end

local function roundDefPrecision(in_num, factor)
	-- rounds out_num with significant figures of in_num (default precision)
	local out_num = in_num * factor
	if factor/60 == math.floor(factor/60) then -- sexagesimal integer
		return out_num
	end
	-- first, count digits after decimal mark, handling cases like '12.345e6'
	local exponent, prec
	local integer, dot, decimals, expstr = in_num:match('^(%d*)(%.?)(%d*)(.*)')
	local e = expstr:sub(1, 1)
	if e == 'e' or e == 'E' then
		exponent = tonumber(expstr:sub(2))
	end
	if dot == '' then
		prec = -integer:match('0*$'):len()
	else
		prec = #decimals
	end
	if exponent then
		-- So '1230' and '1.23e3' both give prec = -1, and '0.00123' and '1.23e-3' give 5.
		prec = prec - exponent
	end
	-- significant figures
	local in_bracket = 10^-prec -- -1 -> 10, 5 -> 0.00001
	local out_bracket = in_bracket * out_num / in_num
	out_bracket = 10^math.floor(math.log10(out_bracket)+.5) -- 1230 -> 1000, 0.00123 -> 0.001
	-- round it (credit to Luc Bloom from http://lua-users.org/wiki/SimpleRound)
	return math.floor(out_num/out_bracket + (out_num >=0 and 1 or -1) * 0.5) * out_bracket
end

-- format data type quantity
local function printDatatypeQuantity(data, parameters)
	local amount = data.amount
	amount = mw.ustring.gsub(amount, "%+", "")
	local suffix = ""
	local conv_amount, conv_suffix
	if string.sub(parameters.formatting or '', 1, 4) == "unit" or string.sub(parameters.formatting or '', 1, 8) == "duration" or parameters.convert then
		local unit_id = data.unit
		unit_id = mw.ustring.sub(unit_id, mw.ustring.find(unit_id, "Q"), -1)
		if string.sub(unit_id, 1, 1) == "Q" then
			suffix = getUnit(amount, unit_id, parameters)
			local convert_to
			if parameters.convert == "default" or parameters.convert == "default2" then
				local exist, units = pcall(require, wiki.module_title .. "/Units")
				if exist and next(units.convert_default) ~= nil then
					convert_to = units.convert_default[unit_id]
				end
			elseif string.sub(parameters.convert or '', 1, 1) == "Q" then
				convert_to = resolveEntityId(parameters.convert)
			elseif string.sub(parameters.formatting or '', 1, 8) == "duration" then
				convert_to = 'Q11574' -- seconds
			end
			if convert_to and convert_to ~= unit_id then
				-- convert units
				local conv_temp = { -- formulae for temperatures ºC, ºF, ªK: [from] = {[to] = 'formula'}
					['Q25267'] = {['Q42289'] = '$1*1.8+32', ['Q11597'] = '$1+273.15'},
					['Q42289'] = {['Q25267'] = '($1-32)/1.8', ['Q11597'] = '($1+459.67)*5/9'},
					['Q11597'] = {['Q25267'] = '$1-273.15', ['Q42289'] = '($1-273.15)*1.8000+32.00'}
				}
				if conv_temp[unit_id] and conv_temp[unit_id][convert_to] then
					local amount_f = mw.getCurrentFrame():callParserFunction('#expr', mw.ustring.gsub(conv_temp[unit_id][convert_to], "$1", amount))
					conv_amount = math.floor(tonumber(amount_f) + 0.5)
				else
					local conversions = getStatements(unit_id, 'P2442') -- conversion to standard unit
					table.insert(conversions, mw.wikibase.getBestStatements(unit_id, 'P2370')[1]) -- conversion to SI unit
					for _, conv in ipairs(conversions) do
						if conv.mainsnak.snaktype == 'value' then -- no somevalue nor novalue
							if conv.mainsnak.datavalue.value.unit == "http://www.wikidata.org/entity/" .. convert_to then
								conv_amount = roundDefPrecision(amount, tonumber(conv.mainsnak.datavalue.value.amount))
								break
							end
						end
					end
				end
				if conv_amount then
					conv_suffix = getUnit(conv_amount, convert_to, parameters)
				end
			elseif parameters.convert == 'M' and tonumber(amount) > 10^8 then
				conv_amount = math.floor(amount/10^6 + 0.5)
				conv_suffix = ' M' .. string.sub(suffix, 2)
			end
		end
	end
	local lang_obj = mw.language.new(parameters.lang[1])
	local sortkey = string.format("%019d", tonumber(amount) * 1000)
	if string.sub(parameters.formatting or '', 1, 8) == "duration" then
		local sec = tonumber(conv_amount or amount)
		if parameters.formatting == 'durationhms' or parameters.formatting == 'durationh:m:s' then
			local intervals = {"hours", "minutes", "seconds"}
			local sec2table = lang_obj:getDurationIntervals(sec, intervals)
			sec2table["seconds"] = (sec2table["seconds"] or 0) + tonumber("." .. (tostring(sec):match("%.(%d+)") or "0")) -- add decimals
			local duration = ''
			for i, v in ipairs(intervals) do
				if parameters.formatting == 'durationh:m:s' then
					if i == 1 and sec2table[v] then
						duration = duration .. sec2table[v] .. ":"
					elseif i == 2 then
						duration = duration .. string.format("%02d", sec2table[v] or 0) .. ":"
					elseif i == 3 then
						local sec_str = tostring(lang_obj:formatNum(sec2table[v] or 0))
						duration = duration .. (sec2table[v] < 10 and "0" or "") .. sec_str
					end
				elseif sec2table[v] then
					duration = duration .. lang_obj:formatNum(sec2table[v]) .. i18n.datetime.hms[v] .. (i < 3 and " " or "")
				end
			end
			return duration
		else
			return lang_obj:formatDuration(sec)
		end
	end
	if parameters.case then
		amount = case(parameters.case, amount, parameters.lang[1])
	else
		amount = lang_obj:formatNum(tonumber(amount))
	end
	if conv_amount then
		local conv_sortkey = string.format("%019d", tonumber(conv_amount) * 1000)
		conv_amount = lang_obj:formatNum(tonumber(conv_amount))
		if parameters.convert == 'default2' then
			return conv_amount .. conv_suffix .. ' (' .. amount .. suffix .. ')', conv_sortkey
		else
			return conv_amount .. conv_suffix, conv_sortkey
		end
	end
	return amount .. suffix, sortkey
end

-- format data type time
local function printDatatypeTime(data, parameters)
	-- Dates and times are stored in ISO 8601 format
	local timestamp = data.time
	local post_format
	local calendar_add = ""
	local precision = data.precision or 11
	
	if string.sub(timestamp, 1, 1) == '-' then
		post_format = i18n.datetime["bc"]
	elseif string.sub(timestamp, 2, 3) == '00' then
		post_format = i18n.datetime["ad"]
	elseif precision > 8 then
		-- calendar model
		local calendar_model = {["Q12138"] = "gregorian", ["Q1985727"] = "gregorian", ["Q11184"] = "julian", ["Q1985786"] = "julian"}
		local calendar_id = mw.text.split(data.calendarmodel, 'entity/')[2]
		if (timestamp < "+1582-10-15T00:00:00Z" and calendar_model[calendar_id] == "gregorian")
			or (timestamp > "+1582-10-04T00:00:00Z" and calendar_model[calendar_id] == "julian")
			then
			calendar_add = " <sup>(" .. mw.message.new('Wikibase-time-calendar-' .. calendar_model[calendar_id]):inLanguage(parameters.lang[1]):plain() .. ")</sup>"
		end
	end
	
	local function formatTime(form, stamp)
		local pattern
		if type(form) == "function" then
			pattern = form(stamp)
		else
			pattern = form
		end
		stamp = tostring(stamp)
		if mw.ustring.find(pattern, "$1") then
			return mw.ustring.gsub(pattern, "$1", stamp)
		elseif string.sub(stamp, 1, 1) == '-' then -- formatDate() only supports years from 0
			stamp = '+' .. string.sub(stamp, 2)
		elseif string.sub(stamp, 1, 1) ~= '+' then -- not a valid timestamp, it is a number
			stamp = string.format("%04d", stamp)
		end
		local ret = mw.language.new(parameters.lang[1]):formatDate(pattern, stamp)
		ret = string.gsub(ret, "^(%[?%[?)0+", "%1") -- supress leading zeros
		ret = string.gsub(ret, "( %[?%[?)0+", "%1")
		return ret
	end
	
	local function postFormat(t)
		if post_format and mw.ustring.find(post_format, "$1") then
			return mw.ustring.gsub(post_format, "$1", t)
		end
		return t
	end
	
	local intyear = tonumber(string.match(timestamp, "[+-](%d+)"))
	local ret = ""
	
	if precision <= 5 then -- precision is 10000 years or more
		local factor = 10 ^ ((5 - precision) + 4)
		local y2 = math.ceil(math.abs(intyear) / factor)
		local relative = formatTime(i18n.datetime[precision], y2)
		if post_format == i18n.datetime["bc"] then
			ret = mw.ustring.gsub(i18n.datetime.beforenow, "$1", relative)
		else
			ret = mw.ustring.gsub(i18n.datetime.afternow, "$1", relative)
		end
		local ret_number = string.match(ret, "%d+")
		if ret_number ~= nil then
			ret = mw.ustring.gsub(ret, ret_number, mw.language.new(parameters.lang[1]):formatNum(tonumber(ret_number)))
		end
	elseif precision == 6 or precision == 7 then -- millennia or centuries
		local card = math.floor((intyear - 1) / 10^(9 - precision)) + 1
		ret = formatTime(i18n.datetime[precision], card)
		ret = postFormat(ret)
	elseif precision == 8 then -- decades
		local card = math.floor(math.abs(intyear) / 10) * 10
		ret = formatTime(i18n.datetime[8], card)
		ret = postFormat(ret)
	elseif intyear > 9999 then -- not a valid timestamp
		return
	elseif precision == 9 or parameters.formatting == 'Y' then -- precision is year
		ret = formatTime(i18n.datetime[9], intyear)
		if parameters.case then
			ret = case(parameters.case, ret, parameters.lang[1])
		end
		ret = postFormat(ret) .. calendar_add
	elseif precision == 10 then -- month
		ret = formatTime(i18n.datetime[10], timestamp .. " + 1 day") -- formatDate yyyy-mm-00 returns the previous month
		ret = postFormat(ret) .. calendar_add
	else -- precision 11, day
		ret = formatTime(parameters.formatting or i18n.datetime[11], timestamp)
		ret = postFormat(ret) .. calendar_add
	end
	return ret, timestamp
end

-- format data value wikibase-entityid: types wikibase-item, wikibase-property
local function printDatatypeEntity(data, parameters)
	local entity_id = data['id']
	if parameters.formatting == 'raw' then
		return entity_id, entity_id
	end
	local entity_page = 'Special:EntityPage/' .. entity_id
	local label, lang = getLabelByLangs(entity_id, parameters.lang)
	local sitelink = mw.wikibase.getSitelink(entity_id)
	local parameter = parameters.formatting
	local labelcase = label or sitelink
	if parameters.gender == 'feminineform' then
		labelcase = feminineForm(entity_id, lang) or labelcase
	end
	if parameters.case ~= 'gender' then
		labelcase = case(parameters.case, labelcase, lang, parameters.lang[1], entity_id, parameters.id)
	end
	local ret1, ret2
	if parameter == 'label' then
		ret1 = labelcase or entity_id
		ret2 = labelcase or entity_id
	elseif parameter == 'sitelink' then
		ret1 = (sitelink or 'd:' .. entity_page)
		ret2 = sitelink or entity_id
	elseif mw.ustring.find((parameter or ''), '$1', 1, true) then -- formatting = a pattern
		ret1 = mw.ustring.gsub(parameter, '$1', labelcase or entity_id)
		ret1 = expandBraces(ret1, parameter)
		ret2 = labelcase or entity_id
	else
		if parameter == "ucfirst" or parameter == "ucinternallink" then
			if labelcase and lang then
				labelcase = mw.language.new(lang):ucfirst(labelcase)
			end
			-- only first of a list, reset formatting for next ones
			if parameter == "ucinterlanllink" then
				parameters.formatting = 'internallink'
			else
				parameters.formatting = nil -- default format
			end
		end
		
		if sitelink then
			ret1 = '[[' .. sitelink .. '|' .. labelcase .. ']]'
			ret2 = labelcase
		elseif label and (parameter == 'internallink' or parameter == 'ucinternallink') then
			ret1 = '[[' .. label .. '|' .. labelcase .. ']]'
			ret2 = labelcase
		else
			ret1 = '[[d:' .. entity_page .. '|' .. (labelcase or entity_id) .. ']]'
			ret2 = labelcase or entity_id
		end
	end
	
	return ret1 .. addLabelIcon(entity_id, lang, parameters.lang[1], parameters.editicon), ret2
end

-- format data type monolingualtext
local function printDatatypeMonolingual(data, parameters)
	-- data fields: language [string], text [string]
	
	if parameters.list == "lang" and data["language"] ~= parameters.lang[1] then
		return
	elseif parameters.formatting == "language" or parameters.formatting == "text" then
		return data[parameters.formatting]
	end
	local result = data["text"]
	if data["language"] ~= wiki.langcode then
		result = mw.ustring.gsub('<span lang="$1">$2</span>', '$[12]', {["$1"]=data["language"], ["$2"]=data["text"]})
	end
	if mw.ustring.find((parameters.formatting or ''), '$', 1, true) then
		-- output format defined with $text, $language
		result = mw.ustring.gsub(parameters.formatting, '$text', result)
		result = mw.ustring.gsub(result, '$language', data["language"])
	end
	return result
end

local function getSnakValue(snak, parameters)
	if snak.snaktype == 'value' then
		-- see Special:ListDatatypes
		if snak.datatype == "string" then
			return printDatatypeString(snak.datavalue.value, parameters)
		-- other data value string, tabular-data not implemented
		elseif snak.datatype == "commonsMedia" or snak.datatype == "geo-shape" then
			return printDatatypeMedia(snak.datavalue.value, parameters)
		elseif snak.datatype == "url" then
			return printDatatypeUrl(snak.datavalue.value, parameters)
		elseif snak.datatype == "external-id" then
			return printDatatypeExternal(snak.datavalue.value, parameters)
		elseif snak.datatype == 'math' then
			return printDatatypeMath(snak.datavalue.value)
		elseif snak.datatype == 'musical-notation' then
			return printDatatypeMusical(snak.datavalue.value, parameters.formatting)
		-- other data types
		elseif snak.datatype == 'wikibase-item' or snak.datatype == 'wikibase-property' then
			return printDatatypeEntity(snak.datavalue.value, parameters)
		elseif snak.datatype == 'monolingualtext' then
			return printDatatypeMonolingual(snak.datavalue.value, parameters)
		elseif snak.datatype == "globe-coordinate" then
			return printDatatypeCoordinate(snak.datavalue.value, parameters.formatting)
		elseif snak.datatype == "quantity" then
			return printDatatypeQuantity(snak.datavalue.value, parameters)
		elseif snak.datatype == "time" then
			return printDatatypeTime(snak.datavalue.value, parameters)
		end
	elseif snak.snaktype == 'novalue' then
		if parameters.formatting == 'raw' then return end
		return mw.message.new('Wikibase-snakview-snaktypeselector-novalue'):inLanguage(parameters.lang[1]):plain()
	elseif snak.snaktype == 'somevalue' then
		if parameters.formatting == 'raw' then return end
		return mw.message.new('Wikibase-snakview-snaktypeselector-somevalue'):inLanguage(parameters.lang[1]):plain()
	end
	return mw.wikibase.renderSnak(snak)
end

local function printError(key)
	return '<span class="error">' .. i18n.errors[key] .. '</span>'
end

local function getQualifierSnak(claim, qualifierId, parameters)
	-- a "snak" is Wikidata terminology for a typed key/value pair
	-- a claim consists of a main snak holding the main information of this claim,
	-- as well as a list of attribute snaks and a list of references snaks
	if qualifierId then
		-- search the attribute snak with the given qualifier as key
		if claim.qualifiers then
			local qualifier = claim.qualifiers[qualifierId]
			if qualifier then
				if qualifier[1].datatype == "monolingualtext" then
					-- iterate over monolingualtext qualifiers to get local language
					for idx in pairs(qualifier) do
						if getSnak(qualifier[idx], {"datavalue", "value", "language"}) == parameters.lang[1] then
							return qualifier[idx]
						end
					end
				elseif parameters.list then
					return qualifier
				else
					return qualifier[1]
				end
			end
		end
		return nil, printError("qualifier-not-found")
	else
		-- otherwise return the main snak
		return claim.mainsnak
	end
end

local function getValueOfClaim(claim, qualifierId, parameters)
	local snak, error = getQualifierSnak(claim, qualifierId, parameters)
	if not snak then
		return nil, nil, error
	elseif snak[1] then -- a multi qualifier
		local result, sortkey = {}, {}
		local maxvals = tonumber(parameters.list)
		for idx in pairs(snak) do
			result[#result + 1], sortkey[#sortkey + 1] = getSnakValue(snak[idx], parameters)
			if maxvals and maxvals == #result then break end
		end
		return mw.text.listToText(result, parameters.qseparator, parameters.qconjunction), sortkey[1]
	else -- a property or a qualifier
		return getSnakValue(snak, parameters)
	end
end

local function getValueOfParentClaim(claim, qualifierId, parameters)
	local qids = mw.text.split(qualifierId, '/', true)
	local valueraw, parent_claims, value, sortkey
	if qids[1] == parameters.property then
		valueraw, _, _ = getValueOfClaim(claim, nil, {["formatting"]="raw", ["lang"]=parameters.lang})
	else
		valueraw, _, _ = getValueOfClaim(claim, qids[1], {["formatting"]="raw", ["lang"]=parameters.lang})
	end
	if string.sub(valueraw or '', 1, 1) == "Q" then -- protection for 'no value'
		parent_claims = mw.wikibase.getBestStatements(valueraw, qids[2])
		if parent_claims[1] ~= nil then
			value, sortkey, _ = getValueOfClaim(parent_claims[1], nil, parameters)
			-- raw parent value needed for while/black lists, lang for avoiding an error on types other than entity
			valueraw, _, _ = getValueOfClaim(parent_claims[1], nil, {["formatting"]="raw", ["lang"]=parameters.lang})
		end
	end
	return value, sortkey, valueraw
end

-- see d:Help:Sources
local function getReferences(claim, lang)
	local notproperref = {
		["P143"] = true, -- imported from
		["P3452"] = true, -- inferred from
		["P887"] = true, -- based on heuristic
		["P4656"] = true -- Wikimedia import URL
	}
	local result = ""
	-- traverse through all references
	for ref in pairs(claim.references or {}) do
		local refparts
		local refs = {}
		local validref = true
		local ref_name
		-- traverse through all parts of the current reference
		for snakkey, snakval in pairs(claim.references[ref].snaks or {}) do
			for partkey, _ in pairs(claim.references[ref].snaks[snakkey] or {}) do
				if notproperref[snakkey] then -- not a proper reference
					validref = false
					break
				end
			end
			if validref then
				for snakidx = 1, #snakval do
					if snakidx > 1 then refparts = refparts .. ", " end
					refparts = refparts or '' .. getSnakValue(snakval[snakidx], {lang=lang})
				end
				refs[snakkey] = refparts
				refparts = nil
				if snakkey == "P248" then -- stated in
					ref_name = snakval[1].datavalue.value.id
				end
			end
		end
		
		-- fill missing values with parent item
		if ref_name then
			local function refParent(qid, pid, formatting)
				local snak = getSnak(mw.wikibase.getBestStatements(qid, pid), {1, "mainsnak"})
				return snak and getSnakValue(snak, {formatting=formatting, lang=lang})
			end
			
			refs['P50'] = refs['P50'] or refParent(ref_name, 'P50', 'label') -- author
			refs['P407'] = refs['P407'] or refParent(ref_name, 'P407', 'label') -- language of work
			refs['P123'] = refs['P123'] or refParent(ref_name, 'P123', 'label') -- publisher
			refs['P577'] = refs['P577'] or refParent(ref_name, 'P577') -- date
			refs['P1433'] = refs['P1433'] or refParent(ref_name, 'P1433', 'label') -- published in
			refs['P304'] = refs['P304'] or refParent(ref_name, 'P304') -- page(s)
			refs['P433'] = refs['P433'] or refParent(ref_name, 'P433') -- issue
			refs['P236'] = refs['P236'] or refParent(ref_name, 'P236') -- ISSN
			refs['P356'] = refs['P356'] or refParent(ref_name, 'P356') -- DOI
		end
		
		-- get title of local templates for citing references
		local template_web = mw.wikibase.getSitelink('Q5637226') or ""
		template_web = mw.text.split(template_web, ":")[2] -- split off namespace from front
		local template_journal = mw.wikibase.getSitelink('Q5624899') or ""
		template_journal = mw.text.split(template_journal, ":")[2]
		
		local citeParams = {}
		if refs['P854'] and (refs['P1476'] or refs['P248']) and template_web then
			-- if both "reference URL" and "title" (or "stated in") are present, then use cite web template
			citeParams[i18n['cite']['url']] = refs['P854']
			if refs['P248'] and refs['P1476'] == nil then
				citeParams[i18n['cite']['title']] = refs['P248']:match("^%[%[.-|(.-)%]%]")
			else
				citeParams[i18n['cite']['title']] = refs['P1476']
				citeParams[i18n['cite']['website']] = refs['P248']
			end
			citeParams[i18n['cite']['author']] = refs['P50']
			citeParams[i18n['cite']['language']] = refs['P407']
			citeParams[i18n['cite']['publisher']] = refs['P123']
			citeParams[i18n['cite']['date']] = refs['P577']
			citeParams[i18n['cite']['pages']] = refs['P304']
			citeParams[i18n['cite']['access-date']] = refs['P813']
			citeParams[i18n['cite']['archive-url']] = refs['P1065']
			citeParams[i18n['cite']['archive-date']] = refs['P2960']
			citeParams[i18n['cite']['quote']] = refs['P1683']
			refparts = mw.getCurrentFrame():expandTemplate{title=template_web, args=citeParams}
		elseif refs['P1433'] and (refs['P1476'] or refs['P248']) and template_journal then
			-- if both "published in" and "title" (or "stated in") are present, then use cite journal template
			citeParams[i18n['cite']['work']] = refs['P1433']
			citeParams[i18n['cite']['title']] = refs['P1476'] or refs['P248']
			citeParams[i18n['cite']['author']] = refs['P50']
			citeParams[i18n['cite']['date']] = refs['P577']
			citeParams[i18n['cite']['issue']] = refs['P433']
			citeParams[i18n['cite']['pages']] = refs['P304']
			citeParams[i18n['cite']['language']] = refs['P407']
			citeParams[i18n['cite']['issn']] = refs['P236']
			citeParams[i18n['cite']['doi']] = refs['P356']
			refparts = mw.getCurrentFrame():expandTemplate{title=template_journal, args=citeParams}
		elseif validref then
			-- raw ouput
			local snaksorder = claim.references[ref]["snaks-order"]
			local function indexed(a)
				for _, b in ipairs(snaksorder) do
					if b == a then return true end
				end
				return false
			end
			for k, _ in pairs(refs or {}) do
				if not indexed(k) then
					table.insert(snaksorder, k)
				end
			end
			local italics = "''"
			for _, k in ipairs(snaksorder) do
				if refs[k] then
					refparts = refparts and refparts .. " " or ""
					refparts = refparts .. mw.ustring.gsub(getLabelByLangs(k, lang), "^%l", mw.ustring.upper) .. ": "
					refparts = refparts .. italics .. refs[k] .. italics .. "."
					italics = ""
				end
			end
		end
		if refparts then result = mw.getCurrentFrame():extensionTag("ref", refparts, {name=ref_name}) end
	end
	return result
end

-- Set whitelist or blacklist values
local function setWhiteOrBlackList(type_list, num_qual, args)
	local listed = false
	local list = {}
	for i = 0, num_qual do
		if isSet(args[type_list .. i]) then
			listed = true
			list[tostring(i)] = {}
			for v in string.gmatch(args[type_list .. i], 'Q%d+') do
				list[tostring(i)][resolveEntityId(v)] = true
			end
		end
	end
	return list, listed
end

local function tableParameters(args, parameters, column)
	local column_params = mw.clone(parameters)
	column_params.formatting = args["colformat"..column]; if column_params.formatting == "" then column_params.formatting = nil end
	column_params.convert = args["convert" .. column]
	if args["case" .. column] then
		column_params.case = args["case" .. column]
	end
	return column_params
end

local function getEntityId(frame, unnamed)
	local args = frame.args or frame
	local pargs = frame.args and frame:getParent().args
	local id = args.item or args.from or (unnamed and mw.text.trim(args[1] or '') or nil)
	if not isSet(id) and pargs then
		id = pargs.item or pargs.from or (unnamed and mw.text.trim(pargs[1] or '') or nil)
	end
	if isSet(id) then
		if string.find(id, ":") then -- remove prefix as Property:Pid
			id = mw.text.split(id, ":")[2]
		end
	else
		id = mw.wikibase.getEntityIdForCurrentPage()
	end
	return id
end

-- Main function claim ---------------------------------------------
-- on debug console use: =p.claim{item="Q...", property="P...", ...}
function wd.claim(frame)
	local is_sandbox = frame.args and isSet(frame:getParent().args.sandbox)
	if not required and is_sandbox then
		return require(wiki.module_title .. "/" .. mw.message.new('Sandboxlink-subpage-name'):inLanguage(wiki.langcode):plain()).claim(frame)
	end
	local args = frame.args or frame -- via invoke or require
	--If a value is already set, use it
	if isSet(args.value) then
		if args.value == 'NONE' then
			return
		else
			return args.value
		end
	end
	
	-- arguments
	local id = getEntityId(frame)
	if id == nil then return end
	local property = string.upper(args.property or "")
	local qualifierId = {}
	qualifierId[1] = isSet(args.qualifier) and string.upper(args.qualifier) or nil
	local i = 2
	while isSet(args["qualifier" .. i]) do
		qualifierId[i] = string.upper(args["qualifier" .. i])
		i = i + 1
	end
	local formatting = isSet(args.formatting) and args.formatting or nil
	local convert = isSet(args.convert) and args.convert or nil
	local case = args.case
	local list = args.list or true; if (list == "false" or list == "no") then list = false end
	if list == 'firstrank' then list = 'bestrank' end -- alias
	local sorting_col = args.tablesort
	local sorting_up = (args.sorting or "") ~= "-1"
	local separator = isSet(args.separator) and args.separator
	local conjunction = isSet(args.conjunction) and args.conjunction or separator
	local rowformat = args.rowformat
	local references = args.references
	local showerrors = args.showerrors
	local default = args.default
	if default then showerrors = nil end
	local editicon = not (args.editicon == "false" or args.editicon == "no" or formatting == "raw")
	
	local parameters = {["id"] = id, ["property"] = property, ["formatting"] = formatting, ["convert"] = convert,
		["list"] = list, ["case"] = case, ["editicon"] = editicon,
		["separator"] = separator, ["conjunction"] = conjunction, ["qseparator"] = separator, ["qconjunction"] = conjunction}
	parameters.lang = findLang(args.lang)
	
	-- fetch property
	local claims = {}
	local bestrank = (parameters.list == false or parameters.list == 'bestrank') and parameters.list ~= 'lang'
	for wd in string.gmatch(property, 'P%d+') do
		claims = getStatements(id, wd, bestrank)
		if #claims > 0 then
			parameters.property = wd
			break
		end
	end
	if #claims == 0 then
		if showerrors then return printError("property-not-found") else return default end
	end
	
	-- defaults for table
	local preformat, postformat = "", ""
	local whitelisted, blacklisted = false, false
	local whitelist, blacklist = {}, {}
	if parameters.formatting == "table" then
		parameters.separator = parameters.separator or "<br />"
		parameters.conjunction = parameters.conjunction or "<br />"
		parameters.qseparator = ", "
		parameters.qconjunction = ", "
		if not rowformat then
			rowformat = "$0 ($1"
			i = 2
			while qualifierId[i] do
				rowformat = rowformat .. ", $" .. i
				i = i + 1
			end
			rowformat = rowformat .. ")"
		elseif mw.ustring.find(rowformat, "^[*#]") then
			parameters.separator = "</li><li>"
			parameters.conjunction = "</li><li>"
			if mw.ustring.match(rowformat, "^[*#]") == "*" then
				preformat = "<ul><li>"
				postformat = "</li></ul>"
			else
				preformat = "<ol><li>"
				postformat = "</li></ol>"
			end
			rowformat = mw.ustring.gsub(rowformat, "^[*#] ?", "")
		end
		
		-- set whitelist and blacklist values
		whitelist, whitelisted = setWhiteOrBlackList("whitelist", #qualifierId, args)
		blacklist, blacklisted = setWhiteOrBlackList("blacklist", #qualifierId, args)
	end
	
	-- set feminine case if gender is requested
	local itemgender = args.itemgender
	local idgender
	if itemgender then
		if string.match(itemgender, "^P%d+$") then
			local snak_id = getSnak(mw.wikibase.getBestStatements(id, itemgender), {1, "mainsnak", "datavalue", "value", "id"})
			if snak_id then
				idgender = snak_id
			end
		elseif string.match(itemgender, "^Q%d+$") then
			idgender = itemgender
		end
	end
	local gender_requested = false
	if parameters.case == "gender" or idgender then
		gender_requested = true
	elseif parameters.formatting == "table" then
		for i=0, #qualifierId do
			if args["case" .. i] and args["case" .. i] == "gender" then
				gender_requested = true
				break
			end
		end
	end
	if gender_requested then
		if feminineGender(idgender or id) then
			parameters.gender = "feminineform"
		end
	end
	
	-- get initial sort indices
	local sortindices = {}
	for idx in pairs(claims) do
		sortindices[#sortindices + 1] = idx
	end
	-- sort by claim rank
	local comparator = function(a, b)
		local rankmap = { deprecated = 2, normal = 1, preferred = 0 }
		local ranka = rankmap[claims[a].rank or "normal"] .. string.format("%08d", a)
		local rankb = rankmap[claims[b].rank or "normal"] .. string.format("%08d", b)
		return ranka < rankb
	end
	table.sort(sortindices, comparator)
	
	local result, result2
	local error
	if parameters.list or parameters.formatting == "table" then
		-- convert LF to line feed, <br /> may not work on some cases
		parameters.separator = parameters.separator == "LF" and "\010" or parameters.separator
		parameters.conjunction = parameters.conjunction == "LF" and "\010" or parameters.conjunction
		-- i18n separators
		parameters.separator = parameters.separator or mw.message.new('Comma-separator'):inLanguage(parameters.lang[1]):plain()
		parameters.conjunction = parameters.conjunction or (mw.message.new('And'):inLanguage(parameters.lang[1]):plain() .. mw.message.new('Word-separator'):inLanguage(parameters.lang[1]):plain())
		-- iterate over all elements and return their value (if existing)
		local value, valueq
		local sortkey, sortkeyq
		local values = {}
		local sortkeys = {}
		local refs = {}
		local rowlist = {} -- rows to list with whitelist or blacklist
		for idx in pairs(claims) do
			local claim = claims[sortindices[idx]]
			local reference = {}
			if not whitelisted then rowlist[idx] = true end
			if parameters.formatting == "table" then
				local params = tableParameters(args, parameters, "0")
				value, sortkey, error = getValueOfClaim(claim, nil, params)
				if value then
					values[#values + 1] = {}
					sortkeys[#sortkeys + 1] = {}
					refs[#refs + 1] = {}
					if whitelist["0"] or blacklist["0"] then
						local valueraw, _, _ = getValueOfClaim(claim, nil, {["formatting"]="raw", ["lang"]=params.lang})
						if whitelist["0"] and whitelist["0"][valueraw or ""] then
							rowlist[#values] = true
						elseif blacklist["0"] and blacklist["0"][valueraw or ""] then
							rowlist[#values] = false
						end
					end
					for i, qual in ipairs(qualifierId) do
						local j = tostring(i)
						params = tableParameters(args, parameters, j)
						local valueq, sortkeyq, valueraw
						if qual == property then -- hack for getting the property with another formatting, i.e. colformat1=raw
							valueq, sortkeyq, _ = getValueOfClaim(claim, nil, params)
						else
							for q in mw.text.gsplit(qual, '%s*OR%s*') do
								if string.find(q, ".+/.+") then
									valueq, sortkeyq, valueraw = getValueOfParentClaim(claim, q, params)
								elseif string.find(q, "^/.+") then
									local claim2 = getStatements(id, string.sub(q, 2), bestrank)
									if #claim2 > 0 then
										valueq, sortkeyq, _ = getValueOfClaim(claim2[1], nil, params)
									end
								else
									valueq, sortkeyq, _ = getValueOfClaim(claim, q, params)
								end
								if valueq then break end
							end
						end
						values[#values]["col" .. j] = valueq
						sortkeys[#sortkeys]["col" .. j] = sortkeyq or valueq
						if whitelist[j] or blacklist[j] then
							valueq = valueraw or getValueOfClaim(claim, qual, {["formatting"]="raw", ["lang"]=params.lang})
							if whitelist[j] and whitelist[j][valueq or ""] then
								rowlist[#values] = true
							elseif blacklist[j] and blacklist[j][valueq or ""] then
								rowlist[#values] = false
							end
						end
					end
				end
			else
				value, sortkey, error = getValueOfClaim(claim, qualifierId[1], parameters)
				values[#values + 1] = {}
				sortkeys[#sortkeys + 1] = {}
				refs[#refs + 1] = {}
			end
			if not value and showerrors then value = error end
			if value then
				if references and claim.references then reference = claim.references end
				refs[#refs]["col0"] = reference
				values[#values]["col0"] = value
				sortkeys[#sortkeys]["col0"] = sortkey or value
			end
		end
		-- sort and format results
		sortindices = {}
		for idx in pairs(values) do
			sortindices[#sortindices + 1] = idx
		end
		if sorting_col then
			local sorting_table = mw.text.split(sorting_col, '%D+')
			local comparator = function(a, b)
				local valuea, valueb
				local i = 1
				while valuea == valueb and i <= #sorting_table do
					valuea = sortkeys[a]["col" .. sorting_table[i]] or ''
					valueb = sortkeys[b]["col" .. sorting_table[i]] or ''
					i = i + 1
				end
				
				if sorting_up then
					return valueb > valuea
				end
				return valueb < valuea
			end
			table.sort(sortindices, comparator)
		end
		local maxvals = tonumber(parameters.list)
		result = {}
		for idx in pairs(values) do
			local valuerow = values[sortindices[idx]]
			local reference = getReferences({["references"] = refs[sortindices[idx]]["col0"]}, parameters.lang)
			
			value = valuerow["col0"]
			if parameters.formatting == "table" then
				if not rowlist[sortindices[idx]] then
					value = nil
				else
					local rowformatting = rowformat .. "$" -- fake end character added for easy gsub
					value = mw.ustring.gsub(rowformatting, "$0", value)
					value = mw.ustring.gsub(value, "$R0", reference) -- add reference
					for i, _ in ipairs(qualifierId) do
						local valueq = valuerow["col" .. i]
						if args["rowsubformat" .. i] and valueq then
							-- add fake end character $
							-- gsub $i not followed by a number so $1 doesn't match $10, $11...
							-- remove fake end character
							valueq = captureEscapes(valueq)
							valueq = mw.ustring.gsub(args["rowsubformat" .. i] .. "$", "$" .. i .. "(%D)", valueq .. "%1")
							valueq = string.sub(valueq, 1, -2)
							rowformatting = mw.ustring.gsub(rowformatting, "$" .. i .. "(%D)", args["rowsubformat" .. i] .. "%1")
						end
						valueq = valueq and captureEscapes(valueq) or ''
						value = mw.ustring.gsub(value, "$" .. i .. "(%D)", valueq .. "%1")
					end
					value = string.sub(value, 1, -2) -- remove fake end character
					value = expandBraces(value, rowformatting)
				end
			elseif value then
				value = expandBraces(value, parameters.formatting)
				value = value .. reference
			end
			if isSet(value) then
				result[#result + 1] = value
				if not parameters.list or (maxvals and maxvals == #result) then
					break
				end
			end
		end
		-- in a table, add edit icon on last element
		if parameters.formatting == 'table' and parameters.editicon then
			result = addEditIconTable(result, parameters)
		end
		result = preformat .. mw.text.listToText(result, parameters.separator, parameters.conjunction) .. postformat
	else
		-- return first element
		local claim = claims[sortindices[1]]
		result, result2, error = getValueOfClaim(claim, qualifierId[1], parameters)
		if result and references then result = result .. getReferences(claim, parameters.lang) end
	end
	
	if isSet(result) then
		if i18n.addpencil and not (parameters.formatting == 'table' or (result2 and result2 == 'no-icon')) then
			-- add edit icon, except table added previously and except explicit no-icon internal flag
			result = result .. addEditIcon(parameters)
		end
	else
		if showerrors then result = error else result = default end
	end
	return result, (required and not is_sandbox) and untranslated or ''
end

-- This is used to get the TA98 (Terminologia Anatomica first edition 1998) values like 'A01.1.00.005' (property P1323)
-- which are then linked to http://www.unifr.ch/ifaa/Public/EntryPage/TA98%20Tree/Entity%20TA98%20EN/01.1.00.005%20Entity%20TA98%20EN.htm
-- uses the newer mw.wikibase calls instead of directly using the snaks
-- formatPropertyValues returns a table with the P1323 values concatenated with ", " so we have to split them out into a table in order to construct the return string
wd.getTAValue = function(frame)
	local ent = mw.wikibase.getEntity()
	local props = ent:formatPropertyValues('P1323')
	local out = {}
	local t = {}
	for k, v in pairs(props) do
		if k == 'value' then
			t = mw.text.split( v, ", ")
			for k2, v2 in pairs(t) do
				out[#out + 1] = "[http://www.unifr.ch/ifaa/Public/EntryPage/TA98%20Tree/Entity%20TA98%20EN/" .. string.sub(v2, 2) .. "%20Entity%20TA98%20EN.htm " .. v2 .. "]"
			end
		end
	end
	local ret = table.concat(out, "<br> ")
	if #ret == 0 then
		ret = "Invalid TA"
	end
	return ret
end

-- Local functions for getParentValues -----------------------

local function uc_first(word)
	return mw.ustring.upper(mw.ustring.sub(word, 1, 1)) .. mw.ustring.sub(word, 2)
end

local function getPropertyValue(id, property, parameter, langs, editicon, case)
	local snaks = mw.wikibase.getBestStatements(id, property)
	local mysnak = getSnak(snaks, {1, "mainsnak"})
	if mysnak == nil then
		return
	end
	
	local entity_id
	local result = '-' -- default for 'no value'
	if mysnak.datavalue then
		entity_id = "Q" .. tostring(mysnak.datavalue.value['numeric-id'])
		result, _ = getSnakValue(mysnak, {formatting=parameter, lang=langs, editicon=editicon, case=case})
	end
	
	return entity_id, result
end

local function getParentObjects(id,
	prop_format,
	label_format,
	languages, 
	propertySupString, 
	propertyLabel,
	propertyLink,
	labelShow,
	editicon,
	upto,
	upto_linkId,
	last_only,
	grammatical_case,
	include_self)
	
	if upto_linkId == nil then upto_linkId = "" end
	local upto_link_ids = {}
	for q in mw.text.gsplit(upto_linkId, '[^Q%d]') do
		upto_link_ids[resolveEntityId(q)] = true
	end
	local propertySups = mw.text.split(propertySupString, '[^P%d]')
	
	local lastlabel = uc_first(upto or '')
	local maxloop = tonumber(upto) or ((lastlabel .. upto_linkId) == '' and 10 or 50)
	
	local labelFilter = {}
	if labelShow then
		for _, v in ipairs(mw.text.split(labelShow, "/")) do
			labelFilter[uc_first(v)] = true
		end
	end
	
	local label_self
	_, label_self = getPropertyValue(id, propertyLabel, label_format, languages)
	
	local result = {}
	
	for iter = 1, maxloop do
		local link, label, linktext, _id, _link
		for _, propertySup in pairs(propertySups) do 	
			_id, _link = getPropertyValue(id, propertySup, prop_format, languages, editicon, grammatical_case)
			if _id and _link then id = _id; link = _link break end 
		end
		
		if not id or not link then break end
		
		if propertyLink then
			_, linktext = getPropertyValue(id, propertyLink, "label", languages)
			if linktext then
				link = mw.ustring.gsub(link, "%[%[(.*)%|.+%]%]", "[[%1|" .. linktext .. "]]")
			end
		end
		
		_, label = getPropertyValue(id, propertyLabel, label_format, languages, false, "infoboxlabel")
		
		if labelShow == nil or labelFilter[label] then
			result[#result + 1] = {label, link}
			
			if label then 
				labelFilter[label] = nil -- only first label found
			end
		end
		
		if label == lastlabel or upto_link_ids[id] then
			break
		end
	end
	
	if last_only then
		result = {result[#result]}
	end
	
	if include_self then table.insert(result, 1, {label_self, mw.title.getCurrentTitle().text}) end
	
	return result
end

local function parentObjectsToString(result,
	rowformat,
	cascade,
	sorting)
	
	local ret = {}
	local first = 1
	local last = #result
	local iter = 1
	if sorting == "-1" then first = #result; last = 1; iter = -1 end
	for i = first, last, iter do
		local rowtext = mw.ustring.gsub(rowformat, "$[01]", {["$0"] = result[i][1], ["$1"] = result[i][2]})
		ret[#ret +1] = expandBraces(rowtext, rowformat)
	end
	
	if cascade then
		local prefix = ""
		for i = 1, #ret do
			ret[i] = prefix .. "• " .. ret[i]
			prefix = prefix .. "&nbsp;"
		end
	end
	
	return ret
end

-- Returns pairs of instance label and property value fetching a recursive tree
function wd.getParentValues(frame)
	if not required and frame.args and frame:getParent().args.sandbox then
		return require(wiki.module_title .. "/" .. mw.message.new('Sandboxlink-subpage-name'):inLanguage(wiki.langcode):plain()).getParentValues(frame)
	end
	local args = frame.args or frame -- via invoke or require
	local id = getEntityId(frame)
	if id == nil then return end
	local languages = findLang(args.lang)
	local propertySup = args.property; if not isSet(propertySup) then propertySup = "P131" end --administrative entity
	local propertyLabel = args.label; if not isSet(propertyLabel) then propertyLabel = "P31" end --instance
	local propertyLink = args.valuetext; if propertyLink == "" then propertyLink = nil end --internallink
	local property_format = args.formatting; if property_format == "" then property_format = nil end
	local label_format = args.labelformat; if not isSet(label_format) then label_format = "label" end
	local upto = args.upto; if upto == "" then upto = nil end
	local last_only = (args.last_only == "true" or args.last_only == "yes")
	local labelShow = args.labelshow; if labelShow == "" then labelShow = nil end
	local editicon = not (args.editicon == "false" or args.editicon == "no")
	local include_self = (args.include_self == "true" or args.include_self == "yes")
	local case = args.case; if case == "" then case = nil end
	
	if isSet(args.uptolabelid) then
		upto, _ = getLabelByLangs(args.uptolabelid, languages)
	end
	
	if isSet(args.showlabelid) then
		local showLabelList = {}
		for substring in mw.text.gsplit(args.showlabelid, '[^Q%d]') do
			table.insert(showLabelList, (getLabelByLangs(substring, languages)))
		end
		if #showLabelList > 0 then
			labelShow = table.concat(showLabelList,"/")
		end
	end
	
	local result = getParentObjects(id,
		property_format,
		label_format,
		languages, 
		propertySup, 
		propertyLabel,
		propertyLink,
		labelShow,
		editicon,
		upto,
		args.uptovalueid or args.uptolinkid,
		last_only,
		case,
		include_self)
	if #result == 0 then return end
	
	local rowformat = args.rowformat; if not isSet(rowformat) then rowformat = "$0 = $1" end
	local separator = args.separator; if not isSet(separator) then separator = "<br />" end
	local sorting = args.sorting; if sorting == "" then sorting = nil end
	local cascade = (args.cascade == "true" or args.cascade == "yes")
	
	local ret = parentObjectsToString(result,
		rowformat,
		cascade,
		sorting)
	ret = addEditIconTable(ret, {property=propertySup, editicon=editicon, id=id, lang=languages})
	return mw.text.listToText(ret, separator, separator)
end

-- Link with a parent label --------------------
function wd.linkWithParentLabel(frame)
	if not required and frame.args and isSet(frame:getParent().args.sandbox) then
		return require(wiki.module_title .. "/" .. mw.message.new('Sandboxlink-subpage-name'):inLanguage(wiki.langcode):plain()).linkWithParentLabel(frame)
	end
	local args = {}
	if frame.args then
		for k, v in pairs(frame.args) do -- metatable
			args[k] = v
		end
	else
		args = frame -- via require
	end
	if isSet(args.value) then
		return args.value
	end
	
	-- get id value of property/qualifier
	local largs = mw.clone(args)
	largs.list = tonumber(args.list) and args.list or "true"
	largs.formatting = "raw"
	largs.separator = "/·/"
	largs.editicon = "false"
	local items_list, _ = wd.claim(largs)
	if not isSet(items_list) then return end
	local items_table = mw.text.split(items_list, "/·/", true)
	
	-- get internal link of property/qualifier
	largs.formatting = "internallink"
	local link_list, _ = wd.claim(largs)
	local link_table = mw.text.split(link_list, "/·/", true)
	
	-- get label of parent property
	local parent_claim = getSnak(getStatements(items_table[1], args.parent, true), {1, "mainsnak", "datatype"})
	if parent_claim == 'monolingualtext' then
		largs.formatting = nil
		largs.list = 'lang'
	else
		largs.formatting = "label"
		largs.list = "false"
	end
	largs.property = args.parent
	largs.qualifier = nil
	for i, v in ipairs(items_table) do
		largs.item = v
		local link_label, _ = wd.claim(largs)
		if isSet(link_label) then
			link_table[i] = mw.ustring.gsub(link_table[i] or '', "%[%[(.*)%|.+%]%]", "[[%1|" .. link_label .. "]]")
		end
	end
	args.editicon = not (args.editicon == "false" or args.editicon == "no")
	args.id = getEntityId(frame)
	args.lang = findLang(args.lang)
	return mw.text.listToText(link_table) .. addEditIcon(args)
end

-- Calculate number of years old ----------------------------
function wd.yearsOld(frame)
	if not required and frame.args and frame:getParent().args.sandbox then
		return require(wiki.module_title .. "/" .. mw.message.new('Sandboxlink-subpage-name'):inLanguage(wiki.langcode):plain()).yearsOld(frame)
	end
	local args = frame.args or frame -- via invoke or require
	local id = getEntityId(frame)
	local lang = mw.language.new('en')
	
	local function getBestValue(id, prop)
		local snak_value = getSnak(mw.wikibase.getBestStatements(id, prop), {1, "mainsnak", "datavalue", "value"})
		return snak_value
	end
	
	local birth = getBestValue(id, 'P569')
	if type(birth) ~= 'table' or birth.time == nil or birth.precision == nil or birth.precision < 8 then
		return
	end
	local death = getBestValue(id, 'P570')
	if type(death) ~= 'table' or death.time == nil or death.precision == nil then
		death = {['time'] = lang:formatDate('c'), ['precision'] = 11} -- current date
	elseif death.precision < 8 then
		return
	end
	
	local dates = {}
	dates[1] = {['min'] = {}, ['max'] = {}, ['precision'] = birth.precision}
	dates[1].min.year = tonumber(mw.ustring.match(birth.time, "^[+-]?%d+"))
	dates[1].min.month = tonumber(mw.ustring.match(birth.time, "\-(%d%d)\-"))
	dates[1].min.day = tonumber(mw.ustring.match(birth.time, "\-(%d%d)T"))
	dates[1].max = mw.clone(dates[1].min)
	dates[2] = {['min'] = {}, ['max'] = {}, ['precision'] = death.precision}
	dates[2].min.year = tonumber(mw.ustring.match(death.time, "^[+-]?%d+"))
	dates[2].min.month = tonumber(mw.ustring.match(death.time, "\-(%d%d)\-"))
	dates[2].min.day = tonumber(mw.ustring.match(death.time, "\-(%d%d)T"))
	dates[2].max = mw.clone(dates[2].min)
	
	for i, d in ipairs(dates) do
		if d.precision == 10 then -- month
			d.min.day = 1
			local timestamp = string.format("%04d", tostring(math.abs(d.max.year)))
				.. string.format("%02d", tostring(d.max.month))
				.. "01"
			d.max.day = tonumber(lang:formatDate("j", timestamp .. " + 1 month - 1 day"))
		elseif d.precision < 10 then -- year or decade
			d.min.day = 1
			d.min.month = 1
			d.max.day = 31
			d.max.month = 12
			if d.precision == 8 then -- decade
				d.max.year = d.max.year + 9
			end
		end
	end
	
	local function age(d1, d2)
		local years = d2.year - d1.year
		if d2.month < d1.month or (d2.month == d1.month and d2.day < d1.day) then
			years = years - 1
		end
		if d2.year > 0 and d1.year < 0 then
			years = years - 1 -- no year 0
		end
		return years
	end
	
	local old_min = age(dates[1].max, dates[2].min)
	local old_max = age(dates[1].min, dates[2].max)
	local old, old_expr
	if old_min == 0 and old_max == 0 then
		old = "< 1"
		old_max = 1 -- expression in singular
	elseif old_min == old_max then
		old = old_min
	else
		old = old_min .. "/" .. old_max
	end
	if args.formatting == 'unit' then
		local langs = findLang(args.lang)
		local yo, yo_sg, yo_pl, yo_pau
		if langs[1] == wiki.langcode then
			yo_sg = i18n["years-old"].singular
			yo_pl = i18n["years-old"].plural
			yo_pau = i18n["years-old"].paucal
		end
		if not isSet(yo_pl) then
			yo_pl, _ = getLabelByLangs('Q24564698', langs)
			yo_sg = yo_pl
		end
		if not isSet(yo_pau) then
			yo_pau = yo_pl
		end
		yo = mw.language.new(langs[1]):plural(old_max, {yo_sg, yo_pau, yo_pl})
		if mw.ustring.find(yo, '$1', 1, true) then
			old_expr = mw.ustring.gsub(yo, "$1", old)
		else
			old_expr = old .. '&nbsp;' .. yo
		end
	elseif args.formatting then
		old_expr = expandBraces(mw.ustring.gsub(args.formatting, '$1', old), args.formatting)
	else
		old_expr = old
	end
	
	return old_expr
end

-- Gets a label in a given language (content language by default) or its fallbacks, optionnally linked.
function wd.getLabel(frame)
	if not required and frame.args and frame:getParent().args.sandbox then
		return require(wiki.module_title .. "/" .. mw.message.new('Sandboxlink-subpage-name'):inLanguage(wiki.langcode):plain()).getLabel(frame)
	end
	local args = frame.args or frame -- via invoke or require
	local id = getEntityId(frame, true)
	if id == nil then return end
	local editicon = not (args.editicon == "false" or args.editicon == "no") and mw.wikibase.isValidEntityId(id)
	
	local label_icon = ''
	local label, lang
	if args.label then
		label = args.label
	else
		local languages = findLang(args.lang)
		-- exceptions or labels fixed
		local exist, labels = pcall(require, wiki.module_title .. "/labels" .. (languages[1] == wiki.langcode and '' or '/' .. languages[1]))
		if exist and next(labels.infoboxLabelsFromId) ~= nil then
			label = labels.infoboxLabelsFromId[id]
		end
		
		if label == nil then
			label, lang = getLabelByLangs(id, languages)
			if label then
				if args.itemgender and feminineGender(args.itemgender) then
					label = feminineForm(id, lang) or label
				end
				label = mw.language.new(lang):ucfirst(mw.text.nowiki(label)) -- sanitize
			end
			label_icon = addLabelIcon(id, lang, languages[1], editicon)
		end
	end
	
	local linked = args.linked
	if isSet(linked) and linked ~= "no" then
		local article = mw.wikibase.getSitelink(id) or ("d:Special:EntityPage/" .. id)
		return "[[" .. article .. "|" .. (label or id) .. "]]" .. label_icon, not required and '' or untranslated
	else
		return (label or id) .. label_icon, not required and '' or untranslated
	end
end

-- Utilities -----------------------------
-- See also module ../debug.

-- Copied from Module:Wikibase
function wd.getSiteLink(frame)
	local id = getEntityId(frame, 1)
	if id == nil then
		return
	end
	return mw.wikibase.getSitelink(id, mw.text.trim(frame.args[2] or ''))
end

-- Helper function for the default language code used
function wd.lang(frame)
	local lang = frame and frame.args[1] -- nil via require
	return findLang(lang)[1]
end

-- Number of statements
function wd.numStatements(frame)
	local id = getEntityId(frame)
	if id == nil then return 0 end
	local prop = mw.text.trim(frame.args[1])
	local num = {}
	if frame.args[2] then -- qualifier
		local qual = mw.text.trim(frame.args[2])
		local values = wd.claim{item=id, property=prop, qualifier=qual, formatting='raw', separator='/·/'}
		if values then
			num = mw.text.split(values, '/·/')
		end
	else
		num = mw.wikibase.getBestStatements(id, prop)
	end
	return #num
end

-- Returns true if property datavalue is found excluding novalue/somevalue
function wd.validProperty(frame)
	local property = mw.text.trim(frame.args[1])
	local item = getEntityId(frame)
	if item == nil then return end
	local prop_data = getSnak(mw.wikibase.getBestStatements(item, property), {1, "mainsnak", "datavalue"})
	return prop_data and true or nil
end

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

local wd = {}

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

wd.compare = {}

local modules = { }
local modulesNames = {
	reference = 'Module:Wikidata/Références',
	i18n = 'Module:Wikidata/I18n',
	globes = 'Module:Wikidata/Globes',
	langcodes = 'Module:Dictionnaire Wikidata/Codes langue', -- big, infrequently useda
	invertedlangcodes = 'Module:Dictionnaire Wikidata/Codes langue/inversé',

	linguistic = 'Module:Linguistique',
	datemodule = 'Module:Date',
	formatDate = 'Module:Date complexe',
	formatNum = 'Module:Conversion',
	langmodule = 'Module:Langue',
	cite = 'Module:Biblio',
	weblink = 'Module:Weblink'
}

local function loadModule( t, key )
	if modulesNames[key] then
		local m = require( modulesNames[key] )
		t[key] = m
		return m
	end
end
setmetatable( modules, { __index = loadModule } )

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

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

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

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

local function formatError( key , category, debug)
    if debug then
        return error(modules.i18n[key] or key)
    end
    if category then
        return addCat(category, key)
    else
        return addCat('cat-unsorted-issue', key)
    end
end

-- 

function wd.isSpecial(snak)
	return (snak.snaktype ~= 'value')
end

function wd.getId(snak)
	if (snak.snaktype == 'value') then
		return 'Q' .. snak.datavalue.value['numeric-id']
	end
end

function wd.getNumericId(snak)
	if (snak.snaktype == 'value') then
		return snak.datavalue.value['numeric-id']
	end
end

function wd.getMainId(claim)
	return wd.getId(claim.mainsnak)
end

function wd.entityId(entity)
	if type(entity) == 'string' then
		return entity
	elseif type(entity) == 'table' then
		return entity.id
	end
end

function wd.getEntityIdForCurrentPage()
	return mw.wikibase.getEntityIdForCurrentPage()
end

-- function that returns true if the "qid" parameter is the qid 
-- of the item that is linked to the calling page
function wd.isPageOfQId(qid) 
	local self_id = mw.wikibase.getEntityIdForCurrentPage() 
	return self_id ~= nil and qid == self_id 
end

function wd.getEntity( val ) 
	if type(val) == 'table' then
		return val
	end
	if val == '-' then
		return nil
	end
	if val == '' then
		val = nil
	end
	return mw.wikibase.getEntity(val)
end

function wd.splitStr(val) -- transforme en table les chaînes venant du Wikitexte qui utilisent des virgules de séparation
	if type(val) == 'string' then
		val = mw.text.split(val, ",")
	end
	return val
end

function wd.isHere(searchset, val)
	for i, j in pairs(searchset) do
		if val == j then
			return true
		end
	end
	return false
end


local function wikidataLink(entity)
	local name =':d:'
	
	if type(entity) == 'string' then
		if entity:match("P[0-9+]") then
			entity = "Property:" .. entity
		end
		return name .. entity
	elseif type(entity) == 'table' then
		if entity["type"] == "property" then
			name = ":d:Property:"
		end
		return name .. entity.id
	elseif type(entity) == nil then
		return formatError('entity-not-found')
	end
end

function wd.siteLink(entity, project, lang)
	-- returns 3 values: a sitelink (with the relevant prefix) a project name and a language
	lang = lang or defaultlang
	if (type(project) ~= 'string') then
		project = 'wiki'
	end
	project = project:lower()
	if project == 'wikipedia' then
		project = 'wiki'
	end
	if type(entity) == 'string' and (project == 'wiki') and ( (not lang or lang == defaultlang) ) then -- évite de charger l'élément entier
		return  mw.wikibase.sitelink(entity), 'wiki', defaultlang
	end
	if project == 'wikidata' then
		return wikidataLink(entity), 'wikidata'
	end
	local projects = {
		-- nom = {préfixe sur Wikidata, préfix pour les liens sur Wikipédia, ajouter préfixe de langue}
		wiki = {'wiki', nil, true}, -- wikipedia
		commons = {'commonswiki', 'commons', false},
		commonswiki = {'commonswiki', 'commons', false},
		wikiquote = {'wikiquote', 'q', true},
		wikivoyage = {'wikivoyage', 'voy', true},
		wikibooks = {'wikibooks', 'b', true},
		wikinews = {'wikinews', 'n', true},
		wikiversity = {'wikiversity', 'v', true},
		wikisource = {'wikisource', 's', true},
		wiktionary = {'wiktionary', 'wikt', true},
		specieswiki = {'specieswiki', 'species', false},
		metawiki = {'metawiki', 'm', false},
		incubator = {'incubator', 'incubator', false},
		outreach = {'outreach', 'outreach', false},
		mediawiki = {'mediawiki', 'mw', false}
	}

	local entityid = entity.id or entity

	local projectdata = projects[project:lower()]
	if not projectdata then -- defaultlink might be in the form "dewiki" rather than "project: 'wiki', lang: 'de' "
		for k, v in pairs(projects) do
			if project:match( k .. '$' ) 
				and mw.language.isKnownLanguageTag(project:sub(1, #project-#k))
			then
				lang = project:sub(1, #project-#k)
				project = project:sub(#lang + 1, #project)
				projectdata = projects[project]
				break
			end
		end
		if not mw.language.isKnownLanguageTag(lang) then
			return --formatError('invalid-project-code', projet or 'nil')
		end
	end
	if not projectdata then
		return -- formatError('invalid-project-code', projet or 'nil')
	end
	
	local linkcode = projectdata[1]
	local prefix = projectdata[2]
	local multiversion = projectdata[3]
	if multiversion then
		linkcode = lang .. linkcode
	end
	local link = mw.wikibase.getSitelink(entityid, linkcode)
	if not link then
		return nil
	end
	
	if prefix then
		link = prefix .. ':' .. link
	end
	if multiversion then
		link = ':' .. lang .. ':' .. link
	end
	return link, project, lang
end

-- add new values to a list, avoiding duplicates
function wd.addNewValues(olditems, newitems, maxnum, stopval)
	if not newitems then
		return olditems
	end
	for _, qid in pairs(newitems) do
		if stopval and (qid == stopval) then
			table.insert(olditems, qid)
			return olditems
		end
		if maxnum and (#olditems >= maxnum) then
			return olditems
		end
		if not wd.isHere(olditems, qid) then
			table.insert(olditems, qid)
		end
	end
	return olditems
end

--=== FILTER CLAIMS ACCORDING TO VARIOUS CRITERIA : FUNCTION GETCLAIMS et alii ===

local function notSpecial(claim)
	local type
	
	if claim.mainsnak ~= nil then
		type = claim.mainsnak.snaktype
	else
		-- condition respectée quand showonlyqualifier est un paramètre renseigné
		-- dans ce cas, claim n'est pas une déclaration entière, mais UNE snak qualifiée du main snak
		type = claim.snaktype
	end
	
	return type == 'value'
end

local function hasTargetValue(claim, targets) -- retourne true si la valeur est dans la liste des target, ou si c'est une valeur spéciale filtrée séparément par excludespecial
	local id = wd.getMainId(claim)
	local targets = wd.splitStr(targets)
	return wd.isHere(targets, id) or wd.isSpecial(claim.mainsnak)
end

local function excludeValues(claim, values) -- true si la valeur n'est pas dans la liste, ou si c'est une valeur spéciale (filtrée à part par excludespecial)
	return wd.isSpecial(claim.mainsnak) or not ( hasTargetValue(claim, values) )
end

local function bestRanked(claims)
	if not claims then
		return nil
	end
	local preferred, normal = {}, {}
	for i, j in pairs(claims) do
		if j.rank == 'preferred' then
			table.insert(preferred, j)
		elseif j.rank == 'normal' then
			table.insert(normal, j)
		end
	end
	if #preferred > 0 then
		return preferred
	else
		return normal
	end
end

local function withRank(claims, target)
	if target == 'best' then
		return bestRanked(claims)
	end
	local newclaims = {}
	for pos, claim in pairs(claims)  do
		if target == 'valid' then
			if claim.rank ~= 'deprecated' then
				table.insert(newclaims, claim)
			end
		elseif claim.rank == target then
			table.insert(newclaims, claim)
		end
	end
	return newclaims
end

function wd.hasQualifier(claim, acceptedqualifs, acceptedvals, excludequalifiervalues)
	local claimqualifs = claim.qualifiers
	
	if (not claimqualifs) then
		return false
	end

	acceptedqualifs = wd.splitStr(acceptedqualifs)
	acceptedvals = wd.splitStr( acceptedvals)


	local function ok(qualif) -- vérification pour un qualificatif individuel
		if not claimqualifs[qualif] then
			return false
		end
		if not (acceptedvals) then  -- si aucune valeur spécifique n'est demandée, OK
			return true
		end
		for i, wanted in pairs(acceptedvals) do
			for j, actual in pairs(claimqualifs[qualif]) do
				if wd.getId(actual) == wanted then
					return true
				end
			end
		end
	end

	for i, qualif in pairs(acceptedqualifs) do
		if ok(qualif) then
			return true
		end
	end
	return false
end

function wd.hasQualifierNumber(claim, acceptedqualifs, acceptedvals, excludequalifiervalues)
	local claimqualifs = claim.qualifiers
	
	if (not claimqualifs) then
		return false
	end

	acceptedqualifs = wd.splitStr(acceptedqualifs)
	acceptedvals = wd.splitStr( acceptedvals)


	local function ok(qualif) -- vérification pour un qualificatif individuel
		if not claimqualifs[qualif] then
			return false
		end
		if not (acceptedvals) then  -- si aucune valeur spécifique n'est demandée, OK
			return true
		end
		for i, wanted in pairs(acceptedvals) do
			for j, actual in pairs(claimqualifs[qualif]) do
				if mw.wikibase.renderSnak(actual) == wanted then
					return true
				end
			end
		end
	end

	for i, qualif in pairs(acceptedqualifs) do
		if ok(qualif) then
			return true
		end
	end
	return false
end

local function hasSource(claim, targetsource, sourceproperty)
	sourceproperty = sourceproperty or 'P248'
	if targetsource == "-" then
		return true
	end
	if (not claim.references) then return
		false
	end
	local candidates = claim.references[1].snaks[sourceproperty] -- les snaks utilisant la propriété demandée
	if (not candidates) then
		return false
	end
	if (targetsource == "any") then -- si n'importe quelle valeur est acceptée tant qu'elle utilise en ref la propriété demandée
		return true
	end
	targetsource = wd.splitStr(targetsource)
	for _, source in pairs(candidates) do
		local s = wd.getId(source)
		for i, target in pairs(targetsource) do
			if s == target then return true end
		end
	end
	return false
end

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

function wd.hasDate(claim)
	if not claim then
		return false --error() ?
	end
	if wd.getDateFromQualif(claim, 'P585') or wd.getDateFromQualif(claim, 'P580') or wd.getDateFromQualif(claim, 'P582') then
		return true
	end
	return false
end

local function hasLink(claim, site, lang)
	if (claim.mainsnak.snaktype ~= 'value') then -- ne pas supprimer les valeurs spéciales, il y a une fonction dédiée pour ça
		return true
	end
	local id = wd.getMainId(claim)
	local link = wd.siteLink(id, site, lang)
	if link then
		return true
	end
end

local function isInLanguage(claim, lang) -- ne fonctionne que pour les monolingualtext / étendre aux autres types en utilisant les qualifiers ?
	if type(lang) == 'table' then -- si c'est une table de language séparées par des virgules, on les accepte toutes
		for i, l in pairs(lang) do
			local v = isInLanguage(claim, l)
			if v then
				return true
			end
		end
	end
	if type(lang) ~= ('string') then
		return --?
	end

	if (lang == '-') then
		return true
	end
	if (lang == 'locallang') then
		lang =  mw.getContentLanguage():getCode()
	end
	
	-- pour les monolingual text
	local snak = claim.mainsnak or claim
	if snak.snaktype == 'value' and snak.datavalue.type == 'monolingualtext' then
		if snak.datavalue.value.language == lang then
			return true
		end
		return false
	end

	-- pour les autres types de données : recherche dans les qualificatifs
	if (lang == 'fr') then
		lang = 'Q150'
	elseif (lang == 'en') then
		lang = 'Q1860'
	else
		lang = invertedlangcodes[lang]
	end
	if claim.qualifiers and claim.qualifiers.P407 then
		if wd.hasQualifier(claim, {'P407'}, {lang}) then
			return true
		else
			return false
		end
	end

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

local function firstVals(claims, numval) -- retourn les numval premières valeurs de la table claims
    local numval = tonumber(numval) or 0 -- raise a error if numval is not a positive integer ?
    if not claims then
    	return nil
    end
    while (#claims > numval) do
    	table.remove(claims)
    end
    return claims
end

local function lastVals(claims, numval2) -- retourn les valeurs de la table claims à partir de numval2
    local numval2 = tonumber(numval2) or 0 -- raise a error if numval is not a positive integer ?
    if not claims then
    	return nil
    end
    for i=1,numval2 do
    	table.remove(claims, 1)
    end
    return claims
end

-- retourne les valeurs de la table claims à partir de removedupesdate,
-- sans les dates en doublons avec conversion entre les calendrier julien et grégorien,
-- ou uniquement en catégorisant si le paramètre removedupesdate est égale à 'cat'
local function removeDupesDate(claims, removedupesdate)
	if not claims or #claims < 2 then
		return claims, ''
	end
	local cat = ''
	local newClaims = {}
	local newIsos = {}

	local function findIndex(searchset, val) -- similaire à wd.isHere mais retourne l'index de la valeur trouvée
		for i, j in pairs(searchset) do
			if val == j then
				return i
			end
		end
		return -1
	end

	for _, claim in ipairs( claims ) do
		local snack = claim.mainsnak or claim
		if (snack.snaktype == 'value') and (snack.datatype == 'time') and snack.datavalue.value.precision >= 11 then -- s'il s'agit d'un time et que la précision est au moins l'année
			local iso = snack.datavalue.value.time
			_, _, iso = string.find(iso, "(+%d+-%d+-%d+T)")
			local deleteIfDuplicate = false
			if snack.datavalue.value.calendarmodel == 'http://www.wikidata.org/entity/Q1985727' then -- si la date est grégorienne
				if modules.formatDate.before('+1582', iso) then -- si avant 1582 on calcule la date julienne
					_, _, y, m, d = string.find(iso, "+(%d+)-(%d+)-(%d+)T")
					y, m , d = modules.datemodule.gregorianToJulian(y, m , d)
					if m < 10 then m = '0' .. m end
					if d < 10 then d = '0' .. d end
					iso = '+' .. y .. '-' .. m .. '-' .. d .. 'T'
					deleteIfDuplicate = true
				end
				local index = findIndex(newIsos, iso)
				if index >= 0 then -- si la date est déjà présente
					cat = cat .. '[[Catégorie:Article avec des dates identiques venant de wikidata dans le code de l\'infobox]]'
					if removedupesdate == "cat" then -- ne faire que catégoriser
						table.insert(newIsos, iso)
						table.insert(newClaims, claim)
					elseif not deleteIfDuplicate then -- supprimer l'autre date si la date courante n'a pas été convertie
						newClaims[index] = claim
					end -- sinon supprimer la date courante
				else -- pas de doublon
					table.insert(newIsos, iso)
					table.insert(newClaims, claim)
				end
			elseif snack.datavalue.value.calendarmodel == 'http://www.wikidata.org/entity/Q1985786' then -- si date julienne
				if not modules.formatDate.before('+1582', iso) then -- si après 1582 on calcule la date grégorienne
					_, _, y, m, d = string.find(iso, "+(%d+)-(%d+)-(%d+)T")
					y, m , d = modules.datemodule.julianToGregorian(y, m , d)
					if m < 10 then m = '0' .. m end
					if d < 10 then d = '0' .. d end
					iso = '+' .. y .. '-' .. m .. '-' .. d .. 'T'
					deleteIfDuplicate = true
				end
				local index = findIndex(newIsos, iso)
				if index >= 0 then -- si date déjà présente
					cat = cat .. '[[Catégorie:Article avec des dates identiques venant de wikidata dans le code de l\'infobox]]'
					if removedupesdate == "cat" then -- ne faire que catégoriser
						table.insert(newIsos, iso)
						table.insert(newClaims, claim)
					elseif not deleteIfDuplicate then -- supprimer l'autre date si la date courante n'a pas été convertie
						newClaims[index] = claim
					end -- sinon supprimer la date courante
				else -- pas de doublon
					table.insert(newIsos, iso)
					table.insert(newClaims, claim)
				end
			else -- autre calendrier
				table.insert(newIsos, iso)
				table.insert(newClaims, claim)
			end
		else -- précision insuffisante
			table.insert(newIsos, iso)
			table.insert(newClaims, claim)
		end
	end
	return newClaims, cat
end

local function timeFromQualifs(claim, qualifs)
	local claimqualifs = claim.qualifiers
	if not claimqualifs then
		return nil
	end
	for i, qualif in pairs(qualifs or timequalifiers) do
		local vals = claimqualifs[qualif]
		if vals and (vals[1].snaktype == 'value') then
			return vals[1].datavalue.value.time, vals[1].datavalue.value.precision
		end
	end
end

local function atDate(claim, mydate) 
	if mydate == "today" then
		mydate = os.date("!%Y-%m-%dT%TZ")
	end
	-- determines required precision depending on the atdate format
	local d = mw.text.split(mydate, "-")

	local myprecision
	if d[3] then
		myprecision =  11 -- day
	elseif d[2] then
		myprecision = 10 -- month
	else
		myprecision = 9 -- year
 	end

	-- with point in time
	local d, storedprecision = timeFromQualifs(claim, {'P585'})
	if d then
		return modules.formatDate.equal(mydate, d, math.min(myprecision, storedprecision))
	end
	-- with start or end date -- TODO: precision 
	local mindate = timeFromQualifs(claim, {'P580'}) 
	local maxdate = timeFromQualifs(claim, {'P582'})
	if modules.formatDate.before(mydate, mindate) and  modules.formatDate.before(maxdate, mydate) then
		return true
	end
	return false
end

local function check(claim, condition)
	if type(condition) == 'function' then -- cas standard
		return condition(claim)
	end
	return formatError('invalid type', 'function', type(condition)) 
end

local function minPrecision(claim, minprecision)
	local snack
	if claim.qualifiers then -- si une date est donnée en qualificatif, c'est elle qu'on utilise de préférence au mainsnak
		for i, j in ipairs(datequalifiers) do
			if claim.qualifiers[j] then
				snack = claim.qualifiers[j][1] 
				break
			end
		end
	end
	if not snack then
		snack = claim.mainsnak or claim
	end
	if (snack.snaktype == 'value') and (snack.datatype == 'time') and (snack.datavalue.value.precision < minprecision) then
		return false
	end
	return true
end

function wd.sortClaims(claims, sorttype)
	if not claims then
		return nil
	end
	if sorttype == 'chronological' then
		return wd.chronoSort(claims)
	elseif sorttype == 'inverted' then
		return wd.chronoSort(claims, true)
	elseif sorttype == 'order' then
		return wd.chronoSort(claims)
	elseif sorttype == 'ascending' then
		return wd.quantitySort(claims)
	elseif sorttype == 'descending' then
		return wd.quantitySort(claims, true)
	elseif type(sorttype) == 'function' then
		table.sort(claims, sorttype)
		return claims
	elseif type(sorttype) == 'string' and sorttype:sub(1, 1) == 'P' then
		return wd.numericPropertySort(claims, sorttype)
	end
	return claims
end

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

	local function filter(condition, filterfunction, funargs)
		if not args[condition] then
			return
		end
		for i = #claims, 1, -1 do
			if not( filterfunction(claims[i], args[funargs[1]], args[funargs[2]], args[funargs[3]]) ) then
				table.remove(claims, i)
			end
		end
	end
	filter('isinlang', isInLanguage, {'isinlang'} )
	filter('excludespecial', notSpecial, {} )
	filter('condition', check, {'condition'} )
	if claims[1] and claims[1].mainsnak then
		filter('targetvalue', hasTargetValue, {'targetvalue'} )
		filter('atdate', atDate, {'atdate'} )
		filter('qualifier', wd.hasQualifier, {'qualifier', 'qualifiervalue'} )
		filter('qualifiernumber', wd.hasQualifierNumber, {'qualifiernumber', 'qualifiernumbervalue'} )
		filter('excludequalifier', excludeQualifier, {'excludequalifier', 'excludequalifiervalue'} )
		filter('withsource', hasSource, {'withsource', 'sourceproperty'} )
		filter('withdate', wd.hasDate, {} )
		filter('excludevalues', excludeValues, {'excludevalues'})
		filter('withlink', hasLink, {'withlink', 'linklang'} )
		filter('minprecision', minPrecision, {'minprecision'} )
		claims = withRank(claims, args.rank or 'best')
	end
	if #claims == 0 then
		return nil
	end
	if args.sorttype then
		claims = wd.sortClaims(claims, args.sorttype)
	end
	if args.numval2 then
		claims = lastVals(claims, args.numval2)
	end
	if args.numval then
		claims = firstVals(claims, args.numval)
	end
	return claims

end

function wd.loadEntity(entity, cache)
	if type(entity) ~= 'table' then
		if cache then
			if not cache[entity] then 
				cache[entity] = mw.wikibase.getEntity(entity)
				mw.log("cached")
     		end
			return cache[entity]
        else
			if entity == '' or (entity == '-') then
				entity = nil
			end
        	return mw.wikibase.getEntity(entity)
        end
    else 
    	return entity
    end
end


function wd.getClaims( args ) -- returns a table of the claims matching some conditions given in args
	if args.claims then -- if claims have already been set, return them
		return args.claims
	end
	local properties = args.property
	if type(properties) == 'string' then
		properties = wd.splitStr(string.upper(args.property))
	end
	if not properties then
		return formatError( 'property-param-not-provided' )
	end

	--Get entity
	local entity = args.entity
	if type(entity) == 'string' then
		if entity == '' then
			entity = nil
		end
	elseif type(entity) == 'table' then
		entity = entity.id
	end
        if (not entity) then
            entity = mw.wikibase.getEntityIdForCurrentPage()
        end
	if (not entity) or (entity == '-') then
		return nil
	end
	local claims = {}

	if #properties == 1 then
		claims = mw.wikibase.getAllStatements(entity, properties[1]) -- do not use mw.wikibase.getBestStatements at this stage, as it may remove the best ranked values that match other criteria in the query
	else
		for i, prop in ipairs(properties) do
			local newclaims = mw.wikibase.getAllStatements(entity, prop) 
			if newclaims and #newclaims > 0 then
				for j, claim in ipairs(newclaims) do
					table.insert(claims, claim)
				end
			end
		end
	end


	if (not claims) or (#claims == 0) then
		return nil
	end
	return wd.filterClaims(claims, args)
end

--=== ENTITY FORMATTING ===

function wd.getLabel(entity, lang, labelformat)
	
	if (not entity) then
		return nil -- ou option de gestion des erreurs ?
	end

	lang = lang or defaultlang

	if type(labelformat) == 'function' then
		return labelformat(entity)
	end
	
	entity = entity.id or ( type(entity) == "string" and entity)
	
	if (type(entity) == 'string') and (lang == defaultlang) then -- le plus économique
		local str = mw.wikibase.label(entity)
		if str then -- mw.wikibase.label() ne fonctionne pas avec les redirect https://phabricator.wikimedia.org/T157868
			return str
		end
	end
	
	return mw.wikibase.getLabelByLang(entity, lang)
end

function wd.formatEntity( entity, params )

	if (not entity) then
		return nil --formatError('entity-not-found')
	end
	local id = entity
	if type(id) == 'table' then
		id = id.id
	end

	params = params or {}
	local lang = params.lang or defaultlang
	local speciallabels = params.speciallabels
	local displayformat = params.displayformat
	local labelformat = params.labelformat
	local defaultlabel = params.defaultlabel or id
	local linktype = params.link
	local defaultlink = params.defaultlink
	local defaultlinkquery = params.defaultlinkquery

	if speciallabels and speciallabels[id] then --speciallabels override the standard label + link combination
		return speciallabels[id]
	end
	if params.displayformat == 'raw' then
		return id
	end

	local link, label
	
	label = wd.getLabel(entity, lang, labelformat)
	
	-- détermination du fait qu'on soit ou non en train de rendre l'élément sur la page de son article
	local rendering_entity_on_its_page = wd.isPageOfQId(id)
		

	if not label then
		if (defaultlabel == '-') then 
			return nil
		end
		link = wd.siteLink(id, 'wikidata')
		return '[[' .. link .. '|' .. id .. ']]' .. addCat(modules.i18n['to translate'])
-- si pas de libellé, on met un lien vers Wikidata pour qu'on comprenne à quoi ça fait référence
	end

	if (linktype == '-') or rendering_entity_on_its_page then
		return label
	end

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

	-- defaultlinkquery will try to link to another page on this Wiki
	if (not link) and defaultlinkquery then
		if type(defaultlinkquery) == 'string' then
				defaultlinkquery = {property = defaultlinkquery}
		end
		defaultlinkquery.excludespecial = true
		defaultlinkquery.entity = entity
		local claims = wd.getClaims(defaultlinkquery)
		if claims then
			for i, j in pairs(claims) do
				local id = wd.getMainId(j)
				link = wd.siteLink(id, linktype, lang)
				if link then
					break
				end
			end
		end	
	end

	if link then 
		return '[[' .. link .. '|' .. label .. ']]'
	end

	-- if not link, you can use defaultlink: a sidelink to another Wikimedia project
	if (not defaultlink) then
		defaultlink = {'enwiki'}
	end
	if defaultlink and (defaultlink ~= '-') then
		local linktype
		local sidelink, site, langcode
	
		if type(defaultlink) == 'string' then
			defaultlink = {defaultlink}
		end
		for i, j in ipairs(defaultlink) do
			sidelink, site, langcode = wd.siteLink(entity, j, lang)
			if sidelink then
				break
			end
		end
		if not sidelink then
			sidelink, site = wd.siteLink(entity, 'wikidata')
		end
		
		local icon, class, title = site, nil, nil -- le texte affiché du lien
		if site == 'wiki' then
			icon, class, title = langcode, "indicateur-langue", wd.translate('see-another-language', mw.language.fetchLanguageName(langcode, defaultlang))	
		elseif site == 'wikidata' then
			icon, class, title = 'd',  "indicateur-langue", wd.translate('see-wikidata')		
		else
			title = wd.translate('see-another-project', site)
		end
		local val = '[[' .. sidelink .. '|' .. '<span class = "' .. (class or '').. '" title = "' .. (title or '') .. '">' .. icon .. '</span>]]'
		return label .. ' <small>(' .. val .. ')</small>'
	end 
	return label
end


function wd.addTrackingCat(prop, cat) -- doit parfois être appelé par d'autres modules
	if type(prop) == 'table' then
		prop = prop[1] -- devrait logiquement toutes les ajouter
	end
	if not prop and not cat then
		return formatError("property-param-not-provided")
	end
	if not cat then
		cat = wd.translate('trackingcat', prop or 'P??')
	end
	return addCat(cat )
end

local function unknownValue(snak, label)
	local str = label

	if type(str) == "function" then
		str = str(snak)
	end

	if (not str) then
		if snak.datatype == 'time' then
			str = wd.translate('sometime')
		else
			str = wd.translate('somevalue')
		end
	end

	if type(str) ~= "string" then
		return formatError(snak.datatype)
	end
	return str
end

local function noValue(displayformat)
	if not displayformat then
		return wd.translate('novalue')
	end
	if type(displayformat) == 'string' then
		return displayformat
	end
	return formatError()
end

local function getLangCode(entityid)
	return modules.langcodes[tonumber(entityid:sub(2))]
end

local function showLang(statement, maxLang) -- retourne le code langue entre paranthèse avant la valeur (par exemple pour les biblios et liens externes)
	local mainsnak = statement.mainsnak
	if mainsnak.snaktype ~= 'value' then
		return nil
	end
	local langlist = {}
	if mainsnak.datavalue.type == 'monolingualtext' then
		langlist = {mainsnak.datavalue.value.language}
	elseif (not statement.qualifiers) or (not statement.qualifiers.P407) then
		return
	else
		for i, j in pairs( statement.qualifiers.P407 ) do
			if  j.snaktype == 'value' then
				local langentity = wd.getId(j)
				local langcode =  getLangCode(langentity)
				table.insert(langlist, langcode)
			end
		end
	end
	if (#langlist > 1) or (#langlist == 1 and langlist[1] ~= defaultlang) then -- si c'est en français, pas besoin de le dire
		langlist.maxLang = maxLang
		return modules.langmodule.indicationMultilingue(langlist)
	end
end


-- === DATE HANDLING ===

function wd.addStandardQualifs(str, statement)
	if (not statement) or (not statement.qualifiers) then
		return str
	end
	if not str then
		return error()-- what's that ?
	end

	if statement.qualifiers.P1480 then
		for i, j in pairs(statement.qualifiers.P1480) do
			local v = wd.getId(j)
			if (v == "Q21818619") then
				str = wd.translate('approximate-place', str)
			elseif (v == "Q18122778") or (v == "Q18912752") then
				str = wd.translate('uncertain-information', str)
			elseif (v == "Q5727902") then
				if (statement.mainsnak.datatype == 'time') then
					str = modules.formatDate.fuzzydate(str)
				else
					str = wd.translate('approximate-value', str)
				end
			end			
		end
	end
	return str
end

local function rangeObject(begin, ending, params)
	--[[
		objet comportant un timestamp pour le classement chronologique et deux dateobject (begin et ending)
	]]-- 
	local timestamp
	if begin then
		timestamp = begin.timestamp
	else
		timestamp = ending.timestamp
	end
	return {begin = begin, ending = ending, timestamp = timestamp, type = 'rangeobject'}
end

local function dateObject(orig, params)
	--[[ transforme un snak en un nouvel objet utilisable par Module:Date complexe
		{type = 'dateobject', timestamp = str, era = '+' ou '-', year = number, month = number, day = number, calendar = calendar}
	]]-- 
	if not params then
		params = {}
	end
	
	local newobj = modules.formatDate.splitDate(orig.time, orig.calendarmodel)
	
	newobj.precision = params.precision or orig.precision
	newobj.type = 'dateobject'
	return newobj
end

local function objectToText(obj, params)
	if obj.type == 'dateobject' then
		return modules.formatDate.simplestring(obj, params)
	elseif obj.type == 'rangeobject' then
		return modules.formatDate.daterange(obj.begin, obj.ending, params)
	end
end

function wd.getDateFromQualif(statement, qualif)
	if (not statement) or (not statement.qualifiers) or not (statement.qualifiers[qualif]) then
		return nil
	end
	local v = statement.qualifiers[qualif][1]
	if v.snaktype ~= 'value' then -- que faire dans ce cas ?
		return nil
	end
	return dateObject(v.datavalue.value)
end

function wd.getDate(statement)
	local period = wd.getDateFromQualif(statement, 'P585') -- retourne un dateobject
	if period then
		return period
	end
	local begin, ending = wd.getDateFromQualif(statement, 'P580'),  wd.getDateFromQualif(statement, 'P582')
	if begin or ending then
		return rangeObject(begin, ending) -- retourne un rangeobject fait de deux dateobject
	end
	return nil
end

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


	--cherche la date avec les qualifs P580/P582
	local datetable = wd.getDate(statement)
	if datetable then
		str = objectToText(datetable, params)
	end
	
	-- puis limite intérieur / supérieur
	if not str then
		local start, ending = wd.getDateFromQualif(statement, 'P1319'), wd.getDateFromQualif(statement, 'P1326')
		str = modules.formatDate.between(start, ending, params)
	end

	 -- sinon, le mainsnak, pour les données de type time
	if (not str) and (statement.mainsnak.datatype == 'time') then
		local mainsnak = statement.mainsnak
		if (mainsnak.snaktype == 'value') or (mainsnak.snaktype == 'somevalue') then
			str = wd.formatSnak(mainsnak, params)
		end
	end

	if str and params and (params.addstandardqualifs ~= '-') then
		str = wd.addStandardQualifs(str, statement)
	end
	return str
end

wd.compare.by_quantity = function(c1, c2)
	local v1 = wd.getDataValue(c1.mainsnak)
	local v2 = wd.getDataValue(c2.mainsnak) 
	if not (v1 and v2) then
			return true
	end
	return v1 < v2
end

--[[ tri chronologique générique : 
             retourne une fonction de tri de liste de déclaration
             en fonction d’une fonction qui calcule la clé de tri
             et d’une fonction qui compare les clés de tri
   
   paramètres nommés: (appel type wikidata.compare.chrono_key_sort{sortKey="nom clé"})
        sortKey (optionnel)   : chaine, le nom de la clé utilisée pour un tri 
                                (pour éviter de rentrer en collision avec "dateSortKey" 
                                utilisé par chronoSort au besoin)
        snak_key_get_function : fonction qui calcule la valeur de la clé à partir d’un snak ou d’une déclaration,
        (obligatoire)           le résultat n’est calculé qu’une fois et est stocké en cache dans claim[sortKey]
        key_compare_function  : fonction de comparaison des clés calculées par snak_key_get_function
        (optionnel)
--]]    

function wd.chrono_key_sort(arg)
	
	local snak_key_get_function = arg.snak_key_get_function
	local sortKey = arg.sortKey or "dateSortKey"
	local key_compare_function = arg.key_compare_function or 
									function(c1, c2) return c1 < c2 end
	return function(claims)
		for _, claim in ipairs( claims ) do
			if not claim[sortKey] then
				local key = snak_key_get_function(claim)
				if key then
					claim[sortKey] = wd.compare.get_claim_date(key)
				else
					claim[sortKey] = 0
				end
			end
		end
		table.sort(
			claims,
			function(c1, c2)
				return key_compare_function(c1[sortKey], c2[sortKey])
			end
		)
		return claims
	end
end


function wd.quantitySort(claims, inverted)
	local function sort(c1, c2)
		local v1 = wd.getDataValue(c1.mainsnak)
		local v2 = wd.getDataValue(c2.mainsnak) 
		if not (v1 and v2) then
				return true
		end
		if inverted then
			return v2 < v1
		end
		return v1 < v2
	end
	table.sort(claims, sort )
	return claims
end

function wd.compare.get_claim_date(claim)
	local snack = claim.mainsnak or claim
	local iso
	if (snack.snaktype == 'value') and (snack.datatype == 'time') then
		iso = snack.datavalue.value.time
	else
		iso = timeFromQualifs(claim, datequalifiers) or '0'
	end
	-- transformation en nombre (indication de la base car gsub retourne deux valeurs)
	return tonumber( iso:gsub( '(%d)%D', '%1' ), 10 )
end

function wd.compare.chronoCompare(c1, c2)
	return wd.compare.get_claim_date(c1) < wd.compare.get_claim_date(c2)
end

-- fonction pour renverser l’ordre d’une autre fonction
function wd.compare.rev(comp_criteria)
	return function(c1, c2)
		-- attention les tris en lua attendent des fonctions de comparaison strictement inférieur, on doit
		-- vérifier la non égalité quand on inverse l’ordre d’un critère, d’ou "and comp_criteria(c2,c1)"
		return not(comp_criteria(c1,c2)) and comp_criteria(c2,c1)
	end
end

-- Fonction qui trie des Claims de type time selon l'ordre chronologique
-- Une clé de tri nomée « dateSortKey » est ajouté à chaque claim.
-- Si des clés de tri de ce nom existent déjà, elles sont utilisées sans modification.

function wd.chronoSort( claims, inverted )
	for _, claim in ipairs( claims ) do
		if not claim.dateSortKey then
			claim.dateSortKey = wd.compare.get_claim_date(claim)
		end
	end
	table.sort( 
		claims,
		function ( c1, c2 )
			if inverted then
				return c2.dateSortKey < c1.dateSortKey
			end
			return c1.dateSortKey < c2.dateSortKey
		end
	)
	return claims
end

local function get_numeric_claim_value(claim, propertySort)
	local val
	local claimqualifs = claim.qualifiers
	if claimqualifs then
		local vals = claimqualifs[propertySort]
		if vals and vals[1].snaktype == 'value' then
			val = vals[1].datavalue.value
		end
	end
	return tonumber(val or 0)
end

function wd.compare.numeric(propertySort)
	return function(c1, c2)
		return get_numeric_claim_value(c1, propertySort) < get_numeric_claim_value(c2, propertySort)
	end
end

-- Fonction qui trie des Claims de type value selon l'ordre de la propriété fournit
-- Une clé de tri nomée « dateSortKey » est ajouté à chaque claim.
-- Si des clés de tri de ce nom existent déjà, elles sont utilisées sans modification.

function wd.numericPropertySort( claims, propertySort )
	for _, claim in ipairs( claims ) do
		if not claim.dateSortKey then
			local val = get_numeric_claim_value(claim, propertySort)
			claim.dateSortKey = tonumber(val or 0)
		end
	end
	table.sort(
		claims,
		function ( c1, c2 )
			return c1.dateSortKey < c2.dateSortKey
		end
	)
	return claims
end

--[[
test possible en console pour la fonction précédente : 
= p.formatStatements{entity = "Q375946", property = 'P50', sorttype = 'P1545', linkback = "true"}
--]]

-- ===================
function wd.getReferences(statement)
	local refdata = statement.references
	if not refdata then
		return nil
	end

	local refs = {}
	local hashes = {}
	for i, ref in pairs(refdata) do
		local s
		local function hasValue(prop) -- checks that the prop is here with valid value
			if ref.snaks[prop] and ref.snaks[prop][1].snaktype == 'value' then
				return true
			end
			return false
		end		

		if ref.snaks.P248 then -- cas lorsque P248 (affirmé dans) est utilisé 
			for j, source in pairs(ref.snaks.P248) do
				if source.snaktype == 'value' then
					local page, accessdate, quotation
					if hasValue('P304') then -- page 
						page = wd.formatSnak(ref.snaks.P304[1])
					end
					if hasValue('P813') then -- date de consultation
						accessdate = wd.formatSnak(ref.snaks.P813[1])
					end
					if hasValue('P1683') then -- citation
						quotation = wd.formatSnak(ref.snaks.P1683[1])
					end
					local sourceId = wd.getId(source)
					s = modules.reference.citeitem(sourceId, {['page'] = page, ['accessdate'] = accessdate, ['citation'] = quotation})
					table.insert(refs, s)
					table.insert(hashes, ref.hash .. sourceId)
				end
			end
	
		elseif hasValue('P854') then -- cas lorsque P854 (URL de la référence) est utilisé
			local url, title, author, publisher, accessdate, publishdate, publishlang, quotation

			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

			--todo : handle multiple values for author, etc.
			if hasValue('P813') then -- date de consultation
				accessdate = wd.formatSnak(ref.snaks.P813[1])
			end
			if hasValue('P50') then  -- author (item type)
				author = wd.formatSnak(ref.snaks.P50[1])
			elseif hasValue('P2093') then -- author (string type)
				author = wd.formatSnak(ref.snaks.P2093[1])
			end
			if hasValue('P123') then -- éditeur
				publisher = wd.formatSnak(ref.snaks.P123[1])
			end
			if hasValue('P1683') then -- citation
				quotation = wd.formatSnak(ref.snaks.P1683[1])
			end
			if hasValue('P577') then -- date de publication
				publishdate = wd.formatSnak(ref.snaks.P577[1])
			end 
			if hasValue('P407') then -- langue de l'œuvre
				local id = wd.getId(ref.snaks.P407[1])
				publishlang = getLangCode(id)
			end
			s = modules.cite.lienWeb{titre = title, url = url, auteur = author, editeur = publisher, langue = publishlang, ['en ligne le'] = publishdate, ['consulté le'] = accessdate, ['citation'] = quotation}
			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_virgule">,</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) == 'function' then
		return displayformat(snak, params)
	end

	if datatype == 'wikibase-item' then
		return wd.formatEntity(wd.getId(snak), params)
	end

	if datatype == 'url' then
		return modules.weblink.makelink(value, params.text)
	end

	if datatype == 'math' then
		return mw.getCurrentFrame():extensionTag( "math", value)
	end

	if datatype == '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
			local url = mw.ustring.gsub(urlpattern, '$1', (value:gsub('%%', '%%%%'))):gsub(' ', '%%20')
			value = '[' .. url .. ' ' .. (params.text or value) .. ']'
		end
		return value
	end
	
	if datatype == 'time' then -- format example: +00000001809-02-12T00:00:00Z
		if displayformat == 'raw' then
			return value.time
		else
			local dateobject = dateObject(value, {precision = params.precision})
			return objectToText(dateobject, params)
		end
	end

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

	if datatype == 'quantity' then -- todo : gérer les paramètres précision
		local amount, unit = value.amount, value.unit

		if unit then
			unit = unit:match('Q%d+')
		end

		local raw	
		if displayformat == "raw" then
			raw = true
		end
		return modules.formatNum.displayvalue(amount, unit,
			{targetunit = params.targetunit, raw = raw, rounding = params.rounding, showunit = params.showunit or 'short', showlink = params.showlink}
		)
	end
	if datatype == 'monolingualtext' then
		if value.language == defaultlang then
			return value.text
		else
			return modules.langmodule.langue({value.language, value.text})
		end
	end	
	return formatError('unknown-datavalue-type' )

end

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

	if not claims then
		claims = wd.getClaims(args)
	end
	if not claims or claims == {} then
		return {}, {}, cat
	end
	if args.removedupesdate and (args.removedupesdate ~= '-') then
		claims, cat = removeDupesDate(claims, args.removedupesdate)
	end
	local props = {} -- liste des propriétés associété à chaque string pour catégorisation et linkback
	for i, j in pairs(claims) do
		claims[i] = wd.formatStatement(j, args)
		table.insert(props, j.mainsnak.property)
	end
	if args.removedupes and (args.removedupes ~= '-') then
		claims = wd.addNewValues({}, claims) -- devrait aussi supprimer de props celles qui ne sont pas utilisées
	end
	return claims, props, cat
end

function wd.getQualifiers(statement, qualifs, params)
	if not statement.qualifiers then
		return nil
	end
	local vals = {}
	if type(qualifs) == 'string' then
		qualifs = wd.splitStr(qualifs)
	end
	for i, j in pairs(qualifs) do
		if statement.qualifiers[j] then
			for k, l in pairs(statement.qualifiers[j]) do
				table.insert(vals, l)
			end
		end
	end
	if #vals == 0 then
		return nil
	end
	return vals
end

function wd.getFormattedQualifiers(statement, qualifs, params)
	if not params then params = {} end
	local qualiftable = wd.getQualifiers(statement, qualifs)
	if not qualiftable then
		return nil
	end
	qualiftable = wd.filterClaims(qualiftable, params) or {}
	for i, j in pairs(qualiftable) do
		qualiftable[i] = wd.formatSnak(j, params)
	end
	return modules.linguistic.conj(qualiftable, params.conjtype)
end

function wd.showQualifiers(str, statement, args)
	local qualifs =  args.showqualifiers
	if not qualifs then
		return str -- or error ?
	end
	if type(qualifs) == 'string' then
			qualifs = wd.splitStr(qualifs)
	end
	local qualifargs = args.qualifargs or {}
	-- formatage des qualificatifs = args commençant par "qualif", ou à défaut, les mêmes que pour la valeur principale
	qualifargs.displayformat = args.qualifdisplayformat or args.displayformat
	qualifargs.labelformat = args.qualiflabelformat or args.labelformat
	qualifargs.link = args.qualiflink or args.link
	qualifargs.linktopic = args.qualiflinktopic or args.linktopic
	qualifargs.conjtype = args.qualifconjtype
	qualifargs.defaultlink = args.qualifdefaultlink or args.defaultlink
	qualifargs.defaultlinkquery = args.qualifdefaultlinkquery or args.defaultlinkquery
			
	local formattedqualifs = wd.getFormattedQualifiers(statement, qualifs, qualifargs)
	if formattedqualifs and formattedqualifs ~= "" then
		str = str .. " (" .. formattedqualifs .. ")"
	end
	return str
end


function wd.formatSnak( snak, params )
	if not params then params = {} end -- pour faciliter l'appel depuis d'autres modules
	if snak.snaktype == 'somevalue' then
		return unknownValue(snak, params.unknownlabel)
	elseif snak.snaktype == 'novalue' then
		return noValue(params.novaluelabel)
	elseif snak.snaktype == 'value' then
		return wd.getDataValue( snak, params)
	else
		return formatError( 'unknown-snak-type' )
	end
end

function wd.formatStatement( statement, args ) -- FONCTION A REORGANISER (pas très lisible)
	if not args then
		args = {}
	end
	if not statement.type or statement.type ~= 'statement' then
		return formatError( 'unknown-claim-type' )
	end
	local prop = statement.mainsnak.property

	local str

	-- special displayformat f
	if args.statementformat and (type(args.statementformat) == 'function') then
		str = args.statementformat(statement, args)
	elseif (statement.mainsnak.datatype == 'time') and (statement.mainsnak.dateformat ~= '-') then
		if args.displayformat == 'raw' and statement.mainsnak.snaktype == 'value' then
			str = statement.mainsnak.datavalue.value.time
		else
			str = wd.getFormattedDate(statement, args)
		end
	elseif args.showonlyqualifier and (args.showonlyqualifier ~= '') then
		str = wd.getFormattedQualifiers(statement, args.showonlyqualifier, args)
		if not str then
			return nil
		end
		if args.addstandardqualifs ~= '-' then
			str = wd.addStandardQualifs(str, statement)
		end
	else
		str = wd.formatSnak( statement.mainsnak, args )
		if (args.addstandardqualifs ~= '-') and (args.displayformat ~= 'raw') then
			str = wd.addStandardQualifs(str, statement)
		end
	end

	-- ajouts divers
	if args.showlang == true then
		local indicateur = showLang(statement, args.maxLang)
		if indicateur then
			str = indicateur .. '&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, alrady added by wd.formatStatement
		if period then
			str = str .. " <small>(" .. period .. ")</small>"
		end
	end

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

	return str
end

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

	local class = ''
	if property then
		class = 'wd_' .. string.lower(property)
	end
	local icon = '[[File:Blue pencil.svg|%s|10px|baseline|class=noviewer|link=%s]]'
	local title = wd.translate('see-wikidata-value')
	local url = mw.uri.fullUrl('d:' .. id, 'uselang=fr')
	url.fragment = property -- ajoute une #ancre si paramètre "property" défini
	url = tostring(url)
	local v = mw.html.create('span')
		:addClass(class)
		:wikitext(str)
		:tag('span')
			:addClass('noprint wikidata-linkback')
			:wikitext(icon:format(title, url))
		:allDone()
	return tostring(v)
end

function wd.addRefAnchor(str, id)
--[[
	Insère une ancre pour une référence générée à partir d'un élément wd.
	L'id Wikidata sert d'identifiant à l'ancre, à utiliser dans les modèles type "harvsp"
--]]
	return tostring(
		mw.html.create('span')
			:attr('id', id)
			:attr('class', "ouvrage")
			:wikitext(str)
	)
end

--=== FUNCTIONS USING AN ENTITY AS ARGUMENT ===
local function formatStatementsGrouped(args, type)
	-- regroupe les affirmations ayant la même valeur en mainsnak, mais des qualificatifs différents
	-- (seulement pour les propriétés de type élément)

	local claims = wd.getClaims(args)
	if not claims then
		return nil
	end
	local groupedClaims = {}

	-- regroupe les affirmations par valeur de mainsnak
	local function addClaim(claim)
		local id = wd.getMainId(claim)
		for i, j in pairs(groupedClaims) do 
			if (j.id == id) then
				table.insert(groupedClaims[i].claims, claim)
				return
			end
		end
		table.insert(groupedClaims, {id = id, claims = {claim}})
	end
	for i, claim in pairs(claims) do	
		addClaim(claim)
	end

	local stringTable = {}

	-- instructions ad hoc pour les paramètres concernant la mise en forme d'une déclaration individuelle
	local funs = {
		{param = "showqualifiers", fun = function(str, claims)
			local qualifs = {}
			for i, claim in pairs(claims) do
				local news = wd.getFormattedQualifiers(claim, args.showqualifiers, args)
				if news then
					table.insert(qualifs, news)
				end
			end
			local qualifstr = modules.linguistic.conj(qualifs, "qualif-separator")
			if 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)
		for i, fun in pairs(funs) do
			if args[fun.param] then
				str = fun.fun(str, group.claims, args)
			end
		end
		table.insert(stringTable, str)
	end
				
	args.valuetable = stringTable
	return wd.formatStatements(args)
end


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

	if args.value == '-' then
		return nil
	end

	-- If a value is already set: use it, except if it's the special value {{WD}} (use wikidata)
	if args.value and args.value ~= '' then
		local valueexpl = wd.translate("activate-query")
		if args.value ~= valueexpl then
			return args.value
		end
	-- There is no value set, and args.expl disables wikidata on empty values
	elseif args.expl then
		return nil
	end

	if args.grouped and args.grouped ~= '' then
		args.grouped = false
		return formatStatementsGrouped(args)
	end
	local valuetable = args.valuetable -- dans le cas où les valeurs sont déjà formtées
	local props -- les prorpriétés réellement utilisées (dans certainse cas, ce ne sont pas toutes celles de ags.property
	local cat = ''
	if not valuetable then -- cas le plus courant
		valuetable, props, cat = wd.stringTable(args)
	end

	local str = modules.linguistic.conj(valuetable, args.conjtype)
	if not str then
		return args.default
	end
	if not props then
		props = wd.splitStr(args.property)[1]
	end
	if args.ucfirst ~= '-' then
		str = modules.linguistic.ucfirst(str)
	end

	if args.addcat and (args.addcat ~= '-') then
		str = str .. wd.addTrackingCat(props) .. cat
	end
	if args.linkback and (args.linkback ~= '-') then
		str = wd.addLinkBack(str, args.entity, props)
	end
	if args.returnnumberofvalues then
		return str, #valuetable
	end
	return str
end

function wd.formatAndCat(args)
	if not args then
		return nil
	end
	args.linkback = args.linkback or true
	args.addcat = true
	if args.value then -- do not ignore linkback and addcat, as formatStatements do
		if args.value == '-' then
			return nil
		end
		local val = args.value .. wd.addTrackingCat(args.property)
		val = wd.addLinkBack(val, args.entity, args.property)
		return val
	end 
	return wd.formatStatements( args )
end

function wd.getTheDate(args)
	local claims = wd.getClaims(args)
	if not claims then
		return nil
	end
	local formattedvalues = {}
	for i, j in pairs(claims) do
		local v = wd.getFormattedDate(j, args)
		if v then
			table.insert(formattedvalues, v )
		end
	end
	local val = modules.linguistic.conj(formattedvalues)
	if not val then
		return nil
	end
	if args.addcat == true then
		val = val .. wd.addTrackingCat(args.property)
	end
	val = wd.addLinkBack(val, args.entity, args.property)
	return val
end


function wd.keyDate (event, item, params)
	params = params or {}
	params.entity = item
	if type(event) == 'table' then
		for i, j in pairs(event) do
			params.targetvalue = nil -- réinitialisation barbare des paramètres modifiés
			local s = wd.keyDate(j, item, params)
			if s then
				return s
			end
		end
	elseif type(event) ~= 'string' then
		 return formatError('invalid-datatype', type(event), 'string')
	elseif string.sub(event, 1, 1) == 'Q' then -- on demande un élément utilisé dans P:P793 (événement clé)
		params.property = 'P793'
		params.targetvalue = event
		params.addcat = params.addcat or true
		return wd.getTheDate(params)
	elseif string.sub(event, 1, 1) == 'P'  then -- on demande une propriété
		params.property = event
		return wd.formatAndCat(params)
	else
		return formatError('invalid-entity-id', event)
	end
end

function wd.mainDate(entity)	
	-- essaye P580/P582
	local args = {entity = entity, addcat = true}
	
	args.property = 'P580'
	local startpoint = wd.formatStatements(args)
	args.property = 'P582'
	local endpoint = wd.formatStatements(args)

	local str
	if (startpoint or endpoint) then
		str = modules.formatDate.daterange(startpoint, endpoint, params)
		str = wd.addLinkBack(str, entity, 'P582')
		return str
	end

	-- défaut : P585
	args.property = {'P585', 'P571'}
	args.linkback = true
	return wd.formatStatements(args)
end

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

function wd.getIds(item, query)
	query.excludespecial = true
	query.displayformat = 'raw'
	query.entity = item
	query.addstandardqualifs = '-'
	return wd.stringTable(query)
end


-- recursively adds a list of qid to an existing list, based on the results of a query
function wd.addVals(list, query, maxdepth, maxnodes, stopval)
	maxdepth = tonumber(maxdepth) or 10
	maxnodes = tonumber(maxnodes) or 100
	if (maxdepth < 0) then
		return list
	end
	if stopval and wd.isHere(list, stopval) then
		return list
	end
	local origsize = #list
	for i = 1, origsize do
		-- tried a  "checkpos" param instead of starting to 1 each time, but no impact on performance
		local candidates = wd.getIds(list[i], query)
		list = wd.addNewValues(list, candidates, maxnodes, stopval)
		if list[#list] == stopval then
			return list
		end
		if #list >= maxnodes then
			return list
		end
	

end
	if (#list == origsize) then
		return list
	end
	return wd.addVals(list, query, maxdepth - 1, maxnodes, stopval, origsize + 1)
end

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

function wd.transitiveVals(item, query, maxdepth, maxnodes, stopval)
	maxdepth = tonumber(maxdepth) or 5
	if type(query) == "string" then
		query = {property = query}
	end

	-- récupération des valeurs
	local vals = wd.getIds(item, query)
	if not vals then
		return nil
	end
	local v = wd.addVals(vals, query, maxdepth - 1, maxnodes, stopval) 
	if not v then
		return nil
	end

	-- réarrangement des valeurs
	if query.valorder == "inverted" then
		local a = {}
		for i, j in pairs(v) do
			table.insert(a, 1, j)
		end
		v = a
	end

	return v
end

-- returns true if an item is the value of a query, transitively
function wd.inTransitiveVals(searchedval, sourceval, query, maxdepth, maxnodes )
	local vals = wd.transitiveVals(sourceval, query, maxdepth, maxnodes, searchedval )
	if (not vals) then 
		return false
	end
	for _, val in ipairs(vals) do
		if (val == searchedval) then
			return true
		end
	end
	return false
end

-- returns true if an item is a superclass of another, based on P279
function wd.isSubclass(class, item, maxdepth)
	local query = {property = 'P279'}
	if class == item then -- item is a subclass of itself iff it is a class
		if wd.getIds(item, query) then
			return true
		end
		return false
	end
	return wd.inTransitiveVals(class, item, query, maxdepth )
end

-- returns true if one of the best ranked P31 values of an item is the target or a subclass of the target
-- rank = 'valid' would seem to make sense, but it would need to check for date qualifiers as some P31 values have begin or end date
function wd.isInstance(targetclass, item, maxdepth)
	maxdepth = maxdepth or 10
	local directclasses = wd.transitiveVals(item, {property = 'P31'}, 1)
	if not directclasses then
		return false
	end 
	for i, class in pairs(directclasses) do
		if wd.isSubclass(targetclass, class, maxdepth - 1) then
			return true
		end
	end
	return false
end

-- return the first value in a transitive query that belongs to a particular class. For instance find a value of P131 that is a province of Canada
function wd.findVal(sourceitem, targetclass, query, recursion, instancedepth)
	if type(query) == "string" then
		query = {property = query}
	end
	local candidates = wd.getIds(sourceitem, query)
	if candidates then
		for i, j in pairs(candidates) do
			if wd.isInstance(targetclass, j,  instancedepth) then
				return j
			end
		end
		if not recursion then
			recursion = 3
		else
			recursion = recursion - 1
		end
		if recursion < 0 then
			return nil
		end
		for i, candidate in pairs(candidates) do
			return wd.findVal(candidate, targetclass, query, recursion, instancedepth)
		end
	end
end


-- === VARIA ===
function wd.getDescription(entity, lang)
	lang = lang or defaultlang

	local description
	if lang == defaultlang then
		return  mw.wikibase.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
"https://qu.wikipedia.org/w/index.php?title=Módulo:Wikidata&oldid=646866" p'anqamanta chaskisqa (Wikipedia, Qhichwa / Quechua)