Module permettant d'utiliser de récupérer et de mettre en forme des données issues de Wikidata.

Dans la mesure du possible, ce module devrait être neutre sur le plan linguistique. Pour les textes spécifiquement français, voir Module:Wikidata/I18n.

Fonctions exportables

modifier

Fonctions opérant au niveau d'un snak

modifier
Nom Arguments Description
formatSnak snak, params Récupère la valeur d'un snak et la met en forme
getDataValue snak, params Récupère la valeur d'un snak avec une valeur de type "value" et la met en forme
isSpecial snak retourne true si un snak est une valeur spéciale (novalue ou somevalue)
getId snak retourne l'identifiant Qdd de l'élément utilisé comme valeur d'un snak

Fonctions opérant au niveau d'une affirmation

modifier
Nom Arguments Description
formatStatement params Transforme une déclaration individuelle en chaîne wikitexte, selon les mêmes paramètres que stringTable
getDate statement, qualifs récupère les informations de date stockées dans une déclaration Wikidata (qualificatifs ou valeur principale) et les stocke sous forme de table
getFormattedDate statement, params récupère la date associée à un déclaration Wikidata comme getDate, mais la retourne sous forme d'une chaîne formattée selon les paramètres params
hasQualifier claim, acceptedqualifs, acceptedvals, excludequalifiervalues retourne true si l'affirmation comporte des qualificatifs utilisant les propriétés acceptedqualifs avec les valeurs acceptedvals, et sans les valeurs excludequalifiervalues. Si acceptedqualifs n'est pas renseignée, toutes les propriétés sont acceptées. Si acceptedvals n'est pas renseigné, toutes les valeurs sont acceptées.
getMainId claim retourne l'identifiant Qdd de l'élément utilisé comme valeur du "mainsnak" d'une déclaration
getFormattedQualifiers statement, qualifs, params à partir d'une affirmation, retourne une chaîne contenant les qualificatifs demandés. La table params permet de personnaliser l'affichage, voir #Paramètres.
getReferences statement récupère et affiche la partie références d'une déclaration Wikidata.
addTrackingCat property, cat catégorise dans [[Catégorie:Page utilisant $property]]

Fonctions opérant au niveau d'une entité

modifier
Nom Arguments Description
sortClaims claims, sorttype Met en ordre une série de valeurs pour ordre chronologique ou de quantité, voir l'argument "sorttype" pour plus de détail.
filterClaims claims, params Prend une table d'affirmations, et exclut celles qui ne correspondent pas aux critères donnés dans la table params. Pour la liste de ces arguments, voir l'aide ci dessous
getClaims params entity = et |property = , et peut contenir de nombreux autres paramètres optonnels, voir #Paramètres.
getIds params Sélectionne des affirmations de la même manière que getClaims met au lieu de retourner les affirmations complètes, elle ne retourne que l'identifiant de son mainsnak (ne fonctionne que pour les données de type élément).
stringTable Retourne les mêmes affirmations que getClaims, sauf éventuellement celles supprimées par le paramètre removedupes. Chacune est rendue sous forme d'une chaîne en Wikitexte directement utilisable. Les paramètres de mise en forme sont également contenus dans la table params.
formatStatements params Retourne les mêmes valeurs que stringTable, mais concaténées sous forme de chaîne unique. La méthode de concaténation est définie par le paramètre conjtype qui utilise Module:Linguistique. Si les paramètres linkback et addcat sont activés, un rétrolien et une catégorie de maintenance peuvent également être ajoutés.
formatAndCat params Même fonction que formatStatements, mais ajoute un rétrolien vers Wikidata et une catégorie de maintenance
getTheDate params affiche une date qui se trouve soit en valeur principale soit en qualificatif de la propriété prop de l'élément item
mainDate entity Récupère et met en forme la "date principale" que l'on peut associer à l'élément, en fonction de ses propriétésP580 (« date de début »), P582 (« date de fin ») et P585 (« date »)
keyDate event, item, params Récupère et format une date provenant de l'élément item. event doit être soit un identifiant de propriété, dans ce cas là, la fonction récupérera les valeurs dans cette propriété, soit un identifiant d'élément. Dans ce cas là, elle cherchera cet élément dans les valeurs de P793 (« événement clé ») et retournera la date indiquée en qualificatif.event peut également être une liste d'identifiants sous forme de table, dans ce cas là, la valeur retournée sera celle du premier identifiant pour lequel quelque chose est trouvé. Par exemple wd.keyDate('Q1417098', 'P1690'} cherchera la valeur dans P793 (« événement clé ») Q1417098 (« inauguration ») et si elle ne trouve rien, elle cherchera dans P1690 (« ICD-10-PCS »).
getLabel entity, lang, labelformat Retourne par défaut le libellé Wikidata de l'entité dans, en français ou dans la langue indiquée dans le deuxième paramètre. Le paramètre optionnel labelformat permet de définir un affichage alternatif. Sa valeur doit être une fonction prenant pour argument l'entité et retournant une chaîne.
siteLink entity, lang, project retourne un lien vers la page projet demandé ('wikipedia', 'wikivoyage', 'commons') etc. lié à l'entité Wikidata indiquée, dans la langue demandée. Par défaut, la langue est le français et le projet Wikpédia.
formatEntity entity, params Prend l'identifiant (ou la table de données) donnée en paramètre entité et la transforme, et la retourne sur la forme d'un texte contenant un libellé et éventuellement un lien interne.
getDescription entity, lang affiche la description Wikidata pour une entité donnée, dans un langue donnée (français par défaut)
getNumericId snak retourne l'identifiant numérique de l'élément utilisé comme valeur d'un snak
getEntity str retourne la table de données correpondant à un identifiant (inverse de getEntityId)
getEntityId str retourne l'identifiant d'une entité Wikidata passée sous la forme d'une table de données (inverse de getEntity)


