1 ;;;; sxml.xpath.test -*- scheme -*-
3 ;;;; Copyright (C) 2010 Free Software Foundation, Inc.
4 ;;;; Copyright (C) 2001,2002,2003,2004 Oleg Kiselyov <oleg at pobox dot com>
6 ;;;; This library is free software; you can redistribute it and/or
7 ;;;; modify it under the terms of the GNU Lesser General Public
8 ;;;; License as published by the Free Software Foundation; either
9 ;;;; version 3 of the License, or (at your option) any later version.
11 ;;;; This library is distributed in the hope that it will be useful,
12 ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 ;;;; Lesser General Public License for more details.
16 ;;;; You should have received a copy of the GNU Lesser General Public
17 ;;;; License along with this library; if not, write to the Free Software
18 ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 ;; Unit tests for (sxml xpath).
26 (define-module (test-suite sxml-xpath)
27 #:use-module (test-suite lib)
28 #:use-module (sxml xpath))
32 (head (title "Slides"))
34 (p (@ (align "center"))
35 (table (@ (style "font-size: x-large"))
37 (td (@ (align "right")) "Talks ")
38 (td (@ (align "center")) " = ")
39 (td " slides + transition"))
41 (td (@ (align "center")) " = ")
42 (td " data + control"))
44 (td (@ (align "center")) " = ")
47 (li (a (@ (href "slides/slide0001.gif")) "Introduction"))
48 (li (a (@ (href "slides/slide0010.gif")) "Summary")))
52 ;; Example from a posting "Re: DrScheme and XML",
53 ;; Shriram Krishnamurthi, comp.lang.scheme, Nov. 26. 1999.
54 ;; http://www.deja.com/getdoc.xp?AN=553507805
56 '(poem (@ (title "The Lovesong of J. Alfred Prufrock")
59 (line "Let us go then, you and I,")
60 (line "When the evening is spread out against the sky")
61 (line "Like a patient etherized upon a table:"))
63 (line "In the room the women come and go")
64 (line "Talking of Michaelangelo."))))
66 (define (run-test selector node expected)
68 (equal? expected (selector node))))
70 (with-test-prefix "test-all"
73 ;; Location path, full form: child::para
74 ;; Location path, abbreviated form: para
75 ;; selects the para element children of the context node
77 '(elem (@) (para (@) "para") (br (@)) "cdata" (para (@) "second par"))
79 (expected '((para (@) "para") (para (@) "second par")))
81 (run-test (select-kids (node-typeof? 'para)) tree expected)
82 (run-test (sxpath '(para)) tree expected))
84 ;; Location path, full form: child::*
85 ;; Location path, abbreviated form: *
86 ;; selects all element children of the context node
88 '(elem (@) (para (@) "para") (br (@)) "cdata" (para "second par"))
91 '((para (@) "para") (br (@)) (para "second par")))
93 (run-test (select-kids (node-typeof? '*)) tree expected)
94 (run-test (sxpath '(*)) tree expected))
96 ;; Location path, full form: child::text()
97 ;; Location path, abbreviated form: text()
98 ;; selects all text node children of the context node
100 '(elem (@) (para (@) "para") (br (@)) "cdata" (para "second par"))
105 (run-test (select-kids (node-typeof? '*text*)) tree expected)
106 (run-test (sxpath '(*text*)) tree expected))
108 ;; Location path, full form: child::node()
109 ;; Location path, abbreviated form: node()
110 ;; selects all the children of the context node, whatever their node type
112 '(elem (@) (para (@) "para") (br (@)) "cdata" (para "second par"))
114 (expected (cdr tree))
116 (run-test (select-kids (node-typeof? '*any*)) tree expected)
117 (run-test (sxpath '(*any*)) tree expected)
120 ;; Location path, full form: child::*/child::para
121 ;; Location path, abbreviated form: */para
122 ;; selects all para grandchildren of the context node
125 '(elem (@) (para (@) "para") (br (@)) "cdata" (para "second par")
126 (div (@ (name "aa")) (para "third para")))
129 '((para "third para")))
132 (node-join (select-kids (node-typeof? '*))
133 (select-kids (node-typeof? 'para)))
135 (run-test (sxpath '(* para)) tree expected)
139 ;; Location path, full form: attribute::name
140 ;; Location path, abbreviated form: @name
141 ;; selects the 'name' attribute of the context node
144 '(elem (@ (name "elem") (id "idz"))
145 (para (@) "para") (br (@)) "cdata" (para (@) "second par")
146 (div (@ (name "aa")) (para (@) "third para")))
152 (node-join (select-kids (node-typeof? '@))
153 (select-kids (node-typeof? 'name)))
155 (run-test (sxpath '(@ name)) tree expected)
158 ;; Location path, full form: attribute::*
159 ;; Location path, abbreviated form: @*
160 ;; selects all the attributes of the context node
162 '(elem (@ (name "elem") (id "idz"))
163 (para (@) "para") (br (@)) "cdata" (para "second par")
164 (div (@ (name "aa")) (para (@) "third para")))
167 '((name "elem") (id "idz")))
170 (node-join (select-kids (node-typeof? '@))
171 (select-kids (node-typeof? '*)))
173 (run-test (sxpath '(@ *)) tree expected)
177 ;; Location path, full form: descendant::para
178 ;; Location path, abbreviated form: .//para
179 ;; selects the para element descendants of the context node
182 '(elem (@ (name "elem") (id "idz"))
183 (para (@) "para") (br (@)) "cdata" (para "second par")
184 (div (@ (name "aa")) (para (@) "third para")))
187 '((para (@) "para") (para "second par") (para (@) "third para")))
190 (node-closure (node-typeof? 'para))
192 (run-test (sxpath '(// para)) tree expected)
195 ;; Location path, full form: self::para
196 ;; Location path, abbreviated form: _none_
197 ;; selects the context node if it is a para element; otherwise selects nothing
200 '(elem (@ (name "elem") (id "idz"))
201 (para (@) "para") (br (@)) "cdata" (para "second par")
202 (div (@ (name "aa")) (para (@) "third para")))
205 (run-test (node-self (node-typeof? 'para)) tree '())
206 (run-test (node-self (node-typeof? 'elem)) tree (list tree))
209 ;; Location path, full form: descendant-or-self::node()
210 ;; Location path, abbreviated form: //
211 ;; selects the context node, all the children (including attribute nodes)
212 ;; of the context node, and all the children of all the (element)
213 ;; descendants of the context node.
214 ;; This is _almost_ a powerset of the context node.
216 '(para (@ (name "elem") (id "idz"))
217 (para (@) "para") (br (@)) "cdata" (para "second par")
218 (div (@ (name "aa")) (para (@) "third para")))
223 '((@) "para" (@) "second par"
224 (@ (name "aa")) (para (@) "third para")
229 (node-self (node-typeof? '*any*))
230 (node-closure (node-typeof? '*any*)))
232 (run-test (sxpath '(//)) tree expected)
235 ;; Location path, full form: ancestor::div
236 ;; Location path, abbreviated form: _none_
237 ;; selects all div ancestors of the context node
238 ;; This Location expression is equivalent to the following:
239 ; /descendant-or-self::div[descendant::node() = curr_node]
240 ;; This shows that the ancestor:: axis is actually redundant. Still,
241 ;; it can be emulated as the following SXPath expression demonstrates.
243 ;; The insight behind "ancestor::div" -- selecting all "div" ancestors
244 ;; of the current node -- is
245 ;; S[ancestor::div] context_node =
246 ;; { y | y=subnode*(root), context_node=subnode(subnode*(y)),
247 ;; isElement(y), name(y) = "div" }
249 ;; { y | y=subnode*(root), pred(y) }
250 ;; can be expressed in SXPath as
251 ;; ((node-or (node-self pred) (node-closure pred)) root-node)
252 ;; The composite predicate 'isElement(y) & name(y) = "div"' corresponds to
253 ;; (node-self (node-typeof? 'div)) in SXPath. Finally, filter
254 ;; context_node=subnode(subnode*(y)) is tantamount to
255 ;; (node-closure (node-eq? context-node)), whereas node-reduce denotes the
256 ;; the composition of converters-predicates in the filtering context.
260 '(div (@ (name "elem") (id "idz"))
261 (para (@) "para") (br (@)) "cdata" (para (@) "second par")
262 (div (@ (name "aa")) (para (@) "third para"))))
263 (context-node ; /descendant::any()[child::text() == "third para"]
267 (node-equal? "third para")))
270 (node-reduce (node-self (node-typeof? 'div))
271 (node-closure (node-eq? context-node))
280 '((div (@ (name "aa")) (para (@) "third para")))))
285 ;; Location path, full form: child::div/descendant::para
286 ;; Location path, abbreviated form: div//para
287 ;; selects the para element descendants of the div element
288 ;; children of the context node
291 '(elem (@ (name "elem") (id "idz"))
292 (para (@) "para") (br (@)) "cdata" (para "second par")
293 (div (@ (name "aa")) (para (@) "third para")
294 (div (para "fourth para"))))
297 '((para (@) "third para") (para "fourth para")))
301 (select-kids (node-typeof? 'div))
302 (node-closure (node-typeof? 'para)))
304 (run-test (sxpath '(div // para)) tree expected)
308 ;; Location path, full form: /descendant::olist/child::item
309 ;; Location path, abbreviated form: //olist/item
310 ;; selects all the item elements that have an olist parent (which is not root)
311 ;; and that are in the same document as the context node
312 ;; See the following test.
314 ;; Location path, full form: /descendant::td/attribute::align
315 ;; Location path, abbreviated form: //td/@align
316 ;; Selects 'align' attributes of all 'td' elements in tree1
319 '((align "right") (align "center") (align "center") (align "center"))
323 (node-closure (node-typeof? 'td))
324 (select-kids (node-typeof? '@))
325 (select-kids (node-typeof? 'align)))
327 (run-test (sxpath '(// td @ align)) tree expected)
331 ;; Location path, full form: /descendant::td[attribute::align]
332 ;; Location path, abbreviated form: //td[@align]
333 ;; Selects all td elements that have an attribute 'align' in tree1
336 '((td (@ (align "right")) "Talks ") (td (@ (align "center")) " = ")
337 (td (@ (align "center")) " = ") (td (@ (align "center")) " = "))
341 (node-closure (node-typeof? 'td))
344 (select-kids (node-typeof? '@))
345 (select-kids (node-typeof? 'align)))))
347 (run-test (sxpath `(// td ,(node-self (sxpath '(@ align))))) tree expected)
348 (run-test (sxpath '(// (td (@ align)))) tree expected)
349 (run-test (sxpath '(// ((td) (@ align)))) tree expected)
350 ;; note! (sxpath ...) is a converter. Therefore, it can be used
351 ;; as any other converter, for example, in the full-form SXPath.
352 ;; Thus we can mix the full and abbreviated form SXPath's freely.
355 (node-closure (node-typeof? 'td))
357 (sxpath '(@ align))))
362 ;; Location path, full form: /descendant::td[attribute::align = "right"]
363 ;; Location path, abbreviated form: //td[@align = "right"]
364 ;; Selects all td elements that have an attribute align = "right" in tree1
367 '((td (@ (align "right")) "Talks "))
371 (node-closure (node-typeof? 'td))
374 (select-kids (node-typeof? '@))
375 (select-kids (node-equal? '(align "right"))))))
377 (run-test (sxpath '(// (td (@ (equal? (align "right")))))) tree expected)
380 ;; Location path, full form: child::para[position()=1]
381 ;; Location path, abbreviated form: para[1]
382 ;; selects the first para child of the context node
384 '(elem (@ (name "elem") (id "idz"))
385 (para (@) "para") (br (@)) "cdata" (para "second par")
386 (div (@ (name "aa")) (para (@) "third para")))
393 (select-kids (node-typeof? 'para))
396 (run-test (sxpath '((para 1))) tree expected)
399 ;; Location path, full form: child::para[position()=last()]
400 ;; Location path, abbreviated form: para[last()]
401 ;; selects the last para child of the context node
403 '(elem (@ (name "elem") (id "idz"))
404 (para (@) "para") (br (@)) "cdata" (para "second par")
405 (div (@ (name "aa")) (para (@) "third para")))
408 '((para "second par"))
412 (select-kids (node-typeof? 'para))
415 (run-test (sxpath '((para -1))) tree expected)
418 ;; Illustrating the following Note of Sec 2.5 of XPath:
419 ;; "NOTE: The location path //para[1] does not mean the same as the
420 ;; location path /descendant::para[1]. The latter selects the first
421 ;; descendant para element; the former selects all descendant para
422 ;; elements that are the first para children of their parents."
425 '(elem (@ (name "elem") (id "idz"))
426 (para (@) "para") (br (@)) "cdata" (para "second par")
427 (div (@ (name "aa")) (para (@) "third para")))
431 (node-reduce ; /descendant::para[1] in SXPath
432 (node-closure (node-typeof? 'para))
434 tree '((para (@) "para")))
435 (run-test (sxpath '(// (para 1))) tree
436 '((para (@) "para") (para (@) "third para")))
439 ;; Location path, full form: parent::node()
440 ;; Location path, abbreviated form: ..
441 ;; selects the parent of the context node. The context node may be
442 ;; an attribute node!
443 ;; For the last test:
444 ;; Location path, full form: parent::*/attribute::name
445 ;; Location path, abbreviated form: ../@name
446 ;; Selects the name attribute of the parent of the context node
449 '(elem (@ (name "elem") (id "idz"))
450 (para (@) "para") (br (@)) "cdata" (para "second par")
451 (div (@ (name "aa")) (para (@) "third para")))
453 (para1 ; the first para node
454 (car ((sxpath '(para)) tree)))
455 (para3 ; the third para node
456 (car ((sxpath '(div para)) tree)))
458 (car ((sxpath '(// div)) tree)))
466 (run-test ; checking the parent of an attribute node
468 ((sxpath '(@ name)) div) (list div))
472 (select-kids (node-typeof? '@))
473 (select-kids (node-typeof? 'name)))
474 para3 '((name "aa")))
476 (sxpath `(,(node-parent tree) @ name))
477 para3 '((name "aa")))
480 ;; Location path, full form: following-sibling::chapter[position()=1]
481 ;; Location path, abbreviated form: none
482 ;; selects the next chapter sibling of the context node
483 ;; The path is equivalent to
484 ;; let cnode = context-node
486 ;; parent::* / child::chapter [take-after node_eq(self::*,cnode)]
491 (chapter (@ (id "one")) "Chap 1 text")
492 (chapter (@ (id "two")) "Chap 2 text")
493 (chapter (@ (id "three")) "Chap 3 text")
494 (chapter (@ (id "four")) "Chap 4 text")
495 (epilogue "Epilogue text")
496 (appendix (@ (id "A")) "App A text")
497 (References "References"))
499 (a-node ; to be used as a context node
500 (car ((sxpath '(// (chapter (@ (equal? (id "two")))))) tree)))
502 '((chapter (@ (id "three")) "Chap 3 text")))
508 (select-kids (node-typeof? 'chapter)))
509 (take-after (node-eq? a-node))
515 ;; preceding-sibling::chapter[position()=1]
516 ;; selects the previous chapter sibling of the context node
517 ;; The path is equivalent to
518 ;; let cnode = context-node
520 ;; parent::* / child::chapter [take-until node_eq(self::*,cnode)]
525 (chapter (@ (id "one")) "Chap 1 text")
526 (chapter (@ (id "two")) "Chap 2 text")
527 (chapter (@ (id "three")) "Chap 3 text")
528 (chapter (@ (id "four")) "Chap 4 text")
529 (epilogue "Epilogue text")
530 (appendix (@ (id "A")) "App A text")
531 (References "References"))
533 (a-node ; to be used as a context node
534 (car ((sxpath '(// (chapter (@ (equal? (id "three")))))) tree)))
536 '((chapter (@ (id "two")) "Chap 2 text")))
542 (select-kids (node-typeof? 'chapter)))
543 (take-until (node-eq? a-node))
550 ;; /descendant::figure[position()=42]
551 ;; selects the forty-second figure element in the document
552 ;; See the next example, which is more general.
554 ;; Location path, full form:
555 ;; child::table/child::tr[position()=2]/child::td[position()=3]
556 ;; Location path, abbreviated form: table/tr[2]/td[3]
557 ;; selects the third td of the second tr of the table
558 (let ((tree ((node-closure (node-typeof? 'p)) tree1))
560 '((td " data + control"))
564 (select-kids (node-typeof? 'table))
565 (node-reduce (select-kids (node-typeof? 'tr))
567 (node-reduce (select-kids (node-typeof? 'td))
570 (run-test (sxpath '(table (tr 2) (td 3))) tree expected)
574 ;; Location path, full form:
575 ;; child::para[attribute::type='warning'][position()=5]
576 ;; Location path, abbreviated form: para[@type='warning'][5]
577 ;; selects the fifth para child of the context node that has a type
578 ;; attribute with value warning
582 (para (@ (type "warning")) "para 2")
583 (para (@ (type "warning")) "para 3")
584 (para (@ (type "warning")) "para 4")
585 (para (@ (type "warning")) "para 5")
586 (para (@ (type "warning")) "para 6"))
589 '((para (@ (type "warning")) "para 6"))
593 (select-kids (node-typeof? 'para))
596 (select-kids (node-typeof? '@))
597 (select-kids (node-equal? '(type "warning")))))
600 (run-test (sxpath '( (((para (@ (equal? (type "warning"))))) 5 ) ))
602 (run-test (sxpath '( (para (@ (equal? (type "warning"))) 5 ) ))
607 ;; Location path, full form:
608 ;; child::para[position()=5][attribute::type='warning']
609 ;; Location path, abbreviated form: para[5][@type='warning']
610 ;; selects the fifth para child of the context node if that child has a 'type'
611 ;; attribute with value warning
615 (para (@ (type "warning")) "para 2")
616 (para (@ (type "warning")) "para 3")
617 (para (@ (type "warning")) "para 4")
618 (para (@ (type "warning")) "para 5")
619 (para (@ (type "warning")) "para 6"))
622 '((para (@ (type "warning")) "para 5"))
626 (select-kids (node-typeof? 'para))
630 (select-kids (node-typeof? '@))
631 (select-kids (node-equal? '(type "warning"))))))
633 (run-test (sxpath '( (( (para 5)) (@ (equal? (type "warning"))))))
635 (run-test (sxpath '( (para 5 (@ (equal? (type "warning")))) ))
639 ;; Location path, full form:
640 ;; child::*[self::chapter or self::appendix]
641 ;; Location path, semi-abbreviated form: *[self::chapter or self::appendix]
642 ;; selects the chapter and appendix children of the context node
646 (chapter (@ (id "one")) "Chap 1 text")
647 (chapter (@ (id "two")) "Chap 2 text")
648 (chapter (@ (id "three")) "Chap 3 text")
649 (epilogue "Epilogue text")
650 (appendix (@ (id "A")) "App A text")
651 (References "References"))
654 '((chapter (@ (id "one")) "Chap 1 text")
655 (chapter (@ (id "two")) "Chap 2 text")
656 (chapter (@ (id "three")) "Chap 3 text")
657 (appendix (@ (id "A")) "App A text"))
661 (select-kids (node-typeof? '*))
664 (node-self (node-typeof? 'chapter))
665 (node-self (node-typeof? 'appendix)))))
667 (run-test (sxpath `(* ,(node-or (node-self (node-typeof? 'chapter))
668 (node-self (node-typeof? 'appendix)))))
673 ;; Location path, full form: child::chapter[child::title='Introduction']
674 ;; Location path, abbreviated form: chapter[title = 'Introduction']
675 ;; selects the chapter children of the context node that have one or more
676 ;; title children with string-value equal to Introduction
677 ;; See a similar example: //td[@align = "right"] above.
679 ;; Location path, full form: child::chapter[child::title]
680 ;; Location path, abbreviated form: chapter[title]
681 ;; selects the chapter children of the context node that have one or
682 ;; more title children
683 ;; See a similar example //td[@align] above.
687 '("Let us go then, you and I," "In the room the women come and go")
691 (node-closure (node-typeof? 'stanza))
693 (select-kids (node-typeof? 'line)) (node-pos 1))
694 (select-kids (node-typeof? '*text*)))
696 (run-test (sxpath '(// stanza (line 1) *text*)) tree expected)