XPath
neoreb3-Mar-2007/19:09:13+1:00
Bonjour à tous

En utilisant prolog.r, j'ai programmé un petit interpreteur XPath.
Je ne sais pas si cela a déjà été fait.
Ca ne respecte pas exactement la syntaxe XPath. C'est juste un proto. Voci quelques exemples qui vous montrent ce qu'il est possible de faire. Le programme est sur mon rebsite.

<movie>
    <title>Star Trek: Insurrection</title>
    <star>Patrick Stewart</star>
    <star>Brent Spiner</star>
    <theater>
        <theater-name>MonoPlex 2000</theater-name>
        <showtime>14:15</showtime>
        <showtime>16:30</showtime>
        <showtime>18:45</showtime>
        <showtime>21:00</showtime>
        <price>
            <adult>$8.50</adult>
            <child>$5.00</child>
        </price>
    </theater>
    <theater>
        <theater-name>Bigscreen 1</theater-name>
        <showtime>19:30</showtime>
        <price>$6.00</price>
    </theater>
</movie>

For selecting the theaters
---------------------------------------------
XPath==> /movie/theater

[theater [theater-name ["MonoPlex 2000"] showtime ["14:15"] showtime ["16:30"] showtime ["18:45"] showtime ["21:00"] price [adult ["$8.50"] child ["$5.00"]]]]
[theater [theater-name ["Bigscreen 1"] showtime ["19:30"] price ["$6.00"]]]

For selecting all the stars
----------------------------------------
XPath==> //star

[star ["Patrick Stewart"]]
[star ["Brent Spiner"]]

For selecting the first showtime of all theaters
------------------------------------------------------------------------
XPath==>//theater/showtime(1)

[showtime ["14:15"]]
[showtime ["19:30"]]

For selecting all the showtimes of the second theater
------------------------------------------------------------------------------------
XPath==>//theater(2)/showtime

[showtime ["19:30"]]

For selecting all the showtimes of the Bigscreen1 theater
-----------------------------------------------------------------------------------------
XPath==>//theater[./theater-name/Bigscreen 1]/showtime

[showtime ["19:30"]]

For selecting the theaters with a showtime at 21:00
--------------------------------------------------------------------------------
XPath==>//theater[./showtime/21:00]

[theater [theater-name ["MonoPlex 2000"] showtime ["14:15"] showtime ["16:30"] showtime ["18:45"] showtime ["21:00"] price [adult ["$8.50"] child ["$5.00"]]]]

Goldevil4-Mar-2007/16:14+1:00
J'ai constaté que tu as utilisé %xml2rebxml.r (qui a son opposé rebxml2xml.r) .
Sur rebol.org, j'ai découvert également rebelxml.r et xml-parse.r.

J'ai une question pour ceux qui ont déjà fait du xml avec Rebol.