Fonctions récupérant des données dans plusieurs entités

modifier
Nom Arguments Description
isSubclass class, item, maxdepth
isinstance class, item, maxdepth
inTransitiveVals searchedval, sourceval, query, maxdepth, maxnodes
Nom Arguments Description
iterate entité, chemin : iterateur retourne un itérateur sur toutes les valeurs possibles d’un chemin qui part de l’entité spécifiée. Exemple d’utilisation.
matches entité, chemin, valeur : retourne une valeur booléenne pour savoir si le chemin spécifié permet d’aller de l’entité « entité » à l’entité « valeur ». « isInstance » au-dessus est équilavente à path.matches(item, "P31/P279*", class) par exemple. Maxdepth pour l’instant pas implémenté, mais sans doute possible, il y a déjà une limite en dur dans les chemins).
Nom Arguments Descriptions
Dump entity affiche le contenu d'une entité Wikidata
sourceStr
frameFun (frame) appelle une des fonctions précédentes depuis le frame. Le nom de la fonction est donné en argument 1
addRefAnchor
addLinkback ajoute un rétrolien (généralement utilisé par formatStatements{linkback = true}, mais peut être utile comme fonction indépendante lorsque les données ont besoin font l'objet de nombreuses manipulations avant leur rendu final
isHere searchset, val retourne true si la chaîne val se trouve dans la table searchset
addNewValues old, new ajoute les de nouvelles valeurs à une table, seulement si elles ne s'y trouvent pas déjà

Paramètres

modifier

Liste des clés pouvant être utilisé dans la table "params" mentionnée ci dessous.

Choix des déclarations

modifier

Paramètres utilisés par la fonction filterclaims et donc indirectement par les fonctions y faisant appel, tels que getClaims et formatStatements.

Nom Mode d'utilisation Description
claims Lua Liste de déclarations à utiliser. Lorsque ce paramètre est activé, les paramètres property et entity ne sont pas utilisés pour la récupération des données.
entity Lua et wikicode L'identifiant de l'entité Wikidata à utiliser. Par défaut, celle liée à la page.
En Lua, on peut passer une entité déjà chargée plutôt que son identifiant.
property Lua et wikicode Le nom de la propriété à utiliser, sous la forme property = PXX. En Lua, on peut utiliser plusieurs propriétés sous forme de tables
excludespecial Lua et wikicode Retire les déclarations dont la valeur principale (mainsnak) n'est pas de type "valeur", c'est-à-dire les déclarations "valeur inconnue" ou "aucune valeur".

En wikicode : activée par la |excludespecial = true
En Lua, activée par un booléen (excludespecial = true)

targetvalue Lua et wikicode Ne retient que la ou les déclarations dont la valeur du snak principal correspond à la (ou une des) valeurs indiquées
excludevalues Lua et wikicode Exclut les déclarations dont la valeur du snak principal correspond à la (ou une des) valeurs indiquées
qualifier Lua et wikicode Seules les déclarations contenant ce qualificatif son retournées. Pour activer l'affichage du qualificatif, voir showqualifiers
qualifiervalue Lua et wikicode Le qualificatif utilisé par l'argument qualifier doit avoir cette valeur (ou une de ces valeurs, s'il s'agit d'une table ou d'une liste de propriétés séparées par des virgules
excludequalifier Lua et wikicode Les déclarations contenant ce qualificatif sont exclues
excludequalifiervalue Lua et wikicode Si ce paramètre est renseigné, excludequalifier n'est activé que lorsque la valeur du qualificatif se trouve dans celles qu'il indique.
withsource Lua et wikicode Ne retourne que les valeurs contenant une source, utilisant la source indiquée dans la propriété P248 (« affirmé dans »), ou une autre propriété définie par le paramètre sourceproperty. Pour accepter n'importe quelle source du moment qu'elle utilise la propriété demandée, mettre la valeur any. Pour désactiver ce paramètre, mettre la valeur -.
sourceproperty Lua et wikicode Propriété à utiliser pour le paramètre withsource
rank Lua et wikicocde Rangs acceptés :
  • preferred
  • normal
  • deprecated
  • valid (= preferred + normal)
  • best (preferred, ou, si aucune ne répond aux autres critères de la requête, normal)

Valeur par défaut : best

numval Lua et wikicode Nombre maximal de valeurs à retourner (les n premières valeurs de la liste établie par les autres paramètres)
withlink Lua et wikicode Ne retourne que les valeurs contenant un lien vers le site spécifié. Si la valeur du paramètre est simplement true, le site utilisé sera Wikipédia en français.
withdate Lua et wikicode Ne retourne que les valeurs possédant un qualificatif de date : P585 (« date »), P580 (« date de début »), P582 (« date de fin »)
atdate Lua et wikicode Exclut les valeurs dont les qualificatifs P580 (« date de début ») ou P582 (« date de fin ») indiquent qu'elle n'était pas valable à la période indiquée. atdate peut-être une date au format wikibase ou au format ISO. Les valeurs sans date ne sont pas exclues (elle peuvent l'être en ayant recours à la fonction withdate). Pour demander la valeur à la date d'aujourd'hui utiliser : |atdate = today.
minprecision Lua et Wikicode Degré de précision minimum accepté pour les données de type date. Doit être une valeur numérique correspondant au modèle de Wikidata (par exemple année = 9)
condition Lua Pour définir une fonction libre. Si la fonction retourne true, la déclaration sera gardée
sorttype Lua et wikicode Manière de classer les déclarations. Valeurs possibles :
  • chronological
  • inverted (chronologique inversé)
  • ascending par ordre croissant (données de type quantité)
  • descending par ordre décroissant (données de type quantité)
  • l'identifiant d'une propriété numérique utilisée dans les qualificatifs (par exemple P1545)
  • une fonction Lua (pour les appels depuis un module). Voir mw:extension:Scribunto/Lua reference manuall#table.sort

Activation / désactivation de Wikidata

modifier
Nom Mode d'utilisation Description
value Lua et wikicode Lorsque le paramètre value est non vide, le module ne cherche pas les données de Wikidata, mais utilise celles fournies localement par ce paramètre. Les fonction formatAndCat en revanche lui applique linkback et addcat)
expl Lua et wikicode Lorsque le paramètre expl est présent, la fonction formatStatements ne fait rien si le paramètre value n'a pas la valeur résultat de {{WD}} (utile pour des données mises à jour mais dont l'utilité dans chaque article est difficile à décider automatiquement).

