Mine sisu juurde

Moodul:Wikidata2

Allikas: Vikipeedia
Mooduli dokumentatsioon

This module gives format to the data extracted from Wikidata

Simple functions

  • claim: It gives the value or values of a qualifier, in the format we ask for. By default, it uses the item (Qnnnn) of the article it is transcluded.

Other functions:

  • getParentValues: it gives the values and labels of a propiety, recursively, up to the limit we say.
  • getTAValue: it gives the value of TA98 (first edition of Terminologia Anatomica, 1998) in the property P1323. There're not extra parameters for this function, but it gives a list with the correct external link format.
  • ViewSomething: we can see anything inside the data, including labels, descriptions, references or interwiki links.
  • years_old: it gives the age of a person. If the person is dead it gives the difference between P569 and P570. If there's not a P570 property, it gives the difference between P569 and the current date. According to the precision of the data, it could give two different numbers. If one of the dates is given in century format, there won't be an answer.
    • The syntax is these: {{#invoke:Wikidata |years_old|formatting=''pattern''|item=}}. item and formatting are not mandatory. And example would be formatting = ($1 years)
Full syntax
{{#invoke:Wikidata|claim|property= |qualifier= |value= |list= |tablesort= |formatting= |separator= |conjunction= |item= |showerrors= |default= }}
If we want it in a table format:
{{#invoke:Wikidata|claim|property= |qualifier= |qualifier2= |...|qualifier9= |tablesort= |rowformat= |colformat0= | colformat1 = |...|rowsubformat1= |...|rowsubformat9= |formatting=table |separator= |conjunction= |item= |showerrors= |default= }}
Functions for the claim function
  • property= (mandatory) The property we want to get, "P" followed by the number. For the full list of properties in Wikidata, see List of properties. You can write it with a little p, but is not recommended. You can even use the label of the property, for example property=country would be the same as property=P17.
  • qualifier= (optional) The proprety of the qualifier, a "P" followed by a number. It would give the link to the qualifier item. You can write it with a little p, but is not recommended.
  • value= (optional) It inserts a preferent value to override the Wikidata item. You can use it inside a template, with a {{{parameter|}}} value.
  • list= (optional). By default list=true, and it gives all the claims of the quliafier (see separator and conjunction).
    • list=false It gives a unique value. It would give the highest ranked one from preferent/normal/deprecated or the oldest one if all are of the same rank.
    • list=firstrank It gives a list of all the items of the highest level. Is the same as list=true if all the elements are of the same level.
    • tablesort=0 (optional) it orders the list from bottom to top. By default the order would be according to the level and age of the claims.
  • formatting= (optional) The format we want to give to the claim:
    • Format of the element (entity):
      • formatting=raw It gives the Qnnnn value of the element.
      • formatting=label It gives the label of the element.
      • formatting=sitelink It gives the link of the Wikipedia link for that element.
      • formatting=internallink Internal link if possible. This can be a sitelink or a label. If there's not a link but there's a label, it will give a red link to that label. If there's not a label, it will give the English label by default.
      • formatting=pattern A pattern we give, using $1 as the element to change.
      We can insert templates and parser functions, but we need to use special templates:{{((}}Name of the template{{!}}Parameter{{!}}Parameter{{))}}
      The default formatting will be a link.
    • Text format (strings):
      • formatting=weblink External link format [http://example.com example.com]
      • formatting=pattern A pattern we give, using $1 as the element to change. Example: formatting=[http://whc.unesco.org/en/list/$1 $1]. We can use it in the cases where Wikidata creates external links, and the correct pattern will be in the Talk of the Propiety.
    • Number format (quantity):
      • formatting=unit The number and the unit
      • formatting=unitcode The number and the abbr. of the unit. It needs ca:Module:Wikidata/Units.
    • Language format (monolingualtext):
      • formatting=language It gives the language of the parameter we want to get, for the items that are formatted as monolingual text, i.e. P1448.
      • formatting=text a special version of the format monolingual text, it only gets the text. If we don't use formatting we would get the text and the language code as in <span lang="en">United...</span>.
      • formatting=eu or another language. It gives a unique value, in this case the monolingual text value for the Basque language. This can be used with P2521.
    • Format for coordinates (globecoordinate) --> but for better uses see ca:Template:Cal coor:
      • formatting=latitude It gives the latitude in decimal format.
      • formatting=longitude It gives the longitude in decimal format
      • formatting=dimension It gives the dimension of the coordinates, in meters, so we can use with the dim parameter of the maps.
      • formatting=globe (by default) it gives the globe parameter for the coordinate template. If we use an external object, you could use ca:Mòdul:Mapa cos celeste/dades
    • Time format (time):
      • formatting= format #time it works with any #time format. For example formatting=d-m-Y (17-11-2024), formatting=[[j xg]] (17 novembri), formatting=Y (2024). By default uses "j F Y" (17 november 2024).
    • Table format for properties and qualifiers
      • formatting=table It gives a list of properties, in a table format, with its qualifiers. It can have up to 9 qualifiers, and every group will be inserted in a row. It uses the separator and conjunction separators as parameters. You can use a lot of different options:
        • qualifier2 ... qualifier9= if you need more than one qualifier
        • rowformat= Formats every row of properties and qualifiers. We use $0 to mark the property, and from $1 to $9 to mark the qualifiers. By default it gives rowformat=$0 ($1, ... $9). We can use list items as * and #, and also templates with the syntax {{((}}, {{!}} and {{))}}.
        • rowsubformat1 ... rowsubformat9= you can define the subformat of every item from $1 to $9. It would be very useful if any qualifier is not present in the item. For example, if you have "rowformat=$0 $1" and "rowsubformat1= or $1" would give "$0 or $1" if both are present and "$0" if there is not "$1".
        • colformat0 ... colformat9= It gives format to every column, using 0 for the property and 1-9 for the qualifiers. We can use any parameter defined in formatting. So we can make extra operations with every column.
        • tablesort= With values from 0 to 9, it orders the table according to that property or qualifier. By default the table is ordered according to the level or age of the Wikidata items in $0.
        • sorting=-1 it gives just the opposite sorting for the ordering. By default is from bottom to top.
  • separator= (optional) for using both in lists and tables. By default, is a comma (, ). In tables, by default, is a line break: <br />.
  • conjunction= (optional) it inserts a conjunction in the last elements of a list. By default is and . In tables is a line break: <br />.
  • case= (optional) not in the Catalan Module. You can see it in the Basque module. You can give declension to words, items...
  • item= (optional) it works with an article that is not the current. We have to insert it using Qnnnn. Use it moderately, please, as the amount of resources are finite.
  • itemgender= (optional) It gives the gender of the item, using P21.
  • showerrors= (optional) It shows the error messages.
  • default= (optional) The message to give if there's an error. It doesn't show if showerrors is on.
Parameters of getParentValues function
  • item= (optional) it allows inserting an item (Qnnnnn). This random approach to Wikidata is not recommended, as is quite resource-consuming.
  • property= (optional) The property we want to search for parents. By default is P131.
  • label= (optional) The label of the property. By default is P31.
  • valuetext= (optional) Value of the link of the property. By default is formatting=internallink.
  • upto= (optional) Where we want to stop the tree. It will give properties till is reached.
    • upto=label: a label that determines the stop signal. For example "upto=state".
    • upto=number: the number of items we want to get. By default is 10, but normally we wouldn't find so much.
  • labelshow= (optional) A filter for the kind of labels we want to show, with a slash to divide them. For example: "labelshow=municipality/region"
  • sorting= (optional) The order of the list. It does only have one possible value, "sorting=-1", in order to show just the opposite order.
  • rowformat= (optional) the way in which we show the $0 label and $1 values. By default, "rowformat=$0 = $1", but we can use others,, for example rowformat=<tr><td>'''$0'''</td><td>$1</td>
  • separator= (optional) The separator of every value couple. By default is <br />. We don't need conjunctions in this function.

Note: Labels can be changed so they fit better in the infoboxes. For example, "Municipality of Brazil" can be changed to "Municipality". We can do this using ca:Module:Wikidata/labels, making every change needed in our language.

Examples
  • Simple use:
{{#invoke:Wikidata | claim |property=P20}} in the article Jean-François Champollion
Property P20 showing the element.
  • Without link:
{{#invoke:Wikidata | claim | property=P20 | formatting=label}}
  • Default value
{{#invoke:Wikidata | claim | property=P20 | value={{{death place|}}} }} if we define {{{death place|}}} in the infobox, it will appear, if not, Paris should be rendered.
  • Date format
{{#invoke:Wikidata | claim | property=P569}} gives the birth date in the correct format
  • Values given with a pattern:
{{#invoke:Wikidata | claim | property=P214 | formatting=[http://viaf.org/viaf/$1 $1]}}
  • Unique value:
{{#invoke:Wikidata | claim | property=P18 | list=false | formatting=[[File:$1|thumb|upright=.5|Example image]]}}
It will give only the first defined image.
  • Formatting of external link
{{#invoke:Wikidata | claim | property=P856 | formatting=weblink}} in the Berlin article
  • Value list
Mall:P {{#invoke:Wikidata | claim | property=P47}} in Bilbo article
  • List with format
Mall:P {{#invoke:Wikidata | claim | property=P527 | separator=<br /> | conjunction=<br />}} in the article Basque Country
  • Coordinates:
{{#invoke:Wikidata | claim | property=P625 | formatting=latitude}}
{{#invoke:Wikidata | claim | property=P625 | formatting=longitude}}
-- version 20191023 from master @cawiki

local p = {}

-----------------------------------------------------------------------------
-- internationalisation at [[Module:Wikidata/i18n]]
local i18n = {
	["errors"] = {
		["property-not-found"] = "Property not found.",
		["qualifier-not-found"] = "Qualifier not found.",
		['not-from-content-page'] = "Do not invoke from content page. Use a template or use a module subpage like /sandbox for testing."
	},
	["datetime"] =
	{
		-- $1 is a placeholder for the actual number
		[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
		[12] = "F j, Y ga",			-- precision: hour
		[13] = "F j, Y g:ia",		-- precision: minute
		[14] = "F j, Y g:i:sa",		-- precision: second
		
		["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 positive years
		["bc-addon"] = " BC",		-- suffix for negative dates
		["ad-addon"] = ""			-- suffix for 1st century AD dates
	},
	["monolingualtext"] = '<span lang="%language">%text</span>',

	["cite"] = {					-- Cite web parameters
		["url"]			= "url",
		["title"]		= "title",
		["website"]		= "website",
		["access-date"]	= "access-date",
		["archive-url"]	= "archive-url",
		["archive-date"]= "archive-date",
		["author"]		= "author",
		["publisher"]	= "publisher",
		["quote"]		= "quote",
		["language"]	= "language",
		["date"]		= "date",
		["pages"]		= "pages"
	}
}

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

----------------------------------------------------------------------------
-- module local functions

local wiki = 
{
	langcode = mw.language.getContentLanguage().code,
	module_title = mw.wikibase.getSitelink('Q12069631'), -- title of this module for access to its /subpages
}

-- 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()
	local exist, res = pcall(require, wiki.module_title .. "/i18n")
	if exist and next(res) ~= nil then
		tableMerge(i18n, res.i18n)
		cases = res.cases
	end
end

loadI18n()

local function case(word, localcase, lang)
	if word == nil or word == '' or cases[localcase] == nil then
		return word
	end
	
	return cases[localcase](word, lang)
end

local function findLang(langcode)
	if langcode == nil or langcode == "" or mw.language.isKnownLanguageTag(langcode) == false then
		local myframe = mw.getCurrentFrame()
		langcode = myframe.args.lang
		if langcode == nil or langcode == "" or mw.language.isKnownLanguageTag(langcode) == false then
			langcode = myframe:getParent().args.lang
			if langcode == nil or langcode == "" or mw.language.isKnownLanguageTag(langcode) == false then
				if not mw.title.getCurrentTitle().isContentPage then
					langcode = myframe:preprocess( '{{int:lang}}' )
				end
				if langcode == nil or langcode == "" or mw.language.isKnownLanguageTag(langcode) == false then
					langcode = wiki.langcode
				end
			end
		end
	end
	local languages = mw.language.getFallbacksFor(langcode)
	table.insert(languages, 1, langcode)
	return languages
end

-- Helper function to obtain the default language code
function p.lang(frame)
	return findLang(frame.args[1])[1]
end

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

-- Is gender femenine? true or false
local function feminineGender(id)
	local claims = mw.wikibase.getBestStatements(id or mw.wikibase.getEntityIdForCurrentPage(),'P21')
	if #claims == 0 then
		return false
	elseif claims[1].mainsnak.datavalue == nil then -- 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 = mw.wikibase.getBestStatements(id, 'P2521') -- female form of label
	for _, feminine_claim in ipairs(feminine_claims) do
		if feminine_claim.mainsnak.datavalue.value.language == lang then
			return feminine_claim.mainsnak.datavalue.value.text
		end
	end
end

-- Fetch unit symbol
local function unitSymbol(id, lang)
	local claims = findClaims(id, 'P5061')
	local langclaims = {}
	if claims then
		for _, snak in ipairs(claims) do
			if snak.mainsnak and snak.mainsnak.datavalue and snak.mainsnak.datavalue.value and
				not langclaims[snak.mainsnak.datavalue.value.language] -- just the first one by language
				then
					langclaims[snak.mainsnak.datavalue.value.language] = snak.mainsnak.datavalue.value.text
			end
		end
		for _, l in ipairs(lang) do
			if langclaims[l] then
				return langclaims[l]
			end
		end
	end
	return langclaims["mul"] -- last try
end

-- Add a small pencil as icon for edit on Wikidata
local function addEditIcon(id, lang, uselang, icon)
	if icon and lang ~= uselang then
		return " [[File:Arbcom ru editing.svg|12px|" .. mw.message.new('Translate-taction-translate'):inLanguage(uselang):plain() .. "|link=d:" .. id .. "]]"
	end
	return ''
end

-- Escape URL escapes to avoid Lua captures
local function urlEscapes(text)
	return mw.ustring.gsub(text, "(%%%d)", "%%%1")
end

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 = urlEscapes(braces_expanded)
		text = mw.ustring.gsub(text, "{{" .. braces .. "}}", braces_expanded)
	end
	
	return text
end

local function resolveEntityId(id) -- pending phab:T157868
	if not id or not mw.wikibase.isValidEntityId(id) then return id end
	-- if no label in local language nor its fallbacks, maybe it is a redirect
	-- not using mw.title.new(id).isRedirect as it is expensive
	if mw.wikibase.getLabel(id) == 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 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

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

local function printDatavalueString(data, parameters)
	if parameters.formatting == 'weblink' then 
		return '[' .. data .. ' ' .. mw.text.split(data, '//' )[2] .. ']'
	elseif mw.ustring.find((parameters.formatting or ''), '$1', 1, true) then -- formatting = a pattern
		return expandBraces(mw.ustring.gsub(parameters.formatting, '$1', data), parameters.formatting)
	elseif parameters.case then
		return case(data, parameters.case, parameters.lang[1])
	else
		return data
	end
end

local function printDatavalueUrl(data, parameters)
	return printDatavalueString(urlEscapes(data), parameters)
end
	
local function printDatavalueCoordinate(data, parameter)
	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'}
	if parameter and mw.ustring.find(parameter, '$lat', 1, true) and mw.ustring.find(parameter, '$lon', 1, true) then
		local ret = mw.ustring.gsub(mw.ustring.gsub(parameter, '$lat', data.latitude), '$lon', data.longitude)
		if mw.ustring.find(parameter, '$globe', 1, true) then
			local myglobe = 'earth'
			if data.globe and 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, parameter)
	elseif parameter == 'latitude' then
		return data.latitude
	elseif parameter == 'longitude' then
		return data.longitude
	elseif parameter == 'dimension' then
		return data.dimension
	else --default formatting='globe'
		if data.globe == '' or data.globe == nil or data.globe == 'http://www.wikidata.org/entity/Q2' then
			return 'earth'
		else
			local globenum = mw.text.split(data.globe, 'entity/')[2]
			return globes[globenum] or globenum
		end
	end
end

local function roundPrecision(in_num, out_num)
	-- rounds out_num with default precision of in_num
	-- 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

local function printDatavalueQuantity(data, parameters)
	local amount = data.amount
	amount = mw.ustring.gsub(amount, "%+", "")
	local sortkey = string.format("%09d", amount)
	local suffix = ""
	if string.sub(parameters.formatting or '', 1, 4) == "unit" or parameters.convert then
		-- example "unit": "http://www.wikidata.org/entity/Q174728"
		local unitID = data.unit
		unitID = mw.ustring.sub(unitID, mw.ustring.find(unitID, "Q"), -1)
		if string.sub(unitID, 1, 1) == "Q" then
			if parameters.convert then
				-- convert units
				local factor, conv_unit
				local conversion = mw.wikibase.getAllStatements(unitID, 'P2442') -- conversion to standard unit
				table.insert(conversion, mw.wikibase.getBestStatements(unitID, 'P2370')[1]) -- conversion to SI unit
				local conversion_string = mw.wikibase.getAllStatements(unitID, 'P370') -- Sandbox-String, i.e. Q42289
				for _, conv in ipairs(conversion_string) do
					table.insert(conversion, conv)
				end
				for _, conv in ipairs(conversion) do
					if conv.mainsnak.snaktype == 'value' then -- no somevalue nor novalue
						if conv.mainsnak.datatype == 'string' then
							conv_unit = conv.qualifiers['P2210'][1].datavalue.value.id
							if conv_unit == parameters.convert then
								factor = conv.mainsnak.datavalue.value
								local amount_f = mw.getCurrentFrame():callParserFunction('#expr', mw.ustring.gsub(factor, "$1", amount))
								amount = roundPrecision(amount, tonumber(amount_f))
								unitId = conv_unit
								break
							end
						else
							conv_unit = conv.mainsnak.datavalue.value.unit
							if conv_unit == "http://www.wikidata.org/entity/" .. parameters.convert then
								factor = tonumber(conv.mainsnak.datavalue.value.amount)
								amount = roundPrecision(amount, amount * factor)
								unitID = mw.ustring.sub(conv_unit, mw.ustring.find(conv_unit, "Q"), -1)
								break
							end
						end
					end
				end
			end
			if parameters.formatting == "unitcode" then
				-- get unit symbol
				local unit_symbol = ''
				if parameters.lang[1] == wiki.langcode and pcall(require, wiki.module_title .. "/Units") then
					unit_symbol = require(wiki.module_title .. "/Units").getUnit(amount, '', unitID, true, '')
				end
				if unit_symbol == '' then
					unit_symbol = unitSymbol(unitID, parameters.lang)
				end
				if unit_symbol then
					suffix = " " .. unit_symbol
				end
			end
			if suffix == '' then
				-- get unit name
				local unit_label, lang = getLabelByLangs(unitID, parameters.lang)
				if lang == wiki.langcode and pcall(require, wiki.module_title .. "/Units") then
					suffix = " " .. require(wiki.module_title .. "/Units").getUnit(amount, unit_label, unitID, false, '')
				else
					suffix = " " .. (unit_label or unitID) .. addEditIcon(unitID, lang, parameters.lang[1], parameters.editicon)
				end
			end
		end
	end
	amount = mw.language.new(parameters.lang[1]):formatNum(tonumber(amount))
	return amount .. suffix, sortkey
end

local function printDatavalueTime(data, parameters)
	-- Dates and times are stored in ISO 8601 format
	local timestamp = data.time
	local sortkey = timestamp
	local addon = ""
	local calendar_add = ""
	
	-- check for negative date, ex. "-0027-01-16T00:00:00Z"
	if string.sub(timestamp, 1, 1) == '-' then
		timestamp = '+' .. string.sub(timestamp, 2)
		addon = i18n.datetime["bc-addon"]
	elseif string.sub(timestamp, 2, 3) == '00' then
		addon = i18n.datetime["ad-addon"]
	else
		-- 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 d(f, t)
		local ts = t or timestamp
		local form = type(f) == "function" and f(ts) or f -- function in i18n.datetime[precision]
		return mw.language.new(parameters.lang[1]):formatDate(form, ts) .. addon .. calendar_add
	end
	local precision = data.precision or 11
	local intyear = tonumber(mw.ustring.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 = mw.ustring.gsub(i18n.datetime[precision], "$1", tostring(y2))
		if addon == i18n.datetime["bc-addon"] then
			-- negative date
			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 then -- millennia
		local card = math.floor((intyear - 1) / 1000) + 1
		if mw.ustring.find(i18n.datetime[6], "$1") then
			ret = mw.ustring.gsub(i18n.datetime[6], "$1", tostring(card)) .. addon .. calendar_add
		else
			ret = d(i18n.datetime[6], string.format("%04d", tostring(card)))
		end
	elseif precision == 7 then -- centuries
		local card = math.floor((math.abs(intyear) - 1) / 100) + 1
		if mw.ustring.find(i18n.datetime[7], "$1") then
			ret = mw.ustring.gsub(i18n.datetime[7], "$1", tostring(card)) .. addon .. calendar_add
		else
			ret = d(i18n.datetime[7], string.format("%04d", tostring(card)))
		end
	elseif precision == 8 then -- decades
		local card = math.floor(math.abs(intyear) / 10) * 10
		ret = mw.ustring.gsub(i18n.datetime[8], "$1", tostring(card)) .. addon .. calendar_add
	elseif parameters.formatting == 'Y' or precision == 9 then -- precision is year
		ret = tostring(intyear) .. addon .. calendar_add
	elseif precision == 10 then -- month
		timestamp = timestamp .. " + 1 day" -- formatDate yyyy-mm-00 returns the previous month
		ret, _ = string.gsub(d(i18n.datetime[10]), " 0+", " ") -- supress leading zeros in year
	elseif parameters.formatting then
		ret, _ = string.gsub(d(parameters.formatting), "([ %[])0+", "%1") -- supress leading zeros in year optionally linked
	else
		ret, _ = string.gsub(d(i18n.datetime[11]), " 0+", " ")
	end
	return ret, sortkey
end

local function printDatavalueEntity(data, parameters)
	local entityId = data['id']
	local entity_page = 'Special:EntityPage/' .. entityId
	if parameters.formatting == 'raw' then
		if data['entity-type'] == 'item' then
			entityId = resolveEntityId(entityId)
		end
		return entityId, entityId
	end
	local label, lang = getLabelByLangs(entityId, parameters.lang)
	local sitelink = mw.wikibase.getSitelink(entityId)
	local parameter = parameters.formatting
	local labelcase = label or sitelink
	if parameters.gender == 'feminineform' and lang ~= nil then -- case gender and item is female
		labelcase = feminineForm(entityId, lang) or labelcase
	end
	if parameters.case then
		labelcase = case(labelcase, parameters.case, lang)
	end
	local ret1, ret2
	if parameter == 'label' then
		ret1 = (labelcase or entityId)
		ret2 = labelcase or entityId
	elseif parameter == 'sitelink' then
		ret1 = (sitelink or 'wikidata:' .. entity_page)
		ret2 = sitelink or entityId
	elseif mw.ustring.find((parameter or ''), '$1', 1, true) then -- formatting = a pattern
		ret1 = mw.ustring.gsub(parameter, '$1', labelcase or entityId)
		ret1 = expandBraces(ret1, parameter)
		ret2 = labelcase or entityId
	else
		if parameter == "ucfirst" or parameter == "ucinternallink" then
			labelcase = labelcase and mw.language.new(lang):ucfirst(labelcase)
			-- 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 = '[[wikidata:' .. entity_page .. '|' .. (labelcase or entityId) .. ']]'
			ret2 = labelcase or entityId
		end
	end
	
	return ret1 .. addEditIcon(entity_page, lang, parameters.lang[1], parameters.editicon), ret2
end

local function printDatavalueMonolingualText(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(mw.ustring.gsub(i18n.monolingualtext, "%%language", data["language"]), "%%text", 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 printError(key)
	return '<span class="error">' .. i18n.errors[key] .. '</span>'
end

-- the "qualifiers" and "snaks" field have a respective "qualifiers-order" and "snaks-order" field
-- use these as the second parameter and this function instead of the built-in "pairs" function
-- to iterate over all qualifiers and snaks in the intended order.
local function orderedpairs(array, order)
	if not order then return pairs(array) end

	-- return iterator function
	local i = 0
	return function()
		i = i + 1
		if order[i] then
			return order[i], array[order[i]]
		end
	end
end

function findClaims(entityId, property)
	if not property or not entityId then return end
	
	if not mw.ustring.match(property, "^P%d+$") then
		-- get property id for the given label
		property = mw.wikibase.resolvePropertyId(property)
		if not property then return end
	end
	local claims = mw.wikibase.getAllStatements(entityId, property)
	if #claims == 0 then
		claims = nil
	end
	return claims
end

local function getSnakValue(snak, parameters)
	if snak.snaktype == 'value' then
		-- call the respective snak parser
		if snak.datatype == 'math' then
			return printDatatypeMath(snak.datavalue.value)
		elseif snak.datatype == "url" then
			return printDatavalueUrl(snak.datavalue.value, parameters)
		elseif snak.datavalue.type == "string" then
			return printDatavalueString(snak.datavalue.value, parameters)
		elseif snak.datavalue.type == "globecoordinate" then
			return printDatavalueCoordinate(snak.datavalue.value, parameters.formatting)
		elseif snak.datavalue.type == "quantity" then
			return printDatavalueQuantity(snak.datavalue.value, parameters)
		elseif snak.datavalue.type == "time" then
			return printDatavalueTime(snak.datavalue.value, parameters)
		elseif snak.datavalue.type == 'wikibase-entityid' then
			return printDatavalueEntity(snak.datavalue.value, parameters)
		elseif snak.datavalue.type == 'monolingualtext' then
			return printDatavalueMonolingualText(snak.datavalue.value, parameters)
		end
	elseif snak.snaktype == 'novalue' then
		return mw.message.new('Wikibase-snakview-snaktypeselector-novalue'):inLanguage(parameters.lang[1]):plain()
	elseif snak.snaktype == 'somevalue' then
		return mw.message.new('Wikibase-snakview-snaktypeselector-somevalue'):inLanguage(parameters.lang[1]):plain()
	end
	return mw.wikibase.renderSnak(snak)
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 qualifier[idx].datavalue.value and 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

function getValueOfClaim(claim, qualifierId, parameters)
	local error
	local snak
	snak, error = getQualifierSnak(claim, qualifierId, parameters)
	if not snak then
		return nil, nil, error
	elseif snak[1] then -- a multi qualifier
		local result = {}
		local sortkey = {}
		for idx in pairs(snak) do
			result[#result + 1], sortkey[#sortkey + 1] = getSnakValue(snak[idx], parameters)
		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

local function getReferences(claim)
	local refaliases = {
		citeWeb			= "Q5637226",
		author			= "P50",
		publisher		= "P123",
		importedFrom	= "P143",
		statedIn		= "P248",
		pages			= "P304",
		publicationDate	= "P577",
		startTime		= "P580",
		endTime			= "P582",
		chapter			= "P792",
		retrieved		= "P813",
		referenceURL	= "P854",
		archiveURL		= "P1065",
		title			= "P1476",
		quote			= "P1683",
		shortName		= "P1813",
		language		= "P2439",
		archiveDate		= "P2960"
	}
	local result = ""
	-- traverse through all references
	for ref in pairs(claim.references or {}) do
		local refparts
		local refs = {}
		-- traverse through all parts of the current reference
		for snakkey, snakval in pairs(claim.references[ref].snaks or {}) do
			if snakkey ~= refaliases.importedFrom then -- "imported from" is not a proper reference
				for snakidx = 1, #snakval do
					if snakidx > 1 then refparts = refparts .. ", " end
					refparts = refparts or '' .. getSnakValue(snakval[snakidx], {lang={wiki.langcode}})
				end
				refs[snakkey] = refparts
				refparts = nil
			end
		end
		
		-- get title of general template for citing web references
		local template = mw.wikibase.getSitelink(refaliases.citeWeb) or ""
		template = mw.text.split(template, ":")[2] -- split off namespace from front
		
		-- (1) if both "reference URL" and "title" are present, then use the general template for citing web references
		if refs[refaliases.referenceURL] and (refs[refaliases.title] or refs[refaliases.statedIn]) and template then
			local citeParams = {}
			citeParams[i18n['cite']['url']] = refs[refaliases.referenceURL]
			citeParams[i18n['cite']['title']] = refs[refaliases.title] or refs[refaliases.statedIn]:match("^%[%[.-|(.-)%]%]")
			citeParams[i18n['cite']['website']] = refs[refaliases.statedIn]
			citeParams[i18n['cite']['language']] = refs[refaliases.language]
			citeParams[i18n['cite']['date']] = refs[refaliases.publicationDate]
			citeParams[i18n['cite']['access-date']] = refs[refaliases.retrieved]
			citeParams[i18n['cite']['archive-url']] = refs[refaliases.archiveURL]
			citeParams[i18n['cite']['archive-date']] = refs[refaliases.archiveDate]
			citeParams[i18n['cite']['publisher']] = refs[refaliases.publisher]
			citeParams[i18n['cite']['quote']] = refs[refaliases.quote]
			citeParams[i18n['cite']['pages']] = refs[refaliases.pages]
			citeParams[i18n['cite']['author']] = refs[refaliases.author]
			refparts = mw.getCurrentFrame():expandTemplate{title=template, args=citeParams}
		else
			-- raw ouput
			for k, v in orderedpairs(refs or {}, claim.references[ref]["snaks-order"]) do
				if k and v then
					if refparts then refparts = refparts .. ", " else refparts = "" end
					refparts = refparts .. tostring(mw.wikibase.getLabel(k)) .. ": "
					refparts = refparts .. v
				end
			end
		end
		if refparts then result = result .. mw.getCurrentFrame():extensionTag("ref", refparts) end
	end
	return result
end

-- Move to Module:Wikibase?
function p.getSiteLink(frame)
	require(wiki.module_title .. '/debug').track('getSiteLink')
	local id = frame.args[1]; if id == '' then id = nil end
	return mw.wikibase.getSitelink(id or mw.wikibase.getEntityIdForCurrentPage(), frame.args[2])
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 args[type_list .. i] and args[type_list .. i] ~= '' then
			listed = true
			list[tostring(i)] = {}
			local values = mw.text.split(args[type_list .. i], "/", true)
			for _, v in ipairs(values) do
				list[tostring(i)][v] = true
				list[tostring(i)][resolveEntityId(v)] = true
			end
		end
	end
	return list, listed
end

local function tableParameters(args, parameters, column, gender)
	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
		if args["case"..column] == "gender" then
			column_params.gender = gender
		else
			column_params.case = args["case"..column]
		end
		
	end
	return column_params
end

function p.claim(frame)
	if mw.title.new(frame:getParent():getTitle()).isContentPage then
		if not mw.title.new(frame:getTitle()).isSubpage then
			-- invoked from a content page and not invoking a module subpage
			return printError("not-from-content-page")
		end
	end
	return p._main(frame.args, frame:getParent().args)
end

-- Entry point from other modules or debugging, with a list of arguments.
-- From other modules use: require("Module:Wikidata")._main({item="Q...", property="P...", ...})
-- On debug console use: =p._main({item="Q...", lang="xx", property="P...", ...})
function p._main(args, pargs)
	--If a value is already set, use it
	if args.value and args.value ~= '' then
		return args.value
	end
	
	-- arguments
	local id = args.item or (pargs and pargs.item)
	if id == nil or id == "" then
		id = mw.wikibase.getEntityIdForCurrentPage()
	end
	local property = string.upper(args["property"] or "")
	local qualifierId = {}
	qualifierId[1] = args["qualifier"] and string.upper(args["qualifier"]) or nil
	local i = 2
	while args["qualifier" .. i] do
		qualifierId[i] = string.upper(args["qualifier" .. i])
		i = i + 1
	end
	local formatting = args["formatting"]; if formatting == "" then formatting = nil end
	local convert = args["convert"]; if convert == "" then convert = nil end
	if convert and string.sub(convert, 1, 1) ~= "Q" then convert = nil end
	local case = args.case
	local list = args["list"] or true; if (list == "false" or list == "no") then list = false end
	local sorting_col = args.tablesort
	local sorting_up = (args.sorting or "") ~= "-1"
	local separator = args.separator
	local conjunction = args.conjunction or args.separator
	local rowformat = args.rowformat
	local references = args["references"]
	local showerrors = args["showerrors"]
	local default = args["default"]
	local editicon = not (args.editicon == "false" or args.editicon == "no")
	
	local parameters = {["property"] = property, ["formatting"] = formatting, ["convert"] = convert,
		["list"] = list, ["case"] = case, ["editicon"] = editicon,
		["separator"] = separator, ["conjunction"] = conjunction, ["qseparator"] = separator, ["qconjunction"] = conjunction}
	
	-- 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
	if default then showerrors = nil end
	
	-- fetch property
	local claims = findClaims(id, property)
	if not claims or not claims[1] then
		if showerrors then return printError("property-not-found") else return default end
	end
	
	-- find language and defaults
	parameters.lang = findLang(args.lang)
	
	-- 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 = mw.wikibase.getBestStatements(id, itemgender)[1]
			if snak and snak.mainsnak and snak.mainsnak.datavalue and snak.mainsnak.datavalue.value then
				idgender = snak.mainsnak.datavalue.value.id
			end
		elseif string.match(itemgender, "^Q%d+$") then
			idgender = itemgender
		end
	end
	local gender_case
	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
			gender_case = "feminineform"
			if parameters.case == "gender" or idgender then
				parameters.gender = gender_case
			end
		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
	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 firstrank = parameters.list == "firstrank" and claims[sortindices[1]].rank or ''
		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 firstrank ~= '' and firstrank ~= claim.rank then
				break
			end
			if parameters.formatting == "table" then
				local params = tableParameters(args, parameters, "0", gender_case)
				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, gender_case)
						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 = findClaims(id, string.sub(q, 2))
									if claim2 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, '/', true)
			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
		result = {}
		for idx in pairs(values) do
			local valuerow = values[sortindices[idx]]
			local reference = getReferences({["references"] = refs[sortindices[idx]]["col0"]})

			value = valuerow["col0"]
			if parameters.formatting == "table" then
				if not rowlist[sortindices[idx]] then
					value = nil
				else
					value = mw.ustring.gsub(rowformat .. "$", "$0", value) -- fake end character added for easy gsub
					value = mw.ustring.gsub(value, "$R0", reference) -- add reference
					local rowformatting = rowformat .. "$"
					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 = 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 urlEscapes(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
			else
				value = expandBraces(value, parameters.formatting)
			end
			result[#result + 1] = value
			if not parameters.list then
				break
			end
		end
		result = preformat .. mw.text.listToText(result, parameters.separator, parameters.conjunction) .. postformat
	else
		-- return first element
		local claim = claims[sortindices[1]]
		result, _, error = getValueOfClaim(claim, qualifierId[1], parameters)
		if result and references then result = result .. getReferences(claim) end
	end
	
	if result then return result else
		if showerrors then return error else return default end
	end
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
p.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

-- debugging functions, see module ../debug.
function p.ViewSomething(frame)
	return require(wiki.module_title .. "/debug").ViewSomething(frame)
end

function p.Dump(frame)
	return require(wiki.module_title .. "/debug").Dump(frame)
end

function p.getEntityFromTree(frame)
	return require(wiki.module_title .. "/debug").getEntityFromTree(frame)
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
	if snaks and snaks[1] and snaks[1].mainsnak then
		mysnak = snaks[1].mainsnak
	else
		return
	end
	
	local entityId
	local result = '-' -- default for 'no value'
	if mysnak.datavalue then
		entityId = "Q" .. tostring(mysnak.datavalue.value['numeric-id'])
		result, _ = getSnakValue(mysnak, {formatting=parameter, lang=langs, editicon=editicon, case=case})
	end
	
	return entityId, result
end

local function contains(tab, val)
	for index, value in ipairs(tab) do
		if value == val then
			return true
		end
	end
	
	return false
end

local function getParentObjects(id,
	formatting,
	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 = mw.text.split(upto_linkId, '[/%s]+')
	local propertySups = mw.text.split(propertySupString, '[/%s]+')
	
	local lastlabel = uc_first(upto or '')
	local maxloop = tonumber(upto) or (lastlabel == '' and 10 or 50)
	
	local labelFilter = {}
	if labelShow then
		for i, v in ipairs(mw.text.split(labelShow, "/")) do
			labelFilter[uc_first(v)] = true
		end
	end
	
	local label_self
	_, label_self = getPropertyValue(id, propertyLabel, "label", languages)
	
	local result = {}
	
	local label, link, linktext
	
	for iter = 1, maxloop do
		local label, link
		for _, propertySup in pairs(propertySups) do 	
			_id, _link = getPropertyValue(id, propertySup, formatting, 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", languages)
		
		if label then 
			label = case(label, "infoboxlabel", languages[1])
		end
		
		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 not tonumber(upto) and label == lastlabel then
			break
		end
		
		if contains(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,
	separator, 
	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 mw.text.listToText(ret, separator, separator)
end

-- Returns pairs of instance label and property value fetching a recursive tree
function p.getParentValues(frame)
	local args = frame.args
	local id = args.item or frame:getParent().args.item
	if id == "" or id == nil then
		id = mw.wikibase.getEntityIdForCurrentPage()
		if id == nil then return end
	end
	local languages = findLang(args.lang)
	local propertySup = args["property"]; if (propertySup == nil or propertySup == "") then propertySup = "P131" end --administrative entity
	local propertyLabel = args["label"]; if (propertyLabel == nil or propertyLabel == "") then propertyLabel = "P31" end --instance
	local propertyLink = args["valuetext"]; if propertyLink == "" then propertyLink = nil end --internallink
	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 args.uptolabelid and args.uptolabelid ~= "" then
		upto = mw.wikibase.getLabel(args.uptolabelid)
	end
	
	if args.showlabelid and args.showlabelid ~= "" then
		local showLabelList = {} for substring in mw.text.gsplit(args.showlabelid, '[/%s]+') do table.insert(showLabelList, mw.wikibase.getLabel(substring)) end
		if #showLabelList > 0 then
			labelShow = table.concat(showLabelList,"/")
		end
	end
	
	local result = getParentObjects(id,
		args.formatting,
		languages, 
		propertySup, 
		propertyLabel,
		propertyLink,
		labelShow,
		editicon,
		upto,
		args.uptolinkid,
		last_only,
		case,
		include_self)
	
	local rowformat = args["rowformat"]; if (rowformat == nil or rowformat == "") then rowformat = "$0 = $1" end
	local separator = args["separator"]; if (separator == nil or separator == "") then separator = "<br />" end
	local sorting = args["sorting"]; if sorting == "" then sorting = nil end
	local cascade = (args.cascade == "true" or args.cascade == "yes")
	
	return parentObjectsToString(result,
		rowformat,
		separator,
		cascade,
		sorting)
end

function p.linkWithParentLabel(frame)
	local args = {}
	for k, v in pairs(frame.args) do -- metatable
		args[k] = v
	end
	
	-- get internal link of property/qualifier
	args.list = "true"
	args.formatting = "internallink"
	args.separator = "/·/"
	local pargs = frame:getParent().args
	local link_list = p._main(args, pargs)
	if link_list == nil or link_list == "" then
		return
	end
	local link_table = mw.text.split(link_list, "/·/", true)
	
	-- get id value of property/qualifier
	args.formatting = "raw"
	local items_list = p._main(args, pargs)
	local items_table = mw.text.split(items_list, "/·/", true)
	
	-- get label of parent property
	args.property = args.parent
	args.qualifier = nil
	local parent_claims = findClaims(items_table[1], args.parent)
	if parent_claims and parent_claims[1].mainsnak.datatype == 'monolingualtext' then
		args.formatting = nil
		args.list = 'lang'
	else
		args.formatting = "label"
		args.list = "false"
	end
	for i, v in ipairs(items_table) do
		args.item = v
		local link_label = p._main(args, pargs)
		if link_label and link_label ~= '' then
			link_table[i] = mw.ustring.gsub(link_table[i] or '', "%[%[(.*)%|.+%]%]", "[[%1|" .. link_label .. "]]")
		end
	end
	
	return mw.text.listToText(link_table)
end

function p.years_old(frame)
	require(wiki.module_title .. '/debug').track('years_old')
	return p.yearsOld(frame)
end

function p.yearsOld(frame)
	local args = frame.args
	local id = args.item
	if id == '' or id == nil then
		id = mw.wikibase.getEntityIdForCurrentPage()
	end
	local lang = mw.language.new('en')
	
	local function getBestValue(id, prop)
		local snak = mw.wikibase.getBestStatements(id, prop)[1]
		if snak and snak.mainsnak and snak.mainsnak.datavalue and snak.mainsnak.datavalue.value then
			return snak.mainsnak.datavalue.value
		end
	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_min == old_max and old_min or old_min .. "/" .. old_max
	if args.formatting then
		old = expandBraces(mw.ustring.gsub(args.formatting, '$1', old), args.formatting)
	end
	
	return old
end

-- Gets a label in a given language (content language by default) or its fallbacks, optionnally linked.
function p.getLabel(frame)
	local args = frame.args or frame -- via invoke or require
	local id = mw.text.trim(args[1] or "")
	if not id or id == "" then return end
	local editicon = not (args.editicon == "false" or args.editicon == "no")
	
	local pencil = ''
	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
			id = resolveEntityId(id)
			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
			pencil = addEditIcon('Special:EntityPage/' .. id, lang, languages[1], editicon)
		end
	end
	
	local linked = args.linked
	if linked and linked ~= "" and linked ~= "no" then
		local article = mw.wikibase.getSitelink(id) or ("d:" .. id)
		return "[[" .. article .. "|" .. (label or id) .. "]]" .. pencil
	else
		return (label or id) .. pencil
	end
end

return p