1;;;; sxml.xpath.test -*- scheme -*- 2;;;; 3;;;; Copyright (C) 2010 Free Software Foundation, Inc. 4;;;; Copyright (C) 2001,2002,2003,2004 Oleg Kiselyov <oleg at pobox dot com> 5;;;; 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. 10;;;; 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. 15;;;; 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 19 20;;; Commentary: 21;; 22;; Unit tests for (sxml xpath). 23;; 24;;; Code: 25 26(define-module (test-suite sxml-xpath) 27 #:use-module (test-suite lib) 28 #:use-module (sxml xpath)) 29 30(define tree1 31 '(html 32 (head (title "Slides")) 33 (body 34 (p (@ (align "center")) 35 (table (@ (style "font-size: x-large")) 36 (tr 37 (td (@ (align "right")) "Talks ") 38 (td (@ (align "center")) " = ") 39 (td " slides + transition")) 40 (tr (td) 41 (td (@ (align "center")) " = ") 42 (td " data + control")) 43 (tr (td) 44 (td (@ (align "center")) " = ") 45 (td " programs")))) 46 (ul 47 (li (a (@ (href "slides/slide0001.gif")) "Introduction")) 48 (li (a (@ (href "slides/slide0010.gif")) "Summary"))) 49 ))) 50 51 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 55(define tree3 56 '(poem (@ (title "The Lovesong of J. Alfred Prufrock") 57 (poet "T. S. Eliot")) 58 (stanza 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:")) 62 (stanza 63 (line "In the room the women come and go") 64 (line "Talking of Michaelangelo.")))) 65 66(define (run-test selector node expected) 67 (pass-if expected 68 (equal? expected (selector node)))) 69 70(with-test-prefix "test-all" 71 72 73 ;; Location path, full form: child::para 74 ;; Location path, abbreviated form: para 75 ;; selects the para element children of the context node 76 (let ((tree 77 '(elem (@) (para (@) "para") (br (@)) "cdata" (para (@) "second par")) 78 ) 79 (expected '((para (@) "para") (para (@) "second par"))) 80 ) 81 (run-test (select-kids (node-typeof? 'para)) tree expected) 82 (run-test (sxpath '(para)) tree expected)) 83 84 ;; Location path, full form: child::* 85 ;; Location path, abbreviated form: * 86 ;; selects all element children of the context node 87 (let ((tree 88 '(elem (@) (para (@) "para") (br (@)) "cdata" (para "second par")) 89 ) 90 (expected 91 '((para (@) "para") (br (@)) (para "second par"))) 92 ) 93 (run-test (select-kids (node-typeof? '*)) tree expected) 94 (run-test (sxpath '(*)) tree expected)) 95 96 ;; Location path, full form: child::text() 97 ;; Location path, abbreviated form: text() 98 ;; selects all text node children of the context node 99 (let ((tree 100 '(elem (@) (para (@) "para") (br (@)) "cdata" (para "second par")) 101 ) 102 (expected 103 '("cdata")) 104 ) 105 (run-test (select-kids (node-typeof? '*text*)) tree expected) 106 (run-test (sxpath '(*text*)) tree expected)) 107 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 111 (let* ((tree 112 '(elem (@) (para (@) "para") (br (@)) "cdata" (para "second par")) 113 ) 114 (expected (cdr tree)) 115 ) 116 (run-test (select-kids (node-typeof? '*any*)) tree expected) 117 (run-test (sxpath '(*any*)) tree expected) 118 ) 119 120 ;; Location path, full form: child::*/child::para 121 ;; Location path, abbreviated form: */para 122 ;; selects all para grandchildren of the context node 123 124 (let ((tree 125 '(elem (@) (para (@) "para") (br (@)) "cdata" (para "second par") 126 (div (@ (name "aa")) (para "third para"))) 127 ) 128 (expected 129 '((para "third para"))) 130 ) 131 (run-test 132 (node-join (select-kids (node-typeof? '*)) 133 (select-kids (node-typeof? 'para))) 134 tree expected) 135 (run-test (sxpath '(* para)) tree expected) 136 ) 137 138 139 ;; Location path, full form: attribute::name 140 ;; Location path, abbreviated form: @name 141 ;; selects the 'name' attribute of the context node 142 143 (let ((tree 144 '(elem (@ (name "elem") (id "idz")) 145 (para (@) "para") (br (@)) "cdata" (para (@) "second par") 146 (div (@ (name "aa")) (para (@) "third para"))) 147 ) 148 (expected 149 '((name "elem"))) 150 ) 151 (run-test 152 (node-join (select-kids (node-typeof? '@)) 153 (select-kids (node-typeof? 'name))) 154 tree expected) 155 (run-test (sxpath '(@ name)) tree expected) 156 ) 157 158 ;; Location path, full form: attribute::* 159 ;; Location path, abbreviated form: @* 160 ;; selects all the attributes of the context node 161 (let ((tree 162 '(elem (@ (name "elem") (id "idz")) 163 (para (@) "para") (br (@)) "cdata" (para "second par") 164 (div (@ (name "aa")) (para (@) "third para"))) 165 ) 166 (expected 167 '((name "elem") (id "idz"))) 168 ) 169 (run-test 170 (node-join (select-kids (node-typeof? '@)) 171 (select-kids (node-typeof? '*))) 172 tree expected) 173 (run-test (sxpath '(@ *)) tree expected) 174 ) 175 176 177 ;; Location path, full form: descendant::para 178 ;; Location path, abbreviated form: .//para 179 ;; selects the para element descendants of the context node 180 181 (let ((tree 182 '(elem (@ (name "elem") (id "idz")) 183 (para (@) "para") (br (@)) "cdata" (para "second par") 184 (div (@ (name "aa")) (para (@) "third para"))) 185 ) 186 (expected 187 '((para (@) "para") (para "second par") (para (@) "third para"))) 188 ) 189 (run-test 190 (node-closure (node-typeof? 'para)) 191 tree expected) 192 (run-test (sxpath '(// para)) tree expected) 193 ) 194 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 198 199 (let ((tree 200 '(elem (@ (name "elem") (id "idz")) 201 (para (@) "para") (br (@)) "cdata" (para "second par") 202 (div (@ (name "aa")) (para (@) "third para"))) 203 ) 204 ) 205 (run-test (node-self (node-typeof? 'para)) tree '()) 206 (run-test (node-self (node-typeof? 'elem)) tree (list tree)) 207 ) 208 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. 215 (let* ((tree 216 '(para (@ (name "elem") (id "idz")) 217 (para (@) "para") (br (@)) "cdata" (para "second par") 218 (div (@ (name "aa")) (para (@) "third para"))) 219 ) 220 (expected 221 (cons tree 222 (append (cdr tree) 223 '((@) "para" (@) "second par" 224 (@ (name "aa")) (para (@) "third para") 225 (@) "third para")))) 226 ) 227 (run-test 228 (node-or 229 (node-self (node-typeof? '*any*)) 230 (node-closure (node-typeof? '*any*))) 231 tree expected) 232 (run-test (sxpath '(//)) tree expected) 233 ) 234 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. 242 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" } 248 ;; We observe that 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. 257 258 (let* 259 ((root 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"] 264 (car 265 ((node-closure 266 (select-kids 267 (node-equal? "third para"))) 268 root))) 269 (pred 270 (node-reduce (node-self (node-typeof? 'div)) 271 (node-closure (node-eq? context-node)) 272 )) 273 ) 274 (run-test 275 (node-or 276 (node-self pred) 277 (node-closure pred)) 278 root 279 (cons root 280 '((div (@ (name "aa")) (para (@) "third para"))))) 281 ) 282 283 284 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 289 290 (let ((tree 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")))) 295 ) 296 (expected 297 '((para (@) "third para") (para "fourth para"))) 298 ) 299 (run-test 300 (node-join 301 (select-kids (node-typeof? 'div)) 302 (node-closure (node-typeof? 'para))) 303 tree expected) 304 (run-test (sxpath '(div // para)) tree expected) 305 ) 306 307 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. 313 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 317 (let ((tree tree1) 318 (expected 319 '((align "right") (align "center") (align "center") (align "center")) 320 )) 321 (run-test 322 (node-join 323 (node-closure (node-typeof? 'td)) 324 (select-kids (node-typeof? '@)) 325 (select-kids (node-typeof? 'align))) 326 tree expected) 327 (run-test (sxpath '(// td @ align)) tree expected) 328 ) 329 330 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 334 (let ((tree tree1) 335 (expected 336 '((td (@ (align "right")) "Talks ") (td (@ (align "center")) " = ") 337 (td (@ (align "center")) " = ") (td (@ (align "center")) " = ")) 338 )) 339 (run-test 340 (node-reduce 341 (node-closure (node-typeof? 'td)) 342 (filter 343 (node-join 344 (select-kids (node-typeof? '@)) 345 (select-kids (node-typeof? 'align))))) 346 tree expected) 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. 353 (run-test 354 (node-reduce 355 (node-closure (node-typeof? 'td)) 356 (filter 357 (sxpath '(@ align)))) 358 tree expected) 359 ) 360 361 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 365 (let ((tree tree1) 366 (expected 367 '((td (@ (align "right")) "Talks ")) 368 )) 369 (run-test 370 (node-reduce 371 (node-closure (node-typeof? 'td)) 372 (filter 373 (node-join 374 (select-kids (node-typeof? '@)) 375 (select-kids (node-equal? '(align "right")))))) 376 tree expected) 377 (run-test (sxpath '(// (td (@ (equal? (align "right")))))) tree expected) 378 ) 379 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 383 (let ((tree 384 '(elem (@ (name "elem") (id "idz")) 385 (para (@) "para") (br (@)) "cdata" (para "second par") 386 (div (@ (name "aa")) (para (@) "third para"))) 387 ) 388 (expected 389 '((para (@) "para")) 390 )) 391 (run-test 392 (node-reduce 393 (select-kids (node-typeof? 'para)) 394 (node-pos 1)) 395 tree expected) 396 (run-test (sxpath '((para 1))) tree expected) 397 ) 398 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 402 (let ((tree 403 '(elem (@ (name "elem") (id "idz")) 404 (para (@) "para") (br (@)) "cdata" (para "second par") 405 (div (@ (name "aa")) (para (@) "third para"))) 406 ) 407 (expected 408 '((para "second par")) 409 )) 410 (run-test 411 (node-reduce 412 (select-kids (node-typeof? 'para)) 413 (node-pos -1)) 414 tree expected) 415 (run-test (sxpath '((para -1))) tree expected) 416 ) 417 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." 423 424 (let ((tree 425 '(elem (@ (name "elem") (id "idz")) 426 (para (@) "para") (br (@)) "cdata" (para "second par") 427 (div (@ (name "aa")) (para (@) "third para"))) 428 ) 429 ) 430 (run-test 431 (node-reduce ; /descendant::para[1] in SXPath 432 (node-closure (node-typeof? 'para)) 433 (node-pos 1)) 434 tree '((para (@) "para"))) 435 (run-test (sxpath '(// (para 1))) tree 436 '((para (@) "para") (para (@) "third para"))) 437 ) 438 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 447 448 (let* ((tree 449 '(elem (@ (name "elem") (id "idz")) 450 (para (@) "para") (br (@)) "cdata" (para "second par") 451 (div (@ (name "aa")) (para (@) "third para"))) 452 ) 453 (para1 ; the first para node 454 (car ((sxpath '(para)) tree))) 455 (para3 ; the third para node 456 (car ((sxpath '(div para)) tree))) 457 (div ; div node 458 (car ((sxpath '(// div)) tree))) 459 ) 460 (run-test 461 (node-parent tree) 462 para1 (list tree)) 463 (run-test 464 (node-parent tree) 465 para3 (list div)) 466 (run-test ; checking the parent of an attribute node 467 (node-parent tree) 468 ((sxpath '(@ name)) div) (list div)) 469 (run-test 470 (node-join 471 (node-parent tree) 472 (select-kids (node-typeof? '@)) 473 (select-kids (node-typeof? 'name))) 474 para3 '((name "aa"))) 475 (run-test 476 (sxpath `(,(node-parent tree) @ name)) 477 para3 '((name "aa"))) 478 ) 479 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 485 ;; in 486 ;; parent::* / child::chapter [take-after node_eq(self::*,cnode)] 487 ;; [position()=1] 488 (let* ((tree 489 '(document 490 (preface "preface") 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")) 498 ) 499 (a-node ; to be used as a context node 500 (car ((sxpath '(// (chapter (@ (equal? (id "two")))))) tree))) 501 (expected 502 '((chapter (@ (id "three")) "Chap 3 text"))) 503 ) 504 (run-test 505 (node-reduce 506 (node-join 507 (node-parent tree) 508 (select-kids (node-typeof? 'chapter))) 509 (take-after (node-eq? a-node)) 510 (node-pos 1) 511 ) 512 a-node expected) 513 ) 514 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 519 ;; in 520 ;; parent::* / child::chapter [take-until node_eq(self::*,cnode)] 521 ;; [position()=-1] 522 (let* ((tree 523 '(document 524 (preface "preface") 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")) 532 ) 533 (a-node ; to be used as a context node 534 (car ((sxpath '(// (chapter (@ (equal? (id "three")))))) tree))) 535 (expected 536 '((chapter (@ (id "two")) "Chap 2 text"))) 537 ) 538 (run-test 539 (node-reduce 540 (node-join 541 (node-parent tree) 542 (select-kids (node-typeof? 'chapter))) 543 (take-until (node-eq? a-node)) 544 (node-pos -1) 545 ) 546 a-node expected) 547 ) 548 549 550 ;; /descendant::figure[position()=42] 551 ;; selects the forty-second figure element in the document 552 ;; See the next example, which is more general. 553 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)) 559 (expected 560 '((td " data + control")) 561 )) 562 (run-test 563 (node-join 564 (select-kids (node-typeof? 'table)) 565 (node-reduce (select-kids (node-typeof? 'tr)) 566 (node-pos 2)) 567 (node-reduce (select-kids (node-typeof? 'td)) 568 (node-pos 3))) 569 tree expected) 570 (run-test (sxpath '(table (tr 2) (td 3))) tree expected) 571 ) 572 573 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 579 (let ((tree 580 '(chapter 581 (para "para1") 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")) 587 ) 588 (expected 589 '((para (@ (type "warning")) "para 6")) 590 )) 591 (run-test 592 (node-reduce 593 (select-kids (node-typeof? 'para)) 594 (filter 595 (node-join 596 (select-kids (node-typeof? '@)) 597 (select-kids (node-equal? '(type "warning"))))) 598 (node-pos 5)) 599 tree expected) 600 (run-test (sxpath '( (((para (@ (equal? (type "warning"))))) 5 ) )) 601 tree expected) 602 (run-test (sxpath '( (para (@ (equal? (type "warning"))) 5 ) )) 603 tree expected) 604 ) 605 606 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 612 (let ((tree 613 '(chapter 614 (para "para1") 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")) 620 ) 621 (expected 622 '((para (@ (type "warning")) "para 5")) 623 )) 624 (run-test 625 (node-reduce 626 (select-kids (node-typeof? 'para)) 627 (node-pos 5) 628 (filter 629 (node-join 630 (select-kids (node-typeof? '@)) 631 (select-kids (node-equal? '(type "warning")))))) 632 tree expected) 633 (run-test (sxpath '( (( (para 5)) (@ (equal? (type "warning")))))) 634 tree expected) 635 (run-test (sxpath '( (para 5 (@ (equal? (type "warning")))) )) 636 tree expected) 637 ) 638 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 643 (let ((tree 644 '(document 645 (preface "preface") 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")) 652 ) 653 (expected 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")) 658 )) 659 (run-test 660 (node-join 661 (select-kids (node-typeof? '*)) 662 (filter 663 (node-or 664 (node-self (node-typeof? 'chapter)) 665 (node-self (node-typeof? 'appendix))))) 666 tree expected) 667 (run-test (sxpath `(* ,(node-or (node-self (node-typeof? 'chapter)) 668 (node-self (node-typeof? 'appendix))))) 669 tree expected) 670 ) 671 672 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. 678 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. 684 685 (let ((tree tree3) 686 (expected 687 '("Let us go then, you and I," "In the room the women come and go") 688 )) 689 (run-test 690 (node-join 691 (node-closure (node-typeof? 'stanza)) 692 (node-reduce 693 (select-kids (node-typeof? 'line)) (node-pos 1)) 694 (select-kids (node-typeof? '*text*))) 695 tree expected) 696 (run-test (sxpath '(// stanza (line 1) *text*)) tree expected) 697 ) 698 ) 699