Mise en forme des données

modifier
Nom Niveau d'application Mode d'utilisation Description
conjtype liste des déclarations Lua et wikicode La manière de coordonner les déclarations, en utilisant la fonction conj de Module:Linguistique. Valeurs possibles :
  • and ajoute " et " entre l'avant dernière et la dernière valeur, une virgule entre les autres.
  • or ajout " ou " entre l'avant dernière et la dernière valeur
  • comma des virgules partout
  • new line un retour à la ligne

Toute autre valeur donnée au paramètre sera insérée entre chaque valeur (|conjtype = et puis aussi ajoutera "et puis aussi" entre chaque valeurs)

linkback liste des déclarations Lua et wikicode Ajoute un rétrolien vers Wikidata sous forme d'un crayon «  »
addcat liste des déclarations Lua et wikicode Met la page dans une catégorie de suivi (voir Catégorie:Page utilisant des données de Wikidata). Le nom de la catégorie correspond à la valeur du paramètre. Si cette valeur est simplement true, la catégorie est fonction du paramètre property.

Attention, ce paramètre peut être source de bugs. Par exemple, si la propriété est utilisée dans une url ou un lien.

removedupes choix des déclarations
(à la fin, dans une fonction différente)
Lua et wikicode Lorsque, après toutes les mises en forme, deux déclarations sont rendues de la même manière, une seule est conservée.
lang mise en forme des données Lua et Wikicode Langue dans lequel le texte doit être retourné.
snak (entité) Lua et wikicode Code Wikimédia de la langue utilisée pour afficher les libellés (par défaut : français)
ucfirst liste des déclarations Lua et Wikicode ucfirst=- permet de désactiver cela
statementformat déclaration Lua doit être une fonction fonction(statement) return XX retournant une chaîne. Elle sera appliquée à sur chaque déclaration (permet de simplifier certaines infobox Lua)
showdate déclaration Lua et wikicode Pour afficher la date entre parenthèses en petit pour chaque déclaration en utilisant les mêmes qualificatifs que (withdate). Lorsqu'aucune date n'est trouvée, la valeur est affichée sans aucune date.
displayformat snak Lua et wikicode Pour changer le format par défaut entre chaque valeur.
  • weblink transforme les données de type chaîne en lien web
  • raw retourne un format plus brut parfois utile au codage
  • latitude pour Module:Coordinates
  • longitude pour Module:Coordinates