Comme je dois me lancer dans un logiciel sous rebol qui permet de convertir des paquets wddx (http://www.openwddx.org) en variables rebol, et vice versa.

Parmis ces trois scripts qui permettent de manipuler des fichiers XML, lesquels vous semblent les plus intéressants ?
coccinelle4-Mar-2007/21:00:54+1:00
Joli, joli,

Ca me fait très très plaisir de voir une utilisation vraiment originale de prolog.r.

Marco.
neoreb5-Mar-2007/13:31:27+1:00
ah marco = coccinelle
merci merci
Bravo pour prolog.r. J'ai été épaté par ce script.
Je n'ai malheureusement pas le temps de développer un interpréteur XPath complet (je suis encore trop laborieux en rebol) mais xpath.r montre comment le faire.

J'ai une question à te soumettre:
Dans mon script (voir ci-dessous) je force la dérivation des instances du prédicat descendant et pour ça je me sers de db2. C'est une magouille. Ce que j'aurais aimé faire c'est me servir de db et ensuite retirer les deux règles tree_geometry_rules de db (avec un retract). Mais je n'y suis pas arrivé.

tree_geometry_rules: [
	descendant [X Y][
		child [X Y]]
	descendant [X Y][
		child [X Z]
		descendant[Z Y]]]
db2: copy db
assert  db2 tree_geometry_rules
extended_db_facts: copy []
for-which db2 [X Y] [
	descendant [X Y]
][
	append extended_db_facts compose/deep [descendant [[(X)] [(Y)]]]
]
assert db extended_db_facts
...
neoreb5-Mar-2007/13:35:13+1:00
Goldevil,

je ne sais pas trop repondre à ton mail.
Je n'ai pas testé rebelxml et xml-parse.
Par contre, à mon avis, si tu as juste besoin de traduire des paquets wddx en rebol, xml2rebxml conviendra parfaitement.
coccinelle5-Mar-2007/18:09:06+1:00
He oui, Coccinelle = Marco.

Avec prolog.r tu peux assez facilement séparer tes règles de tes faits ou tes déductions. Tu trouveras un exemple dans la documentation ici : http://www.rebol.org/cgi-bin/cgiwrap/rebol/documentation.r?script=prolog.r

Le principe est assez simple, il suffit dans tes clauses de préfixer les prédicats par le nom de la base. Ca devrait te donner quelque chose comme çà
tree_geometry_rules: [
    descendant [X Y][
        db/child [X Y]
    ]
    db/descendant [X Y][
        db/child [X Z]
        db/descendant[Z Y]
    ]
]
for-which tree_geometry_rules [X Y] [
	descendant [X Y]
][
	assert db compose/deep [descendant [[(X)] [(Y)]]]
]
Idem, dans xpath-rule tu peux faire de même (db/xml ..., db/child ... ou db/descendant ...) ce qui t'éviteras de fusionner les faits avec les règles (assert db extended_db_facts et assert db xpath_rules)

Si je suis confus et que tu ne comprends pas comment faire, j'adapterai ton script en indiquant ce que j'ai changé.
coccinelle5-Mar-2007/18:15:49+1:00
En me relisant, je m'apperçoit de quelques petites fautes :
tree_geometry_rules: assert none [
    descendant [X Y][
        db/child [X Y]
    ]
    descendant [X Y][
        db/child [X Z]
        descendant[Z Y]
    ]
]
for-which tree_geometry_rules [X Y] [
	descendant [X Y]
][
	assert db compose/deep [descendant [[(X)] [(Y)]]]
]


guest25-Mar-2007/18:52:41+1:00
Ptite question en passant:
En bindant la base des règles dans la base des faits, ça éviterait pas de prefixer les règles ?
neoreb5-Mar-2007/19:15:36+1:00
ok c'est très clair et c'est pas mal du tout comme possibilité. je vais modifier mon script.
je n'avais pas vu la doc que tu cites mais uniquement celle
accessibe à http://www.ladyreb.org/wiki/doku.php?id=prolog

Par contre dans aucune des 2 docs tu ne fournis un exemple d'utilisation de retract. Tu mentionnes juste que tu ne l'as pas vraiment testé. As ton avis ca marche ? Et si je comprends bien la doc un retract db descendant devrait supprimer les faits et les règles dont la tête est descendant [X Y] ? A quoi sert le raffinement /all ?

merci pour les infos
coccinelle5-Mar-2007/21:32:25+1:00
Alban,

J'ai un suggestion. En laissant l'unification faire son travail, tu peu simplifier xpath-rule. De même, je ne vois pas bien à quoi sert les deux prédicat xpath qui à mon avis pourraient être supprimés ce qui donnerait :
xpath_rules: [
	; for addressing the whole document
	xpath ["/" X][
		xp [["root" "" "" "" ""] X]]	
	xp [["root" "" "" "" ""] doc][
		xml [X]]
	; for not having path starting with / (would be more difficult to handle in the parsepath function)
	xpath [Pl X][
		xp [(parsepath join "root" Pl) X]]
	; child axis		
	xp [[Pathup "/" Test "" ""] X][
		xp [(parsepath Pathup) Y]	
		child [X Y]
		equal? [(to-string X/1) Test]]
	; descendant axis
	xp [[Pathup "//" Test "" ""] X][
		xp [(parsepath Pathup) Y]		
		descendant [X Y]
		equal? [(to-string X/1) Test]]
	; nodetest tied with a position
	xp [[P1 P2 P3 _ Pos] X][
		not-equal? ["" Pos]	
		xp [(parsepath join P1 [P2 P3]) X]		
		index [X Pos]]
	; nodetest tied with a predicate (i.e. a path relative to the context node . (dot))	
	xp [[P1 P2 P3 P4 _] X][
		not-equal? ["" P4]		
		xp [(parsepath join P1 [P2 P3]) X]
		xp [(parsepath join P1 [P2 P3 next P4]) Y]
		descendant[Y X]]
]

Pour ce qui est de retract, comme je l'ai écrit, je pense que ça marche mais je sais que les tests faits étaient succints.

Marco.
neoreb5-Mar-2007/22:37:01+1:00
oui effectivement c'est bien mieux.
quant aux 2 xpa, en effet ils ne servaient strictement à rien...
Je remodifierai le script demain et j'ecrirai quelque part
Many thanks to Marco for his helpful comments

merci
coccinelle7-Mar-2007/22:09:30+1:00
Alban,

Un petite suggestion. Plutôt que ces 2 lignes :
trim/lines xmldata
parse xmldata [any [to "> <" mark: (mark: next mark remove mark mark: next mark) :mark]]
tu devrais utiliser ce bout de code :
space: charset " ^/^M^-"
parse xmldata [any [
	">" s: some space e: (s: remove/part s e) :s
|
	s: some space e: "<" (s: remove/part s e) :s
|
	skip
]]
qui supprime les espace, tab, CR ou LF précédent ou suivant un tag.

Et puis je me dis que comme prolog est fait pour balayer un arbre, il serait peut-être plus simple et rapide de balayer l'arbre construit par xml2rebxml que de le scanner par la fonction parse_doc

Je vais y réfléchir.

Marco
coccinelle8-Mar-2007/16:01:36+1:00
C'est encore moi Alban,

Il me semble possible d'écrire la fonction parse-doc un peu différemment pour qu'elle génère les prédicats descendant [descendant ancêtre] en une seule passe. J'ai fais quelque chose qui donne le bon nombre de résultat mais c'est pas tout à fait çà. J'utilise le parsing pour le faire. Voici le code :
parse-doc: func [
	data [block!]
	/local parents elements rule element attribute search wrk-facts
][
	parents: copy []
	elements: copy/deep [[]]
	parse data rule: [any [
		element: skip ( ; process the element
			unless search: find elements/1 element/1 [
				search: head insert elements/1 reduce [element/1 0]
			]
			append db_facts compose/deep/only [index [(element) (search/2: search/2 + 1)]]
			append db_facts compose/deep/only [child [(element) (parents/1)]]
			wrk-facts: tail db_facts
			foreach parent parents [
				insert wrk-facts compose/deep/only [descendant [(element) (parent)]]
			]
		)
		any [attribute: word! string! ( ; process the attributes
			insert db_facts compose/deep/only [attribute [(element) (attribute)]]
		)]
		( ; Push the current element
			insert/only parents element ; stack the tag
			insert/only elements copy [] ; stack a new element list
		)
		opt into rule ; process the childs
		( ; Pop the current element
			remove parents ; unstack the element
			remove elements ; unstack the element list
		)
	]]
]
L'appel de la fonction se fait un peu plus loin ainsi :
doc: copy [/]
doc: append/only doc xml2rebxml xmldata
parse-doc doc
db: assert none [xml [doc]]
assert db db_facts
toute la partie construisant les prédicats "descendant" n'est plus nécessaire.

A toi de voir si c'est quelque chose qui peut t'intéresser.

Tu peux noter ma manière de faire lorsque je dois parser des structures imbriquées. Je stack des données avant de plonger dans une structure imbriquée et je de-stack après. C'est en général beaucoup plus rapide que l'appel de fonction récursives.

Marco.
neoreb8-Mar-2007/20:00:15+1:00
Salut Marco,

Merci pour toutes tes sugestions. J'y réponds

1. ok pour le code qui suprime les espaces je vais l'intégrer.

2. Pour la suggestion qui est de "balayer l'arbre construit par xml2rebxml avec Prolog", je ne suis pas sûr de comprendre. Cela voudrait-il dire que tu n'as plus de faits instances de child & index mais seulement le fait xml [doc]?
Sinon il est possible de se passer de parse_doc. Il suffit de réécrire xml2rebxml.r pour qu'il génére les faits atomiques (mais je ne crois que c'est ça que tu voulais dire).

