Vés al contingut

Mòdul:Wikidata

De Viquitexts

Aquest mòdul extrau dades formatades de Wikidata. Funciona en la llengua local del wiki, o en una llengua específica per a proves, i té opcions de configuració.

Funcions

Funció bàsica:

  • claim: Retorna el valor (o els valors) d'una declaració o d'un qualificador formatat amb paràmetres o amb un format per defecte segons el tipus de dada.

Altres funcions:

  • getLabel: retorna una etiqueta en la llengua especificada, o la llengua per defecte.
  • getParentValues: retorna etiquetes i valors superiors d'una propietat de forma recursiva.
  • linkWithParentLabel: crea un enllaç amb l'etiqueta d'una propietat superior. Per exemple per enllaçar P405 amb l'etiqueta P835 definida en el corresponent ítem superior.

Altres utilitats: getSiteLink, lang, numStatements.

Funció claim

Retorna el valor (o els valors) d'una declaració o d'un qualificador formatat amb paràmetres o amb un format per defecte segons el tipus de dada. Per defecte està referit a l'ítem de Wikidata (Qid) associat a la pàgina actual.

Sintaxi completa:

{{#invoke:Wikidata|claim|item= |lang= |property= |qualifier= |value= |list= |tablesort= |formatting= |separator= |conjunction= |editicon= |showerrors= |default= }}

Sintaxi addicional pel format de taula:

{{#invoke:Wikidata|claim|item= |lang= |property= |qualifier= |qualifier2= |...|qualifierx= |formatting=table |tablesort= |sorting= |rowformat= |rowsubformat1= |...|rowsubformatx= |colformat0= |...|colformatx= |case0= |...|casex= |separator= |conjunction= |editicon= |showerrors= |default= |references= }}

Paràmetres generals

  • item= (opcional) Permet indicar un ítem (Qid) diferent a l'ítem associat a la pàgina actual. Cal fer-lo servir amb moderació pel seu alt consum de recursos. Es pot usar també com a paràmetre de l'entorn superior de la plantilla. Té com a àlies from.
  • lang= (opcional) Permet indicar el codi d'una llengua determinada. Es pot usar també com a paràmetre de l'entorn superior de la plantilla. Per defecte usa la llengua local del wiki per a l'espai principal d'articles o la llengua d'usuari definida en les preferències per a altres espais de noms. Si no troba el valor en aquesta llengua ho intenta en les llengües alternatives definides en el MediaWiki. Per exemple per català les llengües alternatives són occità i anglès. En cas que el valor no s'hagi trobat en la llengua demanada, hi afegeix el codi de llengua i una icona d'etiqueta per a traduir-ho a Wikidata. Aquesta icona es pot eliminar amb el paràmetre editicon.
  • property= (obligatori) Identificador de la propietat de la declaració (Pid). Es poden definir diferents propietats alternatives amb qualsevol separador, per exemple "P17 o P131", i obtindrà la primera que trobi. Accepta també una p minúscula, però no és recomanat. Igualment accepta l'etiqueta de la propietat, per exemple property=estat equival a property=P17 per la propietat P17.
  • qualifier= (opcional) Qualificador (Pid) del valor de la propietat.
  • value= (opcional) Valor preferent al de Wikidata. Pot ser un paràmetre opcional en una plantilla amb el format {{{paràmetre|}}}, així si existeix pren el valor del paràmetre i si està buit llavors obté el valor de Wikidata.
    • value=NONE Amb aquest valor no treu res de Wikidata. Permet configurar un paràmetre per a que no tregui cap valor de Wikidata, o bé treure només valors informats en la plantilla amb {{{paràmetre|NONE}}}. És equivalent a la sintaxi wiki: {{#ifeq:{{{paràmetre|}}}|NONE|<!-- no res -->}}.
  • references= (opcional) Amb qualsevol valor mostrarà les referències del valor obtingut. Si troba P854 més o bé P1476 o bé P248 llavors ho formata amb la versió local de Q5637226 i amb els paràmetres traduïts a la taula i18n["cite"] de Module:Wikidata/i18n. Si troba P1433, més títol o afirmat, llavors ho formata amb Q5624899}. En cas contrari ho formata com a text.
    En cas d'usar-ho amb formatting=table (vegeu més avall) cal definir on han d'aparèixer les referències en el paràmetre "rowformat".
  • list= (opcional):
    • list=true (per defecte) Mostra una llista de tots els valors preferents i normals (vegeu separator i conjuction més avall). En cap cas obté els valors obsolets.
    • list=false o list=no Mostra només un valor, el de rang més alt segons l'ordre preferent/normal, o el més antic dels que tenen el rang més alt.
    • list=bestrank Mostra una llista dels valors amb el rang més alt. És equivalent a list=true si tots tenen el mateix rang. És equivalent a list=false si només hi ha un valor amb el rang més alt. El valor firstrank és un àlies.
    • list=lang En cas de valors monolingües només treu els corresponents a la llengua del paràmetre lang. Vegeu el paràmetre lang.
    • list=<número> Número màxim de valors a llistar. Amb list=1 és equivalent a list=false. Amb list=0 és equivalent a list=true.
    • tablesort=0 (opcional) ordenació ascendent de la llista. Per defecte l'ordenació és pel rang (preferent, normal) i per l'antiguitat de definició a Wikidata.
  • separator= (opcional) Separador a usar en llistes o taules. En cas de llistes, per defecte és MediaWiki:Comma-separator, en català una coma i un espai en blanc ', '. En cas de taules per defecte és un salt de línia <br />. En algun cas, si s'inclou en etiquetes que usen strip markers, pot ser que un <br /> no funcioni. L'alternativa és usar separator=LF per a un caràcter de control line feed.
  • conjunction= (opcional) Conjunció a usar com a separador diferent entre els dos últims elements de la llista. Per defecte és igual a separator si està definit i si no és MediaWiki:And més MediaWiki:Word-separator, en català ' i '. En el cas de taules per defecte és un salt de línia <br />.
  • case= (opcional) Cas gramatical a generar, depenent de la llengua local, o canvis a aplicar en el wiki local definits a Module:Wikidata/i18n. Com a casos generals tractats pel mòdul:
    • case=gender, segons la definició de P21 de l'element (pàgina actual, item o itemgender) treu el valor de P2521.
    • case=smallcaps, treu l'etiqueta en versaleta.
    Vegeu la documentació de Module:Wikidata/i18n per a altres casos locals.
  • itemgender= (opcional) Element on es comprova P21 per aplicar la forma femenina si fos necessari, per defecte item. S'usa en combinació amb case=gender o case=infoboxlabel.
  • editicon= (opcional) Defineix si es posa alguna icona. Per defecte és true. Amb valors editicon=false o editicon=no és false. Les icones poden ser: una icona d'etiqueta Tradueix en cas que l'etiqueta no s'hagi trobat en la llengua demanada, i un petit llapis Modifica com a icona per mostrar i poder editar la declaració a Wikitada (en el futur gestionat per mw:Wikidata Bridge). La icona del llapis es mostrarà si està definida en la configuració pel wiki a Module:Wikidata/i18n amb el valor ["addpencil"] = true.
  • showerrors= (opcional) Amb qualsevol valor mostra el missatge d'error, si n'hi ha cap. Si no està definit mostrarà el paràmetre default en cas d'error.
  • default= (opcional) Text a mostrar en cas d'error. No té cap efecte si està definit el paràmetre showerrors. Si no està definit, i tampoc el paràmetre showerrors, retornarà un valor buit en cas d'error.
  • sandbox= (opcional) Amb qualsevol valor desvia les crides al Module:Wikidata/proves. El títol de la subpàgina l'obté de MediaWiki:Sandboxlink-subpage-name, en català "proves". El paràmetre es pot usar en l'entorn de plantilla. Com a paràmetre d'invoke o de require no té sentit per les proves. A usar només provisionalment o en previsualització, el mòdul de proves no hauria de tenir cap enllaç permanent.

Paràmetres de formatació

formatting= (opcional) Format desitjat. Valors possibles, per a cada tipus de dada:

Dada amb valor d'element (entity):

  • formatting=raw Número identificador de l'element.
  • formatting=label Etiqueta de l'element en la llengua demanada, alguna de les llengües alternatives o bé com a raw.
  • formatting=sitelink Títol de la pàgina de la Viquipèdia de l'element, sense enllaç. Si no existeix retorna el format raw amb el prefix d:.
  • formatting=internallink Enllaç intern sempre que sigui possible, bé a la pàgina de la Viquipèdia (sitelink) o bé a l'etiqueta (possiblement un enllaç vermell). Com a darrera opció enllaça a Wikidata.
  • formatting=pattern Format segons un patró usant $1 com a paràmetre a substituir. Pot incloure plantilles o funcions parser amb el format: {{((}}nom-de-la-plantilla{{!}}$1{{))}} o bé {{((}}nom-de-la-plantilla{{!}}nom_paràmetre{{=}}$1{{))}}, o similar.
El format per defecte és un enllaç conduït, bé a sitelink o bé a wikitada:raw, usant label com a etiqueta de l'enllaç.
  • formatting=ucfirst Variant del format per defecte amb majúscula inicial en l'etiqueta. En una llista només posa majúscula en el primer valor.
  • formatting=ucinternallink Combinació del format "ucfirst" i "internallink".

Paràmetres de taula de propietat i qualificadors

propietat qualificador1 qualificador2 etc.
valors 1a declaració $0 $1 $2, etc.
2a, etc. $0 $1 $2, etc.

formatting=table Una declaració amb qualificadors es considera com una taula de valors mostrada a la dreta. Es pot indicar el format de les files i de cada columna de la taula. Els separadors per a cada fila són per defecte salts de línia (vegeu separator i conjunction més amunt). Si realment voleu el resultat en una taula wiki podeu usar separator=</tr><tr> amb les corresponents etiquetes d'apertura i tancament abans i desprès de l'invoke. Paràmetres específics per a aquest format:

Columnes:

  • qualifier1 ... qualifierx= Qualificadors amb un número consecutiu i il·limitat. El paràmetre general qualifier és un àlies per qualifier1.
    • qualifierx = Pid1 OR Pid2 Definició alternativa d'un qualificador quan el primer no existeix. Un cas d'ús típic és P585 OR P580. No hi ha límit per successius OR. Els espais en blanc d'abans i després són opcionals.
    • qualifierx = Pid1/Pid2 De l'element corresponent a Pid1 (un qualificador o una propietat) obté la propietat superior Pid2. Permet obtenir dades addicionals en la taula encara que definides en l'element superior. Per exemple, per obtenir les línies de metro amb la seva icona: qualifier=P81/P154. Es pot combinar amb l'opció OR prenent prioritat l'OR.
    • qualifierx = /Pid Equivalent a l'anterior però sense Pid1, obté una segona propietat del mateix element. Permet obtenir dades definides bé en un qualificador o bé en una propietat.
  • colformat0 ... colformatx= Format opcional a aplicar a les columnes, usant 0 per la propietat i 1-x pels qualificadors. Accepta la mateixa sintaxi que formatting per a cada tipus de dada. Vegeu més amunt #Paràmetres de formatació.
  • case0 ... casex= Cas gramatical a aplicar a cada columna. El paràmetre case, sense numeració, s'aplica a tots els valors. Vegeu més amunt #Paràmetres generals.
  • convert0 ... convertx= Conversió d'unitats a aplicar a cada columna. Vegeu més amunt #Paràmetres de formatació per format numèric.
  • whitelist0 ... whitelistx= Llista dels ítems a mostrar per a una columna, usant qualsevol separador.
  • blacklist0 ... blacklistx= Llista dels ítems que no es mostraran per a una columna, usant qualsevol separador. En cas d'usar whitelist per a una columna i blacklist per a una altra els resultats poden ser inesperats (vegeu un comentari al respecte)

Files:

  • rowformat= Format de les files de propietat més qualificadors. La propietat s'indica amb $0 i els qualificadors $1 a $x. Per defecte és rowformat=$0 ($1, ... $x). Pot incloure marques de llistes * o # i també plantilles o funcions parser amb el format: {{((}}plantilla{{!}}paràmetre{{!}}nom{{=}}paràmetre{{))}}. En cas d'usar el paràmetre "references" (vegeu més amunt) cal indicar on han d'aparèixer les referències amb $R0.
  • rowsubformat1 ... rowsubformatx= Format previ a aplicar a $1-$x definits en rowformat, només si existeix el valor. Per exemple, "rowformat=$0 $1" i "rowsubformat1=($1)" resulta "$0 ($1)" o bé "$0" si $1 està buit, evitant uns parèntesis buits. Un rowsubformat pot contenir diferents variables $1-$x. Cal tenir en compte que les substitucions en fan de forma seqüencial per ordre numèric, per tant pot incloure un $x posterior però no un d'anterior ja que haurà estat tractat.
  • tablesort= amb valors de 0 a x, permet ordenar la taula per la propietat (0) o els qualificadors (1 a x). L'ordenació és ascendent alfabèticament, numèricament o per dates, segons el tipus de dada. Accepta múltiples claus usant qualsevol separador, p. ex. tablesort=0/2/1. Per defecte l'ordenació és pel rang de la propietat (preferent, normal) i per l'antiguitat de definició a Wikidata.
  • sorting=-1 inverteix l'ordenació fent-la descendent. Amb qualsevol altre valor, o en el seu defecte, l'ordenació és ascendent. En cas de tablesort amb múltiples claus, el criteri de sorting s'aplicarà a totes per igual.
  • list=false Treu només la primera fila de la taula, segons l'ordre indicat o l'ordre per defecte.

Funció getLabel

Retorna l'etiqueta d'un element.

  • 1= (primer paràmetre posicional, requerit) Identificador de l'ítem de Wikidata (Qid o Pid). Àlies: item, from.
  • lang= (opcional) Codi de llengua, com en la funció claim. Vegeu més amunt #Paràmetres generals.
  • itemgender= (opcional) Identificador de l'ítem que determina el gènere a usar, com en la funció claim. Vegeu més amunt #Paràmetres generals.
  • linked= (opcional) Amb qualsevol valor, excepte 'no', retorna l'etiqueta enllaçada a l'article local o bé a Wikidata.
  • label= (opcional) Treu l'etiqueta indicada. Només té sentit amb linked= per generar un enllaç amb l'etiqueta de label=.
  • editicon= (opcional) permet eliminar la icona del llapis, com en la funció claim. Vegeu més amunt #Paràmetres generals.

Funció getParentValues

Retorna de forma recursiva valors d'una propietat usant com a etiqueta un valor superior. Per exemple pot estraure l'estructura administativa o la classificació de tàxons.

  • item= (opcional) Permet indicar un item (Qid) diferent a l'article actiu. Aquest accés arbitrari a Wikidata està limitat pel seu alt consum de recursos.
  • property= (opcional) Propietat de la declaració (Pid). Per defecte és P131. Admet una llista de valors alternatius usant qualsevol separador.
  • label= (opcional) Propietat superior a usar com a etiqueta. Per defecte és P31.
  • upto= (opcional) Darrera iteració a extraure, si abans no ha trobat cap propietat no existent. Tipus de valors:
    • upto=etiqueta: darrera etiqueta a consultar, per exemple "upto=estat".
    • upto=valor numèric: nombre màxim d'iteracions. Per defecte és 10 com a protecció, normalment un valor més alt del que serà necessari.
  • uptolabelid= (opcional) Alternatiu a upto=etiqueta usant el Qid.
  • uptovalueid= (opcional) Alternatiu a uptolabelid usant el Qid del valor en lloc de l'etiqueta. Admet valors múltiples usant qualsevol separador. Àlies uptolinkid mantingut provisionalment per compatibilitat.
  • labelshow= (opcional) Filtre d'etiquetes a mostrar, separades per una barra / si són més d'una. Per exemple "labelshow=municipi/comarca". Mostra el primer valor trobat per a cada etiqueta, sense repeticions posteriors de la mateixa etiqueta.
  • showlabelid= (opcional) Alternatiu a labelshow usant Qid i qualsevol separador.
  • include_self=true (opcional) Inclou l'etiqueta de la pròpia pàgina en la llista generada.
  • sorting=-1 (opcional) Ordenació descendent de la llista.
  • last_only=true (opcional) Només mostra el darrer valor obtingut. Per exemple amb uptolinkid=Qid només mostrarà el valor corresponent a Qid.
  • formatting= (opcional) Format del valor de la propietat. Per defecte és un enllaç conduït local o a Wikidata (vegeu més amunt formatting de la funció claim per valors entity).
  • valuetext= (opcional) Propietat a usar com a text de l'enllaç amb el valor 'property' com a enllaç conduït.
  • labelformat= (opcional) Format de l'etiqueta. Per defecte és "label" (vegeu més amunt formatting de la funció claim per valors entity).
  • rowformat= (opcional) Format de sortida per a cada parell de valors obtingut, indicant $0 per l'etiqueta i $1 pel valor. Per defecte és "rowformat=$0 = $1" mostrant per exemple "comarca = [[Maresme]]"
  • separator= (opcional) Separador per a cada parell de valors obtingut, per defecte és <br />.
  • cascade=true (opcional) opció de presentació amb sagnat. Pot ser útil quan només s'utilitza $1 a rowformat.

Nota: les etiquetes es modifiquen amb un format adequat a les necessitats d'una infotaula segons les definicions a Module:Wikidata/labels. Per exemple, "municipi del Brasil" es presenta com a "Municipi".

Funció linkWithParentLabel

Crea un enllaç amb l'etiqueta d'una propietat superior.

Accepta la majoria de paràmetres de la funció claim, excepte "formatting" que usa el valor per defecte "internallink". Addicionalment:

  • parent= és la propietat a usar en l'etiqueta corresponent a l'ítem superior del valor obtingut per "property/qualifier". Accepta valors alternatius amb qualsevol separador, igual que el paràmetre "property" de la funció claim.

Utilitats

  • getSiteLink Retorna el títol de pàgina local per un element donat. Sintaxi:
    {{#invoke:Wikidata|getSiteLink|<Qid>|<wiki>}}
    Sense cap paràmetre retorna la pròpia pàgina. Proporcionant l'id retornarà la pàgina local enllaçada en l'element corresponent. En cas de no existir o ser erroni no retorna res. Amb un segon paràmetre amb un codi de wiki obté la pàgina en el wiki especificat (enwiki, frwiki,... cawiktionary, etc.)
  • lang Retorna el codi de llengua tal com el gestiona la funció claim. Vegeu #Paràmetres generals més amunt. Sintaxi:
    {{#invoke:Wikidata|lang|{{{lang|}}}}}
    El paràmetre lang és el codi de la llengua aportat explícitament.
  • numStatements Retorna el nombre de declaracions que té una propietat multi-valor, comptant les de rang més alt. Permet decidir el tractament a donar a llistes llargues. Amb un segon paràmetre compta el nombre de declaracions d'un qualificador. Sintaxi:
    {{#invoke:Wikidata|numStatements|<Pid>|item={{{item|}}} }}
    {{#invoke:Wikidata|numStatements|<Pid>|<Qual_id>|item={{{item|}}} }}
    Exemple: {{#invoke:Wikidata|numStatements|P47|item=Q1861}} → 6
  • validProperty Retorna "null" si la propietat no existeix, o només té valors amb marcador "sense valor" o "valor desconegut" o obsolet. Sintaxi:
    {{#invoke:<nowiki>{{ROOTPAGENAME}}<nowiki>|validProperty|<Pid>|item={{{item|}}}
    Es pot usar com a alternativa a #property. Per compatibilitat amb aquesta funció parser, el paràmetre from és un àlies de item.

Redireccions a Wikidata

Un element de Wikidata pot ser una redirecció a un altre element, sovint com a resultat d'una fusió. Els valors d'una propietat poden ser provisionalment una redirecció, normalment corregit per bot al cap d'una setmana. El tractament de les redireccions és el següent:

  • Funció claim
    • Amb formatting=raw no retorna el Qid redirigit sinó el Qid final de la redirecció.
    • Amb paràmetres whitelist o blacklist, si la llista de valors inclou alguna redirecció, considera tant el Qid redirigit com el nou Qid final.
  • Funció getLabel
    • Si es demana l'etiqueta d'un Qid redirigit, la buscarà en el Qid final.

En tots els casos afegeix un rastreig per poder corregir en les plantilles els Qid redirigits. Es poden trobar a Special:WhatLinksHere/Template:Track/wikidata/redirect.

La resolució de redireccions a Wikidata està pendent de phab:T157868. La solució tècnica adoptada provisionalment pot provocar un alt consum de recursos si un element no té cap etiqueta en la llengua local ni en les llengües alternatives. Es recomana comprovar el rastreig Special:WhatLinksHere/Template:Track/wikidata/label per afegir les etiquetes que manquen.

Crides des d'un altre mòdul

Totes les funcions (claim, getLabel, getParentValues, linkWithParentLabel, lang) es poden cridar des d'altres mòduls via require amb els mateixos paràmetres proporcionats en una taula, per exemple:

require("Module:Wikidata").claim{item="Q...", property="P...", ...}
require("Module:Wikidata").getLabel({"Q...", ['lang']="ca"})

Les funcions claim i getLabel retornen com a segon valor la variable untranslated (nil o true) per a tractament específic en mòduls d'infotaules.

Exemples

Els exemples a continuació són mostres provades en les pàgines corresponents. Es poden fer proves a w:Viquipèdia:Proves de Wikidata, comprovacions en previsualització d'una pàgina o usar el paràmetre item per un element de Wikidata diferent de la pàgina actual.

  • Ús simple:
{{#invoke:Wikidata | claim |property=P20}} a l'article w:Jean-François Champollion dóna: París
és la propietat P20, per defecte amb el valor enllaçat.
  • Sense enllaç:
{{#invoke:Wikidata | claim | property=P20 | formatting=label}} dóna: París
  • Valor preferent:
{{#invoke:Wikidata | claim | property=P20 | value={{{lloc_mort|}}} }} en la infotaula de l'article donarà
el valor de {{{lloc_mort|}}} si està definit en l'article, en cas contrari París
  • Valor tipus data:
{{#invoke:Wikidata | claim | property=P569}} dóna la data de naixement amb el format per defecte:
23 desembre 1790 i 22 desembre 1790
  • Formatat amb un patró:
{{#invoke:Wikidata | claim | property=P214 | formatting=[http://viaf.org/viaf/$1 $1]}} dóna:
34454460
  • Llista de valors:
{{#invoke:Wikidata | claim | property=P47}} a Alcoià dóna:
l'Alacantí, l'Alt Vinalopó, el Comtat, la Marina Baixa, la Vall d'Albaida i el Vinalopó Mitjà
Cal notar l'enllaç conduït [[Comtat (País Valencià)|Comtat]]
  • Llista formatada:
{{#invoke:Wikidata | claim | property=P150 | separator=<br /> | conjunction=<br />}} dóna
Alcoi
Banyeres de Mariola
Benifallim
Castalla
Ibi
Onil
Penàguila
Tibi

Dependències

Vegeu també


local p = {}

-- 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"] = {"", ""},		-- year(s) old, as in {{PLURAL:$1|singular|plural}}
	-- two values for most languages, up to six values for some languages
	-- see documentation of PLURAL magic word in your language, examples:
	-- ["years-old"] = {"singular", "paucal", "plural"} in Russian and other Slavic languages
	-- ["years-old"] = {"zero", "one", "two", "few 3-10", "many 11-99", "other 100-102"} in Arabic
	
	["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"
	},
	
	-- default 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)
	["categoryprop"] = "", -- Category:Pages using Wikidata property $1 (void for no local category)
	["categoryref"] = "", -- Category:Pages with references from Wikidata (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 (type(var) == 'string' and mw.text.trim(var) == ''))
end

-- Set local case to a label
local function case(localcase, label, ...)
	if not isSet(label) then return label end
	
	if type(localcase) == "function" then
		return localcase(label)
	elseif 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
			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')
	local gender_id = getSnak(claims, {1, "mainsnak", "datavalue", "value", "id"})
	if gender_id == "Q6581072" or gender_id == "Q1052281" or gender_id == "Q43445" then -- female, transgender female, female organism
		return true
	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
			local namespace = ''
			if string.sub(label_id, 1, 1) == 'P' then
				namespace = 'Property:'
			end
			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/" .. namespace .. 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

-- editicon values: true/false (no=false), right, void defaults to i18n.addpencil
-- labelicon only by parameter
local function setIcons(arg, parg)
	local val = arg == nil and parg or arg
	local edit_icon, label_icon
	if not isSet(val) then
		edit_icon, label_icon = i18n.addpencil, true
	elseif val == false or val == "false" or val == "no" then
		edit_icon, label_icon = false, false
	else
		edit_icon, label_icon = val, true
	end
	return edit_icon, label_icon
end

-- Add an icon for editing a statement with requirements for Wikidata Bridge
local function addEditIcon(parameters)
	local ret = ''
	if parameters.editicon and parameters.id and parameters.property then
		local icon_style = parameters.editicon == "right" and ' style="float: right;"' or ''
		ret = ' <span class="penicon" data-bridge-edit-flow="single-best-value"' .. icon_style .. '>'
			.. "[[File:Arbcom ru editing.svg|10px|baseline|"
			.. string.gsub(mw.message.new('Wikibase-client-data-bridge-bailout-suggestion-go-to-repo-button'):inLanguage(parameters.lang[1]):plain(), '{{WBREPONAME}}', 'Wikidata')
			.. "|link=https://www.wikidata.org/wiki/" .. parameters.id .. "?uselang=" .. parameters.lang[1] .. "#" .. parameters.property .. "]]"
			.. "</span>"
		if isSet(i18n.categoryprop) then
			ret = ret .. "[[" .. string.gsub(i18n.categoryprop, '$1', parameters.property) .. "]]"
		end
	end
	return ret
end
-- add edit icon to the last element of a table
local function addEditIconTable(thetable, parameters)
	if #thetable == 0 or parameters.editicon == false 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
				local param_name = mw.ustring.sub(parts[i], 1, subparts - 1)
				local param_value = mw.ustring.sub(parts[i], subparts + 1, -1)
				-- reconstruct broken links by parts
				if i < #parts and mw.ustring.find(param_value, "[[", 1, true) and not mw.ustring.find(param_value, "]]", 1, true) then
					parameters[param_name] = param_value
					local part_next = i + 1
					while parts[part_next] and mw.ustring.find(parts[part_next], "]]", 1, true) do
						parameters[param_name] = parameters[param_name] .. "|" .. parts[part_next]
						part_next = part_next + 1
					end
				else
					parameters[param_name] = param_value
				end
			elseif not mw.ustring.find(parts[i], "]]", 1, true) then
				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

-- 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.labelicon)
		end
	end
	if suffix ~= '' then
		suffix = ' ' .. suffix
	end
	return suffix
end
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)
		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.referit and parameters.referit ~= '' then labelcase = parameters.referit end
	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 string.match(parameter or '', 'internallink$') and not mw.wikibase.getEntityIdForTitle(label) then
			ret1 = '[[' .. label .. '|' .. labelcase .. ']]'
			ret2 = labelcase
		else
			ret1 = '[[d:' .. entity_page .. '|<span style="color:#5f9cbb;">' .. (labelcase or entity_id) .. '</span>]]'
			ret2 = labelcase or entity_id
		end
	end
	
	return ret1 .. addLabelIcon(entity_id, lang, parameters.lang[1], parameters.labelicon), ret2
end

-- format data type monolingualtext
local function getSnakValue(snak, parameters)
	if snak.snaktype == 'value' then
		-- see Special:ListDatatypes
		if snak.datatype == 'wikibase-item' or snak.datatype == 'wikibase-property' then
			return printDatatypeEntity(snak.datavalue.value, parameters)
		elseif snak.datatype == "time" then
			return printDatatypeTime(snak.datavalue.value, parameters)
		end
	elseif snak.snaktype == 'somevalue' then
		if parameters.referit and parameters.referit ~= '' then return parameters.referit end
	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 getReferitValue(claim, parameters)
	local result = ""
	if claim.qualifiers then
		if claim.qualifiers['P1932'] then
			local wqualif = claim.qualifiers['P1932'][1].datavalue.value
			if wqualif and wqualif ~= '' then
				return wqualif
			end
		end
	end
	return result
end

local function getValueOfClaim(claim, qualifierId, parameters)
	local snak, error = getQualifierSnak(claim, qualifierId, parameters)
	parameters.referit=getReferitValue(claim, 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 value, sortkey, valueraw = {}, {}, {}
	local parent_raw, value_text
	if qids[1] == parameters.property then
		parent_raw, _, _ = getValueOfClaim(claim, nil, {["formatting"]="raw", ["lang"]=parameters.lang})
	else
		parent_raw, _, _ = getValueOfClaim(claim, qids[1], {["formatting"]="raw", ["lang"]=parameters.lang, ["list"]=true, ["qseparator"]='/', ["qconjunction"]='/'})
	end
	if string.sub(parent_raw or '', 1, 1) == "Q" then -- protection for 'no value'
		local parent_qids = mw.text.split(parent_raw, '/', true)
		for idx, p_qid in ipairs(parent_qids) do
			local parent_claims = mw.wikibase.getBestStatements(p_qid, qids[2])
			if parent_claims[1] then
				value[idx], sortkey[idx], _ = 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[idx], _, _ = getValueOfClaim(parent_claims[1], nil, {["formatting"]="raw", ["lang"]=parameters.lang})
			end
		end
	end
	if value[1] then
		value_text = mw.text.listToText(value, parameters.qseparator, parameters.qconjunction)
	end
	return value_text, sortkey[1], valueraw[1]
end

-- see d:Help:Sources
local function getReferences(claim, parameters)
	if not isSet(parameters.references) then return '' end
	local lang = parameters.lang
	local maxrefs = tonumber(parameters.references) or 1
	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_id
		-- 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}) or '')
				end
				refs[snakkey] = refparts
				refparts = nil
				if snakkey == "P248" then -- stated in
					ref_id = getSnak(snakval, {1, "datavalue", "value", "id"})
				end
			end
		end
		
		-- fill missing values with parent item
		if ref_id 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_id, 'P50', 'label') -- author
			refs['P407'] = refs['P407'] or refParent(ref_id, 'P407', 'label') -- language of work
			refs['P123'] = refs['P123'] or refParent(ref_id, 'P123', 'label') -- publisher
			refs['P577'] = refs['P577'] or refParent(ref_id, 'P577') -- date
			refs['P1433'] = refs['P1433'] or refParent(ref_id, 'P1433', 'label') -- published in
			refs['P304'] = refs['P304'] or refParent(ref_id, 'P304') -- page(s)
			refs['P433'] = refs['P433'] or refParent(ref_id, 'P433') -- issue
			refs['P236'] = refs['P236'] or refParent(ref_id, 'P236') -- ISSN
			refs['P356'] = refs['P356'] or refParent(ref_id, '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
			local ref_name = claim.references[ref].hash
			result[#result + 1] = mw.getCurrentFrame():extensionTag("ref", refparts, {name=ref_name})
			if maxrefs and maxrefs == #result then break end
		end
	end
	if type(result) == 'table' and #result > 0 and isSet(i18n.categoryref) then
		return mw.text.listToText(result) .. "[[" ..i18n.categoryref .. "]]"
	end
	return mw.text.listToText(result)
end

-- Set whitelist or blacklist values
local function setWhiteOrBlackList(num_qual, args)
	local lists = {['whitelist']={}, ['blacklist']={}, ['ignorevalue']={}, ['selectvalue']={}}
	for i = 0, num_qual do
		for k, _ in pairs(lists) do
			if isSet(args[k .. i]) then
				lists[k][tostring(i)] = {}
				local pattern = 'Q%d+'
				if string.sub(args[k .. i], 1, 1) ~= 'Q' then
					pattern = '[^%p%s]+'
				end
				for q in string.gmatch(args[k .. i], pattern) do
					lists[k][tostring(i)][q] = true
				end
			end
		end
	end
	return lists['whitelist'], lists['blacklist'], lists['ignorevalue'], lists['selectvalue']
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(args, pargs, unnamed)
	pargs = pargs or {}
	local id = args.item or args.from or (unnamed and mw.text.trim(args[1] or '') or nil)
	if not isSet(id) 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

local function getArg(value, default, aliases)
	if type(value) == 'boolean' then return value
	elseif value == "false" or value == "no" then return false
	elseif value == "true" or value == "yes" then return true
	elseif value and aliases and aliases[value] then return aliases[value]
	elseif isSet(value) then return value
	elseif default then return default
	else return nil
	end
end

-- Main function claim ---------------------------------------------
-- on debug console use: =p.claim{item="Q...", property="P...", ...}
function p.claim(frame)
	local args = frame.args or frame -- via invoke or require
	local pargs = frame.args and frame:getParent().args or {}
	local is_sandbox = isSet(pargs.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
	--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 parameters = {}
	parameters.id = getEntityId(args, pargs)
	if parameters.id == nil then return end
	parameters.property = string.upper(args.property or "")
	local qualifierId = {}
	qualifierId[1] = getArg(string.upper(args.qualifier or ""))
	local i = 2
	while isSet(args["qualifier" .. i]) do
		qualifierId[i] = string.upper(args["qualifier" .. i])
		i = i + 1
	end
	parameters.formatting = getArg(args.formatting)
	parameters.convert = getArg(args.convert)
	parameters.case = args.case
	parameters.list = getArg(args.list, true, {firstrank='bestrank'})
	parameters.shownovalue = getArg(args.shownovalue, true)
	parameters.separator = getArg(args.separator)
	parameters.conjunction = getArg(args.conjunction, parameters.separator)
	parameters.qseparator = parameters.separator
	parameters.qconjunction = parameters.conjunction
	local sorting_col = args.tablesort
	local sorting_up = (args.sorting or "") ~= "-1"
	local rowformat = args.rowformat
	parameters.references = args.references
	local showerrors = args.showerrors
	local default = args.default
	if default then showerrors = nil end
	parameters.lang = findLang(args.lang)
	if parameters.formatting == "raw" then
		parameters.editicon, parameters.labelicon = false, false
	else
		parameters.editicon, parameters.labelicon = setIcons(args.editicon, pargs.editicon) -- needs loadI18n by findLand
	end
	
	-- fetch property
	local claims = {}
	local bestrank = (parameters.list == false or parameters.list == 'bestrank') and parameters.list ~= 'lang'
	for p in string.gmatch(parameters.property, 'P%d+') do
		claims = getStatements(parameters.id, p, bestrank)
		if #claims > 0 then
			parameters.property = p
			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 = false
	local whitelist, blacklist, ignorevalue, selectvalue = {}, {}, {}, {}
	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, blacklist, ignorevalue, selectvalue = setWhiteOrBlackList(#qualifierId, args)
		local next = next
		if next(whitelist) ~= nil then whitelisted = true end
	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(parameters.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 parameters.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, result_query
	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 == parameters.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(parameters.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
									qual = q
									break
								end
							end
						end
						values[#values]["col" .. j] = valueq
						sortkeys[#sortkeys]["col" .. j] = sortkeyq or valueq
						if whitelist[j] or blacklist[j] or ignorevalue[j] or selectvalue[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
							elseif ignorevalue[j] and ignorevalue[j][valueq or ""] then
								values[#values]["col" .. j] = nil
							elseif selectvalue[j] and not selectvalue[j][valueq or ""] then
								values[#values]["col" .. j] = nil
							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 isSet(parameters.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)
			
			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", {["$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 isSet(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
		
		if args.query == 'num' then
			result_query = 0
			for _, v in pairs(rowlist) do
				result_query = result_query + (v and 1 or 0)
			end
		end
		if #result > 0 then
			if parameters.formatting == 'table' then
				result = addEditIconTable(result, parameters) -- in a table, add edit icon on last element
			end
			result = preformat .. mw.text.listToText(result, parameters.separator, parameters.conjunction) .. postformat
		else
			result = ''
		end
	else
		-- return first element
		local claim = claims[sortindices[1]]
		result, result2, error = getValueOfClaim(claim, qualifierId[1], parameters)
		if result and isSet(parameters.references) then result = result .. getReferences(claim, parameters) end
		if args.query == 'num' then result_query = 1 end
	end
	
	if isSet(result) then
		if 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
	if args.query == 'untranslated' and required and not is_sandbox then
		result_query = untranslated
	end
	return result, result_query or ''
end

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

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

local function getPropertyValue(id, property, parameter, langs, labelicon, 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, labelicon=labelicon, case=case})
	end
	
	return entity_id, result
end

local function getParentObjects(id,
	prop_format,
	label_format,
	languages, 
	propertySupString, 
	propertyLabel,
	propertyLink,
	label_show,
	labelicon0,
	labelicon1,
	upto_number,
	upto_label,
	upto_value,
	last_only,
	grammatical_case,
	include_self)
	
	local propertySups = mw.text.split(propertySupString, '[^P%d]')
	
	local maxloop = 10
	if upto_number then
		maxloop = tonumber(upto_number)
	elseif next(upto_label) or next(upto_value) then
		maxloop = 50
	end
	
	local labels_filter = next(label_show)
	
	local result = {}
	local id_value = id
	for iter = 1, maxloop do
		local link, label, labelwicon, linktext, id_label
		for _, propertySup in pairs(propertySups) do 	
			local _id_value, _link = getPropertyValue(id_value, propertySup, prop_format, languages, labelicon1, grammatical_case)
			if _id_value and _link then id_value = _id_value; link = _link break end
		end
		
		if not id_value or not link then break end
		
		if propertyLink then
			_, linktext = getPropertyValue(id_value, propertyLink, "label", languages)
			if linktext then
				link = mw.ustring.gsub(link, "%[%[(.*)%|.+%]%]", "[[%1|" .. linktext .. "]]")
			end
		end
		
		id_label, label = getPropertyValue(id_value, propertyLabel, label_format, languages, false, "infoboxlabel")
		if labelicon0 then
			_, labelwicon = getPropertyValue(id_value, propertyLabel, label_format, languages, labelicon0, "infoboxlabel")
		else
			labelwicon = label
		end
		
		if labels_filter == nil or (label_show[id_label] or label_show[label]) then
			result[#result + 1] = {labelwicon, link}
			label_show[id_label or 'none'], label_show[label or 'none'] = nil, nil -- only first label found
		end
		
		if upto_label[id_label] or upto_label[label] or upto_value[id_value] then
			break
		end
	end
	
	if last_only then
		result = {result[#result]}
	end
	
	if include_self then
		local label_self, link_self
		_, label_self = getPropertyValue(id, propertyLabel, label_format, languages, labelicon0, "infoboxlabel")
		link_self, _ = getLabelByLangs(id, languages)
		table.insert(result, 1, {label_self, link_self})
	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 direction = mw.language.new(wiki.langcode):isRTL() and "right" or "left"
		local suffix = ""
		for i = 1, #ret do
			ret[i] = '<ul style="line-height:100%; margin-' .. direction .. ':0.45em; padding-' .. direction .. ':0;"><li>' .. ret[i]
			suffix = suffix .. '</li></ul>'
		end
		ret[#ret] = ret[#ret] .. suffix
	end
	
	return ret
end

-- Returns pairs of parent label and property value fetching a recursive tree
function p.getParentValues(frame)
	local args = frame.args or frame -- via invoke or require
	local pargs = frame.args and frame:getParent().args or {}
	if not required and isSet(pargs.sandbox) then
		return require(wiki.module_title .. "/" .. mw.message.new('Sandboxlink-subpage-name'):inLanguage(wiki.langcode):plain()).getParentValues(frame)
	end
	local id = getEntityId(args, pargs)
	if id == nil then return end
	local languages = findLang(args.lang)
	local propertySup = getArg(args.property, "P131") --administrative entity
	local propertyLabel = getArg(args.label, "P31") --instance
	local propertyLink = getArg(args.valuetext)
	local property_format = getArg(args.formatting)
	local label_format = getArg(args.labelformat, "label")
	local upto_number = getArg(args.upto)
	local last_only = getArg(args.last_only, false)
	local editicon, labelicon = setIcons(args.editicon, pargs.editicon)
	local include_self = getArg(args.include_self, false)
	local case = getArg(args.case)
	
	local upto_label = {}
	for q in string.gmatch(args.uptolabelid or '', 'Q%d+') do
		upto_label[q] = true
	end
	if type(upto_number) == 'string' then
		upto_label[uc_first(upto_number)] = true
		upto_number = nil
		require(wiki.module_title .. '/debug').track('upto') -- replace upto by uptolabelid
	end
	
	local upto_value = {}
	for q in string.gmatch(args.uptovalueid or args.uptolinkid or '', 'Q%d+') do
		upto_value[q] = true
	end
	
	local label_show = {}
	for q in string.gmatch(args.showlabelid or '', 'Q%d+') do
		label_show[q] = true
	end
	for _, v in ipairs(mw.text.split(args.labelshow or '', "/")) do
		if v ~= '' then
			label_show[uc_first(v)] = true
			require(wiki.module_title .. '/debug').track('labelshow') -- replace labelshow by showlabelid
		end
	end
	
	local rowformat = args.rowformat; if not isSet(rowformat) then rowformat = "$0 = $1" end
	local labelicon0, labelicon1 = labelicon, labelicon
	if string.find(label_format, '{{.*$0.*}}') or (string.find(rowformat, '{{.*$0.*}}') and label_format ~= 'raw') then
		labelicon0 = false
	end
	
	local result = getParentObjects(id,
		property_format,
		label_format,
		languages, 
		propertySup, 
		propertyLabel,
		propertyLink,
		label_show,
		labelicon0,
		labelicon1,
		upto_number,
		upto_label,
		upto_value,
		last_only,
		case,
		include_self)
	if #result == 0 then return 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 p.linkWithParentLabel(frame)
	local pargs = frame.args and frame:getParent().args or {}
	if not required and isSet(pargs.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, _ = p.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, _ = p.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, _ = p.claim(largs)
		if isSet(link_label) then
			link_table[i] = mw.ustring.gsub(link_table[i] or '', "%[%[(.*)%|.+%]%]", "[[%1|" .. link_label .. "]]")
		end
	end
	args.editicon, _ = setIcons(args.editicon, pargs.editicon)
	args.id = getEntityId(args, pargs)
	args.lang = findLang(args.lang)
	return mw.text.listToText(link_table) .. addEditIcon(args)
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 pargs = frame.args and frame:getParent().args or {}
	if not required and isSet(pargs.sandbox) then
		return require(wiki.module_title .. "/" .. mw.message.new('Sandboxlink-subpage-name'):inLanguage(wiki.langcode):plain()).getLabel(frame)
	end
	local id = getEntityId(args, pargs, 1)
	if id == nil then return end
	local languages = findLang(args.lang)
	local labelicon = false
	if mw.wikibase.isValidEntityId(id) then
		_, labelicon = setIcons(args.editicon, pargs.editicon)
	end
	
	local label_icon = ''
	local label, lang
	if args.label then
		label = args.label
	else
		-- exceptions or labels fixed
		local exist, labels = pcall(require, wiki.module_title .. "/labels" .. (languages[1] == wiki.langcode and '' or '/' .. languages[1]))
		if exist and labels.infoboxLabelsFromId and next(labels.infoboxLabelsFromId) ~= nil then
			label = labels.infoboxLabelsFromId[id]
		end
		
		if label == nil then
			label, lang = getLabelByLangs(id, languages)
			if label then
				if isSet(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
				if args.case then
					label = case(args.case, label, lang)
				end
			end
			label_icon = addLabelIcon(id, lang, languages[1], labelicon)
		end
	end
	
	local linked = args.linked
	local ret2 = required and untranslated or ''
	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, ret2
	else
		return (label or id) .. label_icon, ret2
	end
end

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

-- Copied from Module:Wikibase
function p.getSiteLink(frame)
	local args = frame.args or frame -- via invoke or require
	local pargs = frame.args and frame:getParent().args or {}
	local id = getEntityId(args, pargs, 1)
	if id == nil then return end
	return mw.wikibase.getSitelink(id, mw.text.trim(args[2] or ''))
end

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

-- Number of statements
function p.numStatements(frame)
	local args = frame.args or frame -- via invoke or require
	local pargs = frame.args and frame:getParent().args or {}
	local id = getEntityId(args, pargs)
	if id == nil then return 0 end
	local prop = mw.text.trim(args[1] or '')
	local num = {}
	if not isSet(prop) and frame.args then
		args = {}
		for k, v in pairs(pargs) do
			args[k] = v
		end
		for k, v in pairs(frame.args) do
			args[k] = v
		end
		args.query = 'num'
		_, num = p.claim(args)
		return num
	elseif args[2] then -- qualifier
		local qual = mw.text.trim(args[2])
		local values = p.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 p.validProperty(frame)
	local args = frame.args or frame -- via invoke or require
	local pargs = frame.args and frame:getParent().args or {}
	local item = getEntityId(args, pargs)
	if item == nil then return end
	local property = mw.text.trim(args[1])
	local prop_data = getSnak(mw.wikibase.getBestStatements(item, property), {1, "mainsnak", "datavalue"})
	return prop_data and true or nil
end

function p.editAtWikidata(frame)
	local args = frame.args or frame -- via invoke or require
	local pargs = frame.args and frame:getParent().args or {}
	local value = isSet(args[1])
	if value then return end
	local param = {}
	param.id = getEntityId(args, pargs)
	param.property = args.property
	param.lang = findLang(args.lang)
	param.editicon, _ = setIcons(args.editicon)
	return addEditIcon(param)
end

function p.formatNum(frame)
	local num = tonumber(mw.text.trim(frame.args[1]))
	local lang = findLang(mw.text.trim(frame.args[2]))
	return mw.language.new(lang[1]):formatNum(num)
end

return p