En lua, la valeur du paramètre peut aussi être une fonction.

qualifdisplayformat snak Lua et wikicode Format d'affichage des qualificatifs. Même fonctionnement que le paramètre displayformat. Lorsque ce paramètre est laissé vide, les qualificatifs utilisent le paramètre displayformat comme la valeur principale.
showsource déclaration Lua et wikicode Identifiant d'une source que l'on souhaiterait voir affichée en référence si elle est présente. true affichera toutes les sources utilisant la propriété P248 (« affirmé dans »).
linktopic snak (temporel) Lua et wikicode Type de lien à ajouter aux dates (voir Module:Date). Pour ne mettre aucun lien : linktopic = -
precision snak (temporel) Lua et wikicode Précision avec laquelle les dates doivent être affichées. Valeurs possibles :
  • day
  • month
  • year

Valeur par défaut : day

textformat snak (temporel) Lua et wikicode Format d'affichage des intervalles de temps, tels que définis par Module:Date complexe
  • minimum affichera "1995" à la place de "à partir de 1995".
speciallabels snak (entité) Lua Tables de valeurs spéciales à utiliser à la place du libellé Wikidata pour la valeur de certains éléments, par exemple Module:Dictionnaire Wikidata/Métiers.female
labelformat snak (entité) Lua Fonction de mise en forme des libellés
showlang déclaration Lua et wikicode Lorsque la valeur principale est de type "texte monolingue", son code langue est affiché (utilisé par Module:Site officiel)
showqualifiers déclaration Lua et wikicode Qualificatif à retourner entre parenthèses derrière la valeur de la propriété principale demandée. En wikicode sous le format |showqualifiers = P460, en Lua sous le format showqualifiers = "P460" ou showqualifiers = {"P460", "P461"}
showonlyqualifier déclaration Lua et wikicode Qualificatif de même, mais à retourner seul sans la valeur de la propriété principale demandée
link snak (entité) Lua et wikicode Site vers lequel doivent lier les données de type élément. Par défaut : Wikidata en français. Si égal à "-", aucun lien ne sera créé.
defaultlink snak (entité) Lua et wikicode Lorsque le site demandé par le paramètre link ne retourne pas de lien, affiche un lien entre parenthèses vers le site demandé. Par défaut : Wikipédia en anglais ou, si absent, Wikidata. La valeur "-" désactive le paramètre.
defaultlinkquery snak (entité) Lua defaultlinkquery = {property = 'P279'} le lien créé correspondra à celui fourni par l'élément utilisé dans la propriété P279 (« sous-classe de »).
targetunit snak (quantité) Lua et Wikicode Unité dans laquelle doivent être converties les données de type quantité. Les valeurs possibles sont visibles dans Module:Conversion/Données, par exemple km ou km2.
showunit snak (quantité) Lua et Wikicode