3.Ta fonction parse-doc est tres interessante pour moi car je ne maitrise pas du tout la fonction parse. Je vais donc l'étudier.
Par contre, le but de Prolog est d'abord de faire de la déduction. Dans mon script, j'ai forcé la déduction des faits instances de descendant uniquement pour avoir de meilleurs performances. J'aurais en fait préféré laisser les deux règles Tree_geometry_rules dans la base. En produisant les faits descendant depuis la fonction parse_doc comme tu me le proposes, je ne pourrais plus laisser la possibilité de mettre en commentaire la derivation forcée des faits et de décommenter l'insertion des deux règles Tree_geometry_rules dans la base (voir script).

4. Ceci dit si tu souhaites programmer un interpréteur xpath complet (ça serait super car tu le ferais bien mieux que moi ... et puis je n'ai pas le temps) alors bien sûr il faut penser aux performances, mais dans ce cas là, autant réécrire xml2rebxml.r pour produire directement tous les faits atomiques dont tu aurais besoin pour les règles xpath_rules.

J'espère que je suis clair
neoreb8-Mar-2007/20:36:27+1:00
Pour revenir à ta suggestion (point 2), c'est vrai que le fait xml [doc] est un arbre et donc on devrait pouvoir tout déduire de cet unique fait, d'autant plus que prolog.r est capable d'unifier des variables imbriquées dans des blocs...
neoreb8-Mar-2007/20:52:39+1:00
Pour revenir encore à ta suggestion (point 2), utiliser uniquement le fait xml [doc] permettrait d'éviter la phase de génération des faits atomiques mais par contre on risquerait de moins bonnes performances lors de l'évaluation des requêtes XPath (à voir)
neoreb8-Mar-2007/21:12:43+1:00
bon je viens de comprendre l'utilisation que tu fais de parse pour éliminer les espaces.
Je sens que je vais souffir pour comprendre ta fonction parse-doc
neoreb8-Mar-2007/23:00:48+1:00
Marco, je viens de me rendre compte qu'en fait j'avais vraiment besoin des prédicats xpa pour ditinguer les feuilles des sous-arbres. Je me suis mélangé les pinceaux et je t'ai repondu trop vite la derniere fois.

xpath v0.2 (avec prédicat xpa)
//showtime

[showtime ["14:15"]]
[showtime ["16:30"]]
[showtime ["18:45"]]
[showtime ["21:00"]]
[showtime ["19:30"]]

xpath v0.3 (sans prédicat xpa)
//showtime

[showtime ["14:15"] showtime ["16:30"] showtime ["18:45"] showtime ["21:00"] price [adult ["$8.50"] child ["$5.00"]]]
[showtime ["16:30"] showtime ["18:45"] showtime ["21:00"] price [adult ["$8.50"] child ["$5.00"]]]
[showtime ["18:45"] showtime ["21:00"] price [adult ["$8.50"] child ["$5.00"]]]
[showtime ["21:00"] price [adult ["$8.50"] child ["$5.00"]]]
[showtime ["19:30"] price ["$6.00"]]
neoreb8-Mar-2007/23:10:54+1:00
c'est cette règle qui me permet de récupérer l'element et son son sous-arbre uniquement
	xpath [P Y][
		xpa [P X]
		equal? [false (none? X/2)]
		equal? [Y (reduce [X/1 X/2])]]


Ceci dit on doit pouvoir écrire ça plus de façon plus élégante.
coccinelle9-Mar-2007/18:14:30+1:00
J'avais vu le problème, mais je n'avais pas trouvé son origine, bien vu.

J'aurais tendance à mettre cette logique qui n'est que de la présentation dans la fonction prompt :
prompt: has [expression r s e][
	expression: copy ""
	expression: ask "XPath==> "
	either not empty? expression [
		r: 0
		for-which db [X][
			xpath [expression X]
		][
			parse X [
				s: [ ; soit un tag
					word!
					any [word! string!]
					block!
				| ; soit n'importe quoi d'autre (en principe un string!)
					skip
				] e: (probe copy/part s e)
			]
			r: r + 1
		]
		print [r "solution(s)"]
		false
	][true]
]
mais on pourrait modifier la fonction parse-doc.

Marco.
coccinelle9-Mar-2007/18:35:18+1:00
Modifier parse-doc est un peu plus subtile, mais c'est surement la bonne approche :
parse-doc: func [
	data [block!]
	/local parents elements rule element attribute search wrk-facts
][
	parents: copy []
	elements: copy/deep [[]]
	parse data rule: [any [
; D'abord on repère le début et la fin de l'élément
		s: [ ; soit un tag
			word! ; le tag
			any [word! string!] ; les éventuels attributs
			block! ; les enfants
		| ; soit n'importe quoi d'autre (en principe un string!)
			skip
		] e: (element: copy/part s e)
; On se repositionne au début de l'élément
		:s
; Puis on traite l'élément
		skip ( ; process the element
			unless search: find elements/1 element/1 [
				search: head insert elements/1 reduce [element/1 0]
			]
			append db_facts compose/deep/only [index [(element) (search/2: search/2 + 1)]]
			append db_facts compose/deep/only [child [(element) (parents/1)]]
			wrk-facts: tail db_facts
			foreach parent parents [
				insert wrk-facts compose/deep/only [descendant [(element) (parent)]]
			]
		)
		any [s: word! string! e: ( ; process the attributes
			insert db_facts compose/deep/only [attribute [(element) (copy/part s e)]]
		)]
		( ; Push the current element
			insert/only parents element ; stack the tag
			insert/only elements copy [] ; stack a new element list
		)
		opt into rule ; process the childs
		( ; Pop the current element
			remove parents ; unstack the element
			remove elements ; unstack the element list
		)
	]]
]
A toi de choisir ce que tu préfères.

J'espère surtout que cela te permet de mieux comprendre la fonction parse.
neoreb9-Mar-2007/18:55:02+1:00
Vu la solution avec prompt.
J'aime bien la solution ci-dessous également. Regarde la troisieme règle. J'ai supprimé les xpa.
C'est vraiment génial de pouvoir mélanger du code avec des règles Prolog!


xpath_rules: [
	xpath ["/" X][
		xp [["root" "" "" "" ""] X]]	
	xp [["root" "" "" "" ""] doc][
		xml [X]]
	; for not having path starting with / (would be more difficult to handle in the parsepath function) 
	; and for getting the element and its subtree only (without the following siblings if any)
	xpath [P Y][
		xp [(parsepath join "root" P) X]
		equal? [Y (either X/2 = none [X/1] reduce [reduce [X/1 X/2]]) ]]		
	; child axis		
	xp [[Pathup "/" Test "" ""] X][
		xp [(parsepath Pathup) Y]	
		child [X Y]
		equal? [(to-string X/1) Test]]
	; descendant axis
	xp [[Pathup "//" Test "" ""] X][
		xp [(parsepath Pathup) Y]		
		descendant [X Y]
		equal? [(to-string X/1) Test]]
	; nodetest tied with a position
	xp [[P1 P2 P3 _ Pos] X][
		not-equal? ["" Pos]	
		xp [(parsepath join P1 [P2 P3]) X]		
		index [X Pos]]
	; nodetest tied with a predicate (i.e. a path relative to the context node . (dot))	
	xp [[P1 P2 P3 P4 _] X][
		not-equal? ["" P4]		
		xp [(parsepath join P1 [P2 P3]) X]
		xp [(parsepath join P1 [P2 P3 next P4]) Y]
		descendant[Y X]]
]


je vais regarder ta solution avec la fonction parse-doc.
neoreb9-Mar-2007/19:05:55+1:00
Ca va me prendre la soirée ...
neoreb9-Mar-2007/23:53:45+1:00
J'ai réécrit ma propre fonction parsedoc en m'inspirant de la tienne. Elle est toujours recusive mais je la trouve lisible.
Je ne genere pas les faits descendant. Je prefere le faire avec prolog. Par contre elle gere parfaitement les attributs en les considerant comme noeud non types.
Reste à gerer l'affichage. Je ferai ça ce weekend.

parse-doc: func [
parse the rebxml block and create db atomic facts
parent [block!]
parent block
data [block!]
child block
/local element pcdata attribute value subtree elementlist search pos][
element: pcdata: attribute: value: subtree: none
elementlist: copy []
parse data rule: [ 
	any [
		element: word!  ; element
			(either search: find elementlist element/1 [
				pos: search/2: search/2 + 1][
				pos: 1
				append elementlist reduce [element/1 1]]
			append db_facts compose/deep/only [index [(element) (pos)]]
			append db_facts compose/deep/only [child [(element) (parent)]])
		any [attribute: word! value: string! ; attribute/value
			(probe attribute/1
			probe value/1
			append db_facts compose/deep/only [child [(attribute) (element)]]
			append db_facts compose/deep/only [child [(value) (attribute)]])] 
		subtree: block!  ; subtree
			(parse-doc element subtree/1)
		| pcdata: string! ;pcdata
			(append db_facts compose/deep/only [child [(pcdata) (parent)]])
		| ; anything else (normally / if empty element)
			skip
		] ]
]
neoreb10-Mar-2007/20:27:28+1:00
Bon la nouvelle version de xpath.r gere les attributs et les elements vides. Les attributs sont vus comme etant des noeuds fils. Marco, Je me suis inspiré de ta fonction prompt pour gérer les affichages.
coccinelle11-Mar-2007/0:04:51+1:00
Parse-doc comme tu le fais est nettement plus lisible que la mienne. C'est surement la bonne approche pour le parsing des blocs par contre lorsque tu parse une chaine de caractère constuant une structure imbriquée, la technique des stacks s'impose.

En tout cas, l'utilisation de prolog.r est une bonne idée car il permet de naviger facilement dans les trois sens (child, parent et sibling) alors que la structure de bloc imbriqués ne permet de naviguer que dans deux sens (child et sibling).
neoreb11-Mar-2007/12:20:45+1:00
A ton avis. Est-ce que ca vaut le coup de réécrire parsepath en utilisant la fonction parse uniquement ?

Login required to Post.


Powered by RebelBB and REBOL 2.7.8.4.2