Module:Wikidata/Chemin

Une page de Wikiquote, le recueil des citations libres.
Page en cours de rédaction
Cette page est inachevée. Veuillez considérer le plan et le contenu comme incomplet et en préparation, temporaires et sujets à caution. Si vous souhaitez participer, il vous est recommandé de consulter sa page de discussion au préalable.

Le module Wikidata/Chemin est une implémentation un peu adaptée des PropertyPath de Q54871 (« SPARQL ») en Lua poun Wikidata et les clients Wikibase.

Il existe cependant quelques différences entre les chemins de propriétés sparql et cette implémentation:

  • la syntaxe est étendue pour gérer les qualificatifs des déclarations (opérateur >)
  • contrairement au cas de l’implémentation sparql, il est nécessaire de fournir un point de départ pour le calcul des valeurs possibles au bout du chemin
  • il est possible d’utiliser les identifiants Wikidata des propriétés sans préfixe d’uri, et il est également possible d’utiliser les intitulés des propriétés dans la langue principale du wiki (par exemple il est possible d’utiliser père pour la propriété P22 (« père »)
  • l’opérateur ^ ne peut pas fonctionner en lua avec l’API mw.wikibase actuelle (mai 2019)

Limitation : Le support des rangs. Pour l’instant il n’y a pas de moyen de prendre en compte les déclarations de rang « normal » s’il y en a des privilégiées ou « déprécié ».

Chemins[modifier]

Exemple introductif[modifier]

Un chemin pour trouver les petits-enfants "enfant/enfant" sera notre premier chemin. Nous allons l’utiliser en paramètre d’un modèle de démonstration capable d’utiliser des chemins, à partir d’un élément choisi, prenons l’élément du Dieu Grec Zeus (Q34201 (« Zeus ») : {{Liste valeur chemin wikidata|Q34201|enfant/enfant}}

Le modèle liste en premier résultat (au moment de l’écriture de ces lignes) Q572133 (« Antéros »). La raison de sa présence est l’existence d’une déclaration Wikidata
Q34201 (« Zeus »)
P22 (« père ») : Q40901 (« Arès »)

puis sur Q40901 (« Arès ») d’une déclaration

Q40901 (« Arès »)
P22 (« père ») : Q572133 (« Antéros ») .

En réalité Zeus à de nombreux autres petits enfants qui ne sont par montrés parce que le modèle se limite par défaut à un certain nombre de résultats.

De manière similaire, si nous voulons lister les mères des enfants de Zeus nous pouvons utiliser le chemin enfant/P25 grâce à la propriété P25 (« mère ») :

Ces exemples n’utilisent qu’un seul connecteur, / qui permet d’exprimer une séquence de déclarations. Il en existe d’autre que nous allons décrire brièvement dans la section suivante, qui peuvent se combiner pour créer des chemins plus complexes.

Opérateurs[modifier]

Dans ce qui suit, un chemin d’entités sera une séquence d’éléments et de propriétés. Un chemin d’entités sera potentiellement terminé par une valeur Wikidata comme une date ou une quantité. Voici un exemple de chemin d’entités Q1001 (« Mahatma Gandhi »)P31 (« nature de l’élément »)Q5 (« être humain »)P279 (« sous-classe de »)Q215627 (« personne ») ou Q34201 (« Zeus »)P40 (« enfant »)Q40901 (« Arès »)P40 (« enfant »)Q572133 (« Antéros »). Il est possible de créer des chemins à partir des déclarations Wikidata en partant d’un élément est en suivant les propriétés principales et valeurs principales des différentes déclaration, mais également en suivant les propriétés des qualificatifs et leurs valeurs. Dans ce dernier cas, un chemin peut ressembler à ceci Q991 (« Fiodor Dostoïevski »)P19 (« lieu de naissance »)Q656 (« Saint-Pétersbourg »)P1448 (« nom officiel »)P580 (« date de début »)

Nous allons maintenant décrire la manière de faire correspondre les « chemins de propriétés » (qu’on appellera également « motif de chemin »), qu’on peut voir comme des motifs, à l’instar des expressions rationnelles à des chemins d’entités. Pour cela nous allons introduire l’opération de « dévorage », entre un chemin de propriété et un chemin d’entités. On dira qu’une propriété peut « dévorer » un chemin d’entité C si la première propriété du chemin est P. Le résultat de l’opération sera le chemin auquel on aura ôté les deux premiers éléments. Par exemple si

P peut dévorer C pour donner le résultat suivant Q656 (« Saint-Pétersbourg »)P1448 (« nom officiel »)P580 (« date de début »). Aucune autre propriété ne peut dévorer ce chemin. Nous allons l’étendre dans la suite à des chemins plus complexes que ceux composés avec une unique propriété. L’opération de « dévorage » est utilisée pour construire l’opération de « correspondance » entre un chemin de propriété et un chemin d’entité. Elle peut trouver tous les chemins d’entités qu’on peut construire en partant d’un élément et en suivant les différentes propriétés. Un chemin de propriété correspond à un chemin d’entité si le chemin de propriété peut dévorer entièrement le chemin, à l'exception de son dernier élément.


Par exemple : le chemin Mexique → limitrophe de → USA → limitrophe de → Canada correspond au motif limitrophe de/limitrophe de mais ne correspond pas à limitrophe de ni à limitrophe de/limitrophe de/limitrophe de. Le premier ne correspond pas parce que certaines parties du chemin ne sont pas dévorées, et le dernier parce que le dernier limitrophe de n’a rien à dévorer.


Ce module implémente l’opération d’énumérer les chemins d’entités qui correspondent (peuvent être totalement dévorés) par un motif de chemins. Les différents motifs ont définis dans la suite à l’aide d’opérateurs, pour lesquels on étendra l’opération de dévorage.

Un chemin est construit à partir de chemins élémentaires (des propriétés) et d’opérateurs qui combinent des chemins.

chemin élémentaire
Un chemin élémentaire est soit un identifiant de propriété, comme P31, soit un intitulé de propriété en français comme nature. Un chemin élémentaire dévore un chemin d’entité comme expliqué ci-dessus.
/
l’opérateur de séquençage. Voir l’exemple introductif. Si Modèle:Var est un chemin et Modèle:Var est un chemin alors Modèle:Var/Modèle:Var est également un chemin Modèle:Var/Modèle:Var dévorera un chemin d’entités CE si P1 dévore CE dans un premier temps, puis si P2 dévore le résultat de ce premier dévorage dans un deuxième temps.
|
L’opérateur d’alternative. Si Modèle:Var est un chemin et Modèle:Var est un chemin alors Modèle:Var|Modèle:Var est également un chemin. Modèle:Var|Modèle:Var dévorera un chemin CI si soit P1 dévore IP, soit P2 dévore CI. Le résultat est le résultat de l’opération de dévorage de l’alternative qui correspond.
?
Un opérateur qui rend le chemin qui précède optionnel. Il « dévorera » quelque chose quoi qu’il arrive. Par exemple le motif limitrophe de? en partant de l’élément « Mexico » correspondra au chemin d’entité Mexique (dans ce cas le résultat de l’opération laisse le chemin inchangé) mais également le chemin Mexique → limitrophe de → USA (dans ce cas le résultat du dévorage est USA .
*
Un opérateur (qui correspond à l’étoile de Kleene) qui comme le précédent dévore quelque chose quoi qu’il arrive. Si P est un motif, alors P* est aussi un motif. Il peut manger des chemins de taille arbitraire (en théorie, il existe une limite dans l’implémentation) en répétant le dévorage autant de fois que possible l’opération de dévorage de P. Par exemple le code (père|mère)* va correspondre aux chemins d’entités qui mène d’une personne vers tous ses ancêtres (en incluant elle-même). Elle trouvera le père et la mère de quelqu’un, puis les parents de ces parents, les parents des grands parents et ainsi de suite.
+
P+ est équivalent à P/P*. Similaire à l’opérateur * sauf que le chemin sous-jacent P doit pouvoir dévorer au moins une fois.
>
Cet opérateur sert dans le cas ou on s’intéresse aux qualificatifs d’une déclaration. Par exemple le motif union de>de va dévorer les « snaks » qualificateur avec la propriété P642 (« de ») des déclarations Modèle:P' d’un élément. La valeur à partir de laquelle le prochain opérateur dans une séquence commencera est la valeur du qualificatif.
Cet opérateur peut également être utilisé au tout début d’un motif si le point de départ du chemin est une déclaration, et pas un élément (voir l’exemple plus bas).
!
Cet opérateur permet de définir une correspondance par la négative en spécifiant des propriétés qui ne seront pas dévorées. Toute propriété non listée pourra l’être. Il est suivi soit d’une propriété, soit d’une liste de propriété entre parenthèses séparées par des |. Par exemple !(nature|sous-classe de) vont correspondre avec n’importe quel chemin d’une seule propriété, comme Donald Duck → père → Picsou, mais pas Ghandi → nature → être humain ni humain → sous-classe de → animal. Également utilisable avec l’opérateur > pour exclure un ensemble de qualificateur.
{nombre} et {min, max}
Cet opérateur est utile pour spécifier la répétition d’un dévorage de chemin un certain nombre (borné) de fois. P{4} est équivalent à P/P/P/P, c’est-à-dire que P doit dévorer exactement quatre préfixes d’un chemin, et P{4,6} doit dévorer entre 4 et 6 fois. C’est équivalent à P/P/P/P/P?/P?

Résultats[modifier]

Ce module représente et retourne les chemins qui sont en correspondance avec un chemin sous forme d’une structure de données de type ResultNode qui représente un noeud d’un chemin en correspondance.

Techniquement le chemin est une liste chaînée de ces nœuds. La liste représente le chemin en ordre inverse, le nœud retourné est le dernier nœud d’un chemin. Chaque nœud peut être considéré comme un « snak » ou comme une « déclaration » (statement) dans le modèle de données Lua (en fait ce sont exactement des snak ou statement du module mw.wikibase, auxquels sont ajoutés d’autres attributs et méthodes). Un nœud de résultat peut être utilisé tel quel comme n’importe quel snak ou statement dans les fonctions du module Module:Wikidata.

Les nœuds précédents dans la liste peuvent être récupérés grâce à l’attribut "parent" d’un nœud.

Interface[modifier]

  • Main functions
    iterate
    path (string), start_item (string, entity object) -> iterator (on result node)
    function that takes a path and a start item and returns an iterable object, usable in lua for loops that iterates over the set of paths matching the pattern, each represented by a ResultNode (see above)

Utilisation[modifier]

Fonctions exportables :

  • fonction(frame) – description (courte description de fonction(frame) et autres informations pertinentes).
  • fonction2() – description2 (courte description de fonction2() et autres informations pertinentes).

Autres fonctions :

  • fonction() – description2 (courte description de fonction() et autres informations pertinentes).

Modules externes et autres éléments dont ce module a besoin pour fonctionner :

  • mw.title – description (courte description expliquant la dépendance de ce module externe).

Exemples[modifier]

partir d’une déclaration[modifier]

Lister les dates de début des mandats des présidents de la république française Q191954 (« président de la République française ») par un moyen un peu tordu pour l’exemple. Utilise P1308 (« titulaire ») sur l’élément « président de la RF pour récupérer tous les présidents et P39 (« fonction ») sur les éléments des présidents, et P522 (« type d'orbite ») comme qualificateur.

local elem_presidence = "Q191954"
local res = ""
for fonction in ppath.iterate(elem_presidence, "titulaire/fonction") do -- pour chacune des fonctions occupées par présidents de la république
	res = res .. wd.formatStatement(fonction)
	if elem_presidence == fonction:item_value() then -- si la fonction est celle de président de la RF
		for date in ppath.iterate(fonction, ">P580") do -- on itère sur les dates de début des déclarations
			res = res .. wd.formatSnak(date)
		end
	end
end

du module Module:Wikidata/Chemin/Documentation/Exemple/prez qui nous donne (une seule date parce qu’il n’y a pour l’instant pas de support des rangs)

Erreur de script : le module « Wikidata/Chemin/Documentation/Exemple/prez » n’existe pas. 

Note : Ce code est légèrement plus compliqué qu’il pourrait l’être parce que les date de début de titularisation sont aussi présentes en qualificatif des déclarations « titulaires » de Q191954 (« président de la République française »), on pourrait de contenter de

local elem_presidence = "Q191954"
local res = ""
for date in ppath.iterate(elem_presidence, "titulaire>P580") do -- pour chacune des fonctions occupées par présidents de la république
	res = res .. wd.formatSnak(date)
end

Autres[modifier]

Voir aussi la page de test permettant de tester diverses modifications apportées (quand il y en aura une)


local datastructure = require "Module:Wikidata/Chemin/Path"
local parser = require "Module:Wikidata/Chemin/parser"
local results = require "Module:Wikidata/Chemin/Resultat"

local iter = require "Module:Iterateurs"
local props = require "Module:Propriétés"

local path = {}

--------------
-- TODO : 
--        * Update the "between" path to handle it better epsilon paths
--        * Test full path rendering
--        * 
--------------

-- Definition of a PropertyPath class

local PropertyPath = {}
PropertyPath.__index = PropertyPath

--[[ Datastructure for the paths that will match a path pattern
A path matching the pattern "subclass of*" will be a chain of statements and snaks nodes.  
If we got statements of the form (no qualifiers here, just subject with the main statement snak) :
* <human> <subclass of> <ape>
* <ape> <subclass of> <mammal>
* <mammal> <subclass of> <animal>

a matching path like "<human> -> <ape> -> <mammal> -> <animal>" will be reprensented by a linked list of "ResultNode" objects. 
A result node object is a mw.wikibase "statement" standard object augmented with a few methods and a link that goes from the statement or snak to the previous node in the path.
{ 
   <mammal> <subclass of> <animal>
   "parent" = {
      <ape> <subclass of> <mammal>
      "parent" = {
         <human> <subclass of> <ape>
         "parent" = EpsilonRNode(<human>, "parent" = nil)
      }
   }
}

--]]

local ResultNode = results.ResultNode
local StatementRNode = results.StatementRNode
local QualifierRNode = results.QualifierRNode
local EpsilonRNode = results.EpsilonRNode


------------------------------------------------------------------------------------------------------


local function iterate_on_snaks(
	start_rnode, 
	property_filter_criteria, 
	snak_map_iterator,
	rnode_type)
    assert(snak_map_iterator)
	return iter.pair_map(
    	iter.flatten(
    		iter.select_vals(
    			iter.pair_filter(
    				snak_map_iterator, 
    				property_filter_criteria
    			)
    		),
    		iter.on_vals
    	),
    	function(value) return rnode_type:create(value, start_rnode) end
    )
end

-- creates an iterator that will iterate over all the statements
-- of a specific property of an item

local function iterate_on_statement(start_rnode, property_filter_criteria)
	local item = mw.wikibase.getEntity(start_rnode:item_value())
	return iterate_on_snaks(
		start_rnode, 
		property_filter_criteria,
		iter.on_pairs(item.claims),
		StatementRNode
	)
end

local function iterate_on_statement_qualifier(statement, qualifier_filter_criteria)
	if statement.qualifiers then
		return iterate_on_snaks(
			statement, 
			 qualifier_filter_criteria,
			iter.on_pairs(statement.qualifiers),
			QualifierRNode
		)
	else
		-- no qualifier table when the statement has no qualifiers
		return function() return nil end
	end
end

local iterate_on_statement_from_property = function(start_rnode, pid)
    claims = mw.wikibase.getBestStatements(
    	start_rnode:item_value(),
      	props.normalize(pid)
    ) or {}
	
    return iter.pair_map(
    	iter.pair_filter(iter.on_pairs(claims), function(key, val) return true end),
    	function(key, value) return StatementRNode:create(value, start_rnode) end
    )
end


local function iterate_on_star(start_rnode, child_pnode, depth, iterated, max)
	
	-- start_rnode : the result node from which we will iterate
	-- child_pnode : the path within the star operator (for example P31/P31 if our node is (P31/P31)*
	iterated = iterated or {} -- iterated is the store of already iterated starting points items to avoid infinite loops
	-- max : the max number of iteration depth to go, nil for no limit
	
	depth = depth or 1
	
	--[[
	In pseudo code using a « yield » operator, the algorithm would be
	
	algo star(startnode)
	   for each value v which match child_pnode from startnode
	      yield v
	      for each value vchild in star(v)
	         yield vchild
	      end for
	   end 
	end
	
	But we can’t use a yield operator if the « coroutine » module on lua is not activated. 
	So we must translate this into something more complicated.
	
	Luckily the approach to write iterators in term of composition seems to pay off and
	it seem possible to write code structurally similar to this algorithm thanks to the 
	« flatten » iterator and a recursive closure that creates iterator to handle the
	recursivity implied by the « star » operator nature.

	--]]
	
	function creator()
		return function(start_rnode)
			local depth_overflow = not (not max or depth < max)
			
			if not iterated[start_rnode:item_value()] and not depth_overflow then
				iterated[start_rnode:item_value()] = true
				return iterate_on_star(start_rnode, child_pnode, depth + 1, iterated, max)
			else
				return function() end
			end
		end
	end
		
	return iter.chain(
		iter.singleton(start_rnode),
		iter.flatten(
			child_pnode:iterate(start_rnode), 
			creator()
		)
	)
end

local iterate_on_plus = function(start_rnode, child_pnode, max_depth)
	local first = true
	iterated = iterated or {}
	
	return iter.flatten(
		child_pnode:iterate(start_rnode),
		function(rnode)
			return iterate_on_star(rnode, child_pnode, 1, iterated, max_depth)
		end
	)

end

--[[
Test :
p.test("Q5", "subclass of+") -- at the time writing, "Q215627" is the only direct superclass of human. It does not show up, but there is numerous superclass in the result
--]]

--[[ an iteraton to handle "/" operator sequences, for example « P31/P279* »
 "creators" is a table of functions that needs to create iterators of child nodes.
 In our example, the first cretors element will be a function that takes an item object and
   will return an iterator over P31-statements of this item
 the second one will create an iterator over the path « P279* » and so on.
 The resulting iteratior will iterate on each elements of the second iterator starting from each iterator over the second one
 for each elements in the first one.
--]]


local function iterate_on_iterator_creators(start_rnode, creators, i)
	i = i or 1
	if not(tonumber(i)) then i = 1 end
	-- main iterator : the iterator that will iterate on the values on this node of the path
	local main_iterator = creators[i]:iterate(start_rnode)
	
	if i < #creators then
		--trying to initialize the iterator for the next node with a value of the current one, if we can
		local rnode = main_iterator()
		
		while rnode and not(rnode:has_an_item()) do
			rnode = main_iterator()
		end

		-- could not initialize the next iterator with a proper item ; returnun the empty iterator function
		if not rnode then return function() return end end
		
		-- we found a proper value to iterate on for the next node in the path
		
		-- final iterator : the iterator that will iterate 
		-- on elems that will be returned by each iterations 
		-- on the iterator created by the main client
		
		local final_iterator = iterate_on_iterator_creators(rnode, creators, i+1)
		return function()
			while final_iterator ~= nil do
				-- pulling the element from the next node iterator in the sequence
				local final_elem = final_iterator()
				if final_elem then
					return final_elem
				else
					-- we pulled the last elem for this value, getting a new value 
					-- for this node path and regenerate the next node iterator to pull new final values
					
					local rnode_value = main_iterator()
					
					-- return the element pulled from the next node iterator
					-- if the property has item datatype is not a special value and has the right snaktype
					-- as we can't continue path on other kind of values
					
					if rnode_value then
						if rnode_value:has_an_item() then
							final_iterator = iterate_on_iterator_creators(rnode_value, creators, i+1)
						end
					else
						--we're over, no next value for this node to continue the path
						return
					end
				end
			end
		end
	elseif i == #creators then
		return main_iterator
	end
end

--[[ JSBach : Q1339 ;
Testing with :
test("Q1339", "child/child")

wikidata query equivalent query : 
select ?grandchild where {
  wd:Q1339 wdt:P40/wdt:P40 ?grandchild
}


Adam : wd:Q70899 
test("Q70899", "child/child/child")
wikidata query equivalent query : 
select ?grandgrandchild where {
  wd:Q70899 wdt:P40/wdt:P40/wdt:P40 ?grandgrandchild
}
--]]

local iterate_on_alternatives = function(start_rnode, pnodes)
	local i=1
	local current_iter = pnodes[i]:iterate(start_rnode)
	
    return function ()
    	-- loop to go to next iterator if there is empty one in the list
        while true do
	        local res = current_iter()
	        -- res is an iterator itself ; getting its result
	        if res then
	        	return res
	        else
	        	i = i + 1
	        	if i <= #pnodes then 
	        		-- following to next iterator and resume loop
	        		current_iter = pnodes[i]:iterate(start_rnode)
	        	else
	        		-- no current iterator : ending
	        		return nil
	        	end
	        end
        end
    end
end

--[[
Adam's father or mother : no value of course
p.test('Q70899', "P22|P25")

JS Bach's
p.test("Q1339", "P22|P25")

--]]

function iterate_on_nodes_beetween(start_rnode, pnode, min, max)
	seq = {}
	i  = 1

	while i <= min do
		table.insert(seq, pnode)
		i = i + 1
	end
	
	local sequence_obj = {}
	function sequence_obj:iterate(next_rnode)
		return iterate_on_iterator_creators(next_rnode, seq, min)
	end
	if max then
		local star_obj = {}
		function star_obj:iterate(next_rnode)
			return iterate_on_star(next_rnode, pnode, 1, iterated, max-min)
		end
		return iterate_on_iterator_creators(
			start_rnode,
			{
				sequence_obj,
				star_obj
			}
		)
	else
		return sequence_obj:iterate()
	end
end

function iterate_maybe(start_rnode, pnode)
	iterator = pnode:iterate(start_rnode)
	local self_done = false
	return function()
		if not self_done then
			val = iterator()
			if val then return val else
				self_done = true
				return start_rnode
			end
		end
	end
end

function PropertyPath:new(str)
    local obj = {["path"]=str} 
    setmetatable(obj, self)
    
    ast = parser.parse_path(str)
    assert(ast, "parser did not ruturn a node")
    obj.node  = ast
    
    return obj
end

local function entityId(entity)
	if type(entity) == 'string' then
		return entity
	end
	return entity.id
end


function norm_start_point(start_point)
	if type(start_point) == "string" then
		return EpsilonRNode:create(start_point)
	elseif type(start_point) == "table" then
		if start_point["claims"] ~= nil then
			-- assume this is an item or entity object
			return EpsilonRNode:create(start_point.id)
		elseif start_point["is_RNode"] then
			return start_point
		elseif start_point["qualifiers"] or start_point["mainsnak"] then
			local itemid = string.gmatch(start_point.id, "^.*[^$]")() -- extract the item id from the starting statement
			return StatementRNode:create(start_point, EpsilonRNode:create(itemid))
		end
	end
	mw.logObject(start_point)
	error("from function norm_start_point of module PropertyPath : wrong type for start_point", tostring(start_point)) -- TODO : Log a better error
end

function PropertyPath:iterate(start_point)
	start_point = norm_start_point(start_point)
	return self.node:iterate(start_point)
end

local PropertyNode = datastructure.PropertyNode
local AlternativeNode = datastructure.AlternativeNode
local SequenceNode = datastructure.SequenceNode
local QualifiedStatementNode = datastructure.QualifiedStatementNode
local NegatedPropertySetNode = datastructure.NegatedPropertySetNode
local PlusNode = datastructure.PlusNode
local StarNode = datastructure.StarNode
local BetweenNode = datastructure.BetweenNode
local MaybeNode = datastructure.MaybeNode
local QualifierSnakNode = datastructure.QualifierSnakNode

function PropertyNode:iterate(rnode)
	return iterate_on_statement_from_property(rnode, self.property)
end

--[[
test("Q5", "subclass of")
--]]

function AlternativeNode:iterate(rnode) 
	return iterate_on_alternatives(rnode, self.nodes)
end

function NegatedPropertySetNode:iterate(rnode)
	return iterate_on_statement(rnode,
		function (property, val) return self:matches(property) end
	)
end

--[[
test("Q90, ""!(P150)")
--]]

function SequenceNode:iterate(rnode) 
	return iterate_on_iterator_creators(rnode, self.nodes)
end

function QualifiedStatementNode:iterate(rnode)
	local statement_iterator = iterate_on_statement(
		rnode,
		function (key, value)
			return self.property:matches(key)
		end
	)
	local qualifier_iterator_creator = function(statement) 
		return iterate_on_statement_qualifier(
			statement, 
			function (key, value) return self.qualifier:matches(key) end
		)
	end
	
	return iter.flatten(statement_iterator, qualifier_iterator_creator)
end

--[[ to test with :
p.test("Q79529", "union of>of")
p.test("Q105019",'P22{1,6}'
--]]

function QualifierSnakNode:iterate(statementnode)
		return iterate_on_statement_qualifier(
			statementnode, 
			function (key, value) return self:matches(key) end
	)
end

--[[ to test with :
for x in p.iterate("Q79529", "union of") do p.test(x, ">of") end
--]]

function StarNode:iterate(rnode)
	return iterate_on_star(rnode, self.node)
end

function PlusNode:iterate(rnode)
	return iterate_on_plus(rnode, self.node)
end

function BetweenNode:iterate(rnode)
	return iterate_on_nodes_beetween(rnode, self.node, self.min, self.max)
end

function MaybeNode:iterate(rnode)
	return iterate_maybe(rnode, self.node)
end

-- returns an iterator on the result set of a path from a specific node
-- ppath acn either be a string representing a path or a compiled path
function path.iterate(start_node, ppath)
	if start_node == nil then error("the start node is mandatory to get result on a path, it is nil") end
		
	if type(ppath) == "table" then
		return ppath:iterate(start_node)
	else
		return path.PropertyPath:new(ppath):iterate(start_node)
	end
end

-- function that return a boolean
-- true if there is a path matching ppath from start_node that ends with the value "value"
-- (currently only works if "value" is a Qid string)
function path.matches(start_node, ppath, value)
        for val in path.iterate(start_node, ppath) do
                if val:item_value() == value then
                       return true
                end
        end
        return false
end

----------------------------

--[[
p.test("Q5", "P279")
p.test(mw.wikibase.getEntity("Q5"),  "P279")
for x in p.iterate(mw.wikibase.getEntity("Q5"), "P279") do p.test(x,  "P279") end -- test if we can continue iteration of an RNode object
Complex test : 
p.test("Q27929033","P1552>!()/P31") => OK
p.test("Q27929033","subclass of/P1552>!()/P31") => NOK

--]]
function path.test(start_point, ppath)
	for x in path.iterate(start_point, ppath) do 
		mw.log("woot")
		if x then
			mw.log(x:item_value())
		end
	end
end

-----------------
-- fonctions retournant une déclaration ou un snak qualificatif en fonction d’un chemin de propriété
-- utilisé pour les tris, retourner une clé de tri pour une déclaration choisie par un chemin ou un ensemble de chemins

function path.snak_key_by_path(path)
	local path_to_key = path.PropertyPath:new(path)
	return function(claim)
		return (path_to_key:iterate(claim)())
	end
end

-- takes several property paths and creates a function that returns 
-- the first value with a match

-- example : local get_key = wd.snak_key_by_paths{">P80","P800|P801"}
--           get_key(claim)
-- returns the qualifier value of P80 of the claim if it exists, if not returns the main statement value
-- of P800 of the main value of the « claim » statement, if not the P801 one
-- (used in Module:Infobox/fonctions/personne)

-- Note on the example : TODO : would be equivalent to a single path ">P80|P800|P801" but it’s not possible yet

function path.snak_key_by_paths(paths)
	local paths_to_key = {} 
	for k, pat in ipairs(paths) do
		paths_to_key[#paths_to_key + 1] = path.PropertyPath:new(pat)
	end
	return function(claim)
		-- returns the first value of the first matching path starting from « claim »
		for k, path_to_key in ipairs(paths_to_key) do
			local res = path_to_key:iterate(claim)()
			if res then return res end
		end
	end
end


----------------------------

path.PropertyPath = PropertyPath

return path