Manière d'afficher l'unité demandée. Valeurs possibles :

  • 'long' : affiche le nom complet de l'unité (par exemple "12,7 mètres")
  • 'short' affiche l'abréviation conventionnellement utilisée ("12,7 m")
  • '-' : aucune unité affichée

défaut : 'short'

rounding snak (quantité) nombre de chiffres Lua et Wikicode Nombre de chiffres significatifs à afficher dans les données de type quantité, après conversion éventuelle (défaut : dépend de la précision indiquée sur Wikidata)
urlpattern snak (string) Lua et wikicode La valeur du paramètre est un chaîne représentant une URL, l'expression "$1" sera remplacée par la valeur Wikidata
Avec la propriété P214 (« identifiant VIAF ») et la valeur de paramètre http://viaf.org/viaf/$1, la valeur de l'identifiant VIAF stockée sur Wikidata sera utilisée pour créer un lien externe..
text snak (string) Lua et wikicode Texte à afficher lorsque la valeur crée un lien externe, c'est-à-dire pour les données de type chaîne avec displayformat = "weblink" ou avec le paramètre urlpattern renseigné. Par défaut : la valeur initiale de la déclaration. Utilisé sur Module:Site officiel Module:Bases/architecture.
novaluelabel snak (novalue) Lua et wikicode Libellé à retourner lorsque la valeur est de type "novalue". Par défaut : "-".

En Lua, la valeur peut-être une fonction.

somevaluelabel snak (somevalue) Lua et wikicode Libellé à retourner lorsque la valeur est de type "somevalue". Par défaut : "inconnu".

En Lua, la valeur peut-être une fonction.

returnnumberofvalues liste des déclarations Lua retourne en deuxième valeur le nombre de valeurs récupérées par getClaims (si la fonction est appelée depuis du Wikicode, le nombre sera concaténé au reste de la chaîne retournée

Exemples

modifier

Définition des liens

modifier
Code Rendu Remarques
{{#invoke:Wikidata|frameFun|formatStatements|entity = Q79 | property = P36}} Le Caire (en) Capitale de l'Égypte, format standard
{{#invoke:Wikidata|frameFun|formatStatements|entity = Q79 | property = P36|link=-}} Le Caire Capitale de l'Égypte, sans lien
{{#invoke:Wikidata|frameFun|formatStatements|entity = Q79 | property = P36|link=wikidata}} Le Caire Capitale de l'Égypte, avec lien vers Wikidata
{{#invoke:Wikidata|frameFun|formatStatements|entity = Q937 | property = P569}} Date de naissance d'Albert Einstein, en utilisant le format standard de Wikipédia en français
{{#invoke:Wikidata|frameFun|formatStatements|entity = Q937 | property = P569|linktopic = football }} Date de naissance d'Einstein, avec liens spécialisés dans le football
{{#invoke:Wikidata|frameFun|formatStatements|entity = Q937 | property = P569|linktopic = -}} Date de naissance d'Einstein, sans lien

Il existe trois types de rang sur Wikidata : "normal", "preferred" et "deprecated". Le rang deprecated est réservé aux données que l'on sait fausse mais que l'on garde pour mémoire, par exemple pour documenter une croyance ancienne qui a été infirmée par des études plus récentes.

Par défaut, seul le rang "preferred" est récupéré, mais le paramètre "rank" permet de changer cela.

Code Rendu Remarques
{{#invoke:Wikidata|frameFun|formatStatements|entity =Q12418 | property = P186}} Peinture à l'huile (en) et panneau de peuplier (d) Matériau de la Joconde, affichage par défaut (donc seulement celles avec le rang "preferred" s'il y en a)
{{#invoke:Wikidata|frameFun|formatStatements|entity =Q12418 | property = P186|rank=valid}} Peinture à l'huile (en), panneau de peuplier (d) et bois (en) rank = "valid" accepte les valeur de rang "normal" et "preferred" (par exemple, le matériau d'un chassis qui a été ajouté ultérieurement à la Joconde)
{{#invoke:Wikidata|frameFun|formatStatements|entity =Q12418 | property = P186|rank=normal}} Bois (en) rank = "normal" ne récupère que les données avec un rang "normal", et ignore celles avec un rang "preferred"

Qualificatifs

modifier

Les qualificatifs sont des données supplémentaires intégrées à une affirmation Wikidata qu'elle permet de préciser.

Code Rendu Remarques
{{#invoke:Wikidata|frameFun|formatStatements|entity =Q12418 | property = P186|showqualifiers = P518}} Panneau de peuplier (d) ne retient que les valeurs ayant un qualificatif P518 (« s'applique à »)
{{#invoke:Wikidata|frameFun|formatStatements|entity =Q12418 | property = P186|qualifier = P518|qualifiervalue=Q1737943}} Bois (en) qualifiervalue définit la valeur que possède le qualificatif. Ici, Q1737943 (« châssis ») précise que l'on demande la matériau du chassis. Notez qu'aucune valeur de rang "preferred" n'étant trouvé, un valeur de rang "normal" est retournée.
{{#invoke:Wikidata|frameFun|formatStatements|entity =Q12418 | property = P186|showqualifiers = P518|rank=valid}} Peinture à l'huile (en), panneau de peuplier (d) (support de peinture (en)) et bois (en) (châssis (en)) showqualifiers affiche la valeur des qualificatifs demandé entre parenthèse, après la valeur principale
{{#invoke:Wikidata|frameFun|formatStatements|entity =Q12418 | property = P186|showdate = true|rank=valid}} Peinture à l'huile (en), panneau de peuplier (d) et bois (en) (depuis ) si des qualificatifs indiquant la date sont fournis, ils sont affichées

Affichage des sources

modifier
{{#invoke:Wikidata|frameFun|formatStatements|entity =Q12418 | property = P276|sourceproperty = P854}} Salle 711 (d)
{{#invoke:Wikidata|frameFun|formatStatements|entity =Q11879536 | property = P460|withsource=Q1645493}} Lisa Gherardini (en)
{{#invoke:Wikidata|frameFun|formatStatements|entity =Q11879536 | property = P460|withsource=Q1645493|showsource=true}} Erreur Lua dans package.lua à la ligne 80 : module 'Module:Weblink' not found. Montre seulement les valeurs données par la source
{{#invoke:Wikidata|frameFun|formatStatements|entity =Q153 | property = P231|showsource=true}} 64-17-5[1],[2],[3]
  1. ChEBI, (ontologie), Institut européen de bio-informatique, consulté le Voir et modifier les données sur Wikidata
  2. Global Substance Registration System, (base de données en ligne), consulté le Voir et modifier les données sur Wikidata
  3. CAS Common Chemistry, (base de données en ligne), consulté le Voir et modifier les données sur Wikidata

Autres options

modifier
Code Rendu Remarques
{{#invoke:Wikidata|frameFun|formatStatements|entity = Q937 | property = P106|conjtype = new line|showdate= true}} John Lennon (-)
Ringo Starr (-)
Paul McCartney (-)
George Harrison (-)
conjtype permet de définir le séparateur entre les valeurs (voir Module:Linguistique). Ici "new line" indique qu'il faut revenir à la ligne après chaque valeur.
{{#invoke:Wikidata|frameFun|formatStatements|entity =Q205309 | property = P793|showdate=true|sorttype=chronological|conjtype =new line}} Construction (d) ()
Premier match (d) ()
Rénovation (en) (-)
Rénovation (en) (-)
Démolition (en) ()
Fermeture (en) ()
sorttype = chronological tente de classer les valeurs par ordre chronologique (en se basant sur les qualificatifs). Ici : les événements principaux (d:P:P793) dans l'histoire de l'ancien Stade d'Arsenal.
{{#invoke:Wikidata|frameFun|formatStatements|entity =Q205309 | property = P793|showdate=true|sorttype=inverted|conjtype = new line}} Fermeture (en) ()
Démolition (en) ()
Rénovation (en) (-)
Rénovation (en) (-)
Premier match (d) ()
Construction (d) ()
sorttype = inverted pour inverser l'ordre chronologique
{{#invoke:Wikidata|frameFun|formatStatements|entity =Q4470 | property = P50|showdate=true|sorttype=P1545|conjtype = new line}} Robert Jordan
Brandon Sanderson (en)
sorttype = P1545 pour trié dans l'ordre de la série
{{#invoke:Wikidata|frameFun|formatStatements|entity =Q12418 | property = P186|displayformat=raw}} Q296955 et Q106857865 affiche les identifiants des valeurs (essentiellement utile pour la programmation)
{{#invoke:Wikidata|frameFun|formatStatements|entity =Q12418 | property = P186|numval=1}} Peinture à l'huile (en) retourne les numval première valeurs répondant au reste de la requête (utile notamment pour récupérer une image)
{{#invoke:Wikidata|frameFun|formatStatements|entity=Q535922 | property=P172 |expl= |value={{WD}} }} Modèle:WD avec expl=, pour que la valeur soir retournée il faut value={{WD}}



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

local wd = {}

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

wd.compare = {}

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

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

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

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

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

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

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

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

-- 

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

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

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

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

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

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

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

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

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

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


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

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

	local entityid = entity.id or entity

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

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

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

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

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

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

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

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

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

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


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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

end

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


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

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

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


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

--=== ENTITY FORMATTING ===

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

	lang = lang or defaultlang

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

function wd.formatEntity( entity, params )

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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


-- === DATE HANDLING ===

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

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

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

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

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

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

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

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


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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

			url = wd.formatSnak(ref.snaks.P854[1], {text = "-"})
			if hasValue('P1476') then
				title = wd.formatSnak(ref.snaks.P1476[1])
			else
				title = mw.ustring.gsub(url, '^[Hh][Tt][Tt][Pp]([Ss]?):(/?)([^/])', 'http%1://%3')
			end

			--todo : handle multiple values for author, etc.
			if hasValue('P813') then -- date de consultation
				accessdate = wd.formatSnak(ref.snaks.P813[1])
			end
			if hasValue('P50') then  -- author (item type)
				author = wd.formatSnak(ref.snaks.P50[1])
			elseif hasValue('P2093') then -- author (string type)
				author = wd.formatSnak(ref.snaks.P2093[1])
			end
			if hasValue('P123') then -- éditeur
				publisher = wd.formatSnak(ref.snaks.P123[1])
			end
			if hasValue('P1683') then -- citation
				quotation = wd.formatSnak(ref.snaks.P1683[1])
			end
			if hasValue('P577') then -- date de publication
				publishdate = wd.formatSnak(ref.snaks.P577[1])
			end 
			if hasValue('P407') then -- langue de l'œuvre
				local id = wd.getId(ref.snaks.P407[1])
				publishlang = getLangCode(id)
			end
			s = modules.cite.lienWeb{titre = title, url = url, auteur = author, editeur = publisher, langue = publishlang, ['en ligne le'] = publishdate, ['consulté le'] = accessdate, ['citation'] = quotation}
			table.insert(hashes, ref.hash)
			table.insert(refs, s)			
		elseif ref.snaks.P854 and ref.snaks.P854[1].snaktype == 'value' then
			s = wd.formatSnak(ref.snaks.P854[1], {text = "-"})
			table.insert(hashes, ref.snaks.P854[1].hash)
			table.insert(refs, s)
		end
	end
	if #refs > 0 then
		if #hashes == #refs then
			return refs, hashes
		end
		return refs
	end
end

function wd.sourceStr(sources, hashes)
	if not sources or (#sources == 0) then
		return nil
	end
	local useHashes = hashes and #hashes == #sources
	for i, j in ipairs(sources) do
		local refArgs = {name = 'ref', content = j}
		if useHashes and hashes[i] ~= '-' then
			refArgs.args = {name = 'wikidata-' .. hashes[i]}
		end
		sources[i] = mw.getCurrentFrame():extensionTag(refArgs)
	end
	return table.concat(sources, '<sup class="reference cite_virgule">,</sup>')
end

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

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

	local datatype = snak.datatype
	local value = snak.datavalue.value
	
	local displayformat = params.displayformat
	if type(displayformat) == 'function' then
		return displayformat(snak, params)
	end

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

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

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

	if datatype == 'tabular-data' then
		return mw.ustring.sub(value, 6, 100)  -- returns the name of the file, without the "Data:" prefix
	end

	if (datatype == 'string') or (datatype == 'external-id') or (datatype == 'commonsMedia') then -- toutes les données de type string sauf "math"
		if params.urlpattern then
			local urlpattern = params.urlpattern
			if type(urlpattern) == 'function' then
				urlpattern = urlpattern(value)
			end
			local url = mw.ustring.gsub(urlpattern, '$1', (value:gsub('%%', '%%%%'))):gsub(' ', '%%20')
			value = '[' .. url .. ' ' .. (params.text or value) .. ']'
		end
		return value
	end
	
	if datatype == 'time' then -- format example: +00000001809-02-12T00:00:00Z
		if displayformat == 'raw' then
			return value.time
		else
			local dateobject = dateObject(value, {precision = params.precision})
			return objectToText(dateobject, params)
		end
	end

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

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

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

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

end

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

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

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

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

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


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

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

	local str

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

	-- ajouts divers
	if args.showlang == true then
		local indicateur = showLang(statement, args.maxLang)
		if indicateur then
			str = indicateur .. '&nbsp;' .. str	
		end
	end
	if args.showqualifiers then
		str = wd.showQualifiers(str, statement, args)
	end

	if args.showdate then -- when "showdate and chronosort are both set, date retrieval is performed twice
		local period = wd.getFormattedDate(statement, args, "-") -- 3 arguments indicate the we should not use additional qualifiers, alrady added by wd.formatStatement
		if period then
			str = str .. " <small>(" .. period .. ")</small>"
		end
	end

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

	return str
end

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

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

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

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

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

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

	local stringTable = {}

	-- instructions ad hoc pour les paramètres concernant la mise en forme d'une déclaration individuelle
	local funs = {
		{param = "showqualifiers", fun = function(str, claims)
			local qualifs = {}
			for i, claim in pairs(claims) do
				local news = wd.getFormattedQualifiers(claim, args.showqualifiers, args)
				if news then
					table.insert(qualifs, news)
				end
			end
			local qualifstr = modules.linguistic.conj(qualifs, "qualif-separator")
			if qualifstr and qualifstr ~= "" then
				str = str .. " (" .. qualifstr .. ")"
			end
			return str
			end
		},
		{param = "showdate", fun = function(str, claims)
			-- toutes les dates sont regroupées à l'intérieur des mêmes parenthèses ex "médaille d'or (1922, 1924)"
			local dates = {}
			for i, statement in pairs(claims) do
				local s = wd.getFormattedDate(statement, args, true)
				if statement then table.insert(dates, s) end
			end
			local datestr = modules.linguistic.conj(dates)
			if datestr and datestr ~= "" then
				str = str .. " <small>(" .. datestr .. ")</small>"
			end
			return str
			end
		},
		{param = "showsource", fun = function(str, claims)
			-- les sources sont toutes affichées au même endroit, à la fin
			-- si deux affirmations ont la même source, on ne l'affiche qu'une fois
			local sources = {}
			local hashes = {}
		
			local function dupeRef(old, new)
				for i, j in pairs(old) do
					if j == new then
						return true
					end
				end
			end
			for i, claim in pairs(claims) do
				local refs, refHashes = wd.getReferences(claim)
				if refs then
					for i, j in pairs(refs) do
						if not dupeRef(sources, j) then
							table.insert(sources, j)
							local hash = (refHashes and refHashes[i]) or '-'
							table.insert(hashes, hash)
						end
					end
				end
			end
			return str .. (wd.sourceStr(sources, hashes) or "")
			end
		}
	}

	for i, group in pairs(groupedClaims) do -- bricolage pour utiliser les arguments de formatStatements
		local str = wd.formatEntity(group.id, args)
		for i, fun in pairs(funs) do
			if args[fun.param] then
				str = fun.fun(str, group.claims, args)
			end
		end
		table.insert(stringTable, str)
	end
				
	args.valuetable = stringTable
	return wd.formatStatements(args)
end


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

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

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

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

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

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

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

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


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

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

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

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

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

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


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

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

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

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

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

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

	return v
end

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

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

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

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


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

	local description
	if lang == defaultlang then
		return  mw.wikibase.description(qid)
	end
	if not entity.descriptions then
		return wd.translate('no description')
	end
	local descriptions = entity.descriptions
	if not descriptions then
		return nil
	end
	if descriptions[lang] then
		return descriptions[delang].value
	end
	return entity.id
end

function wd.Dump(entity)
	entity = wd.getEntity(entity)
	if not entity then
		return formatError("entity-param-not-provided")
	end
	return "<pre>"..mw.dumpObject(entity).."</pre>"
end

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


return wd