1require "rexml_test_utils" 2 3require "rexml/document" 4 5class XPathTester < Test::Unit::TestCase 6 include REXMLTestUtils 7 include REXML 8 SOURCE = <<-EOF 9 <a id='1'> 10 <b id='2' x='y'> 11 <c id='3'/> 12 <c id='4'/> 13 </b> 14 <d id='5'> 15 <c id='6' x='y'/> 16 <c id='7'/> 17 <c id='8'/> 18 <q id='19'/> 19 </d> 20 <e id='9'> 21 <f id='10' a='b'/> 22 <f id='11' a='c'/> 23 <f id='12' a='d'> 24 <g id='13'/> 25 </f> 26 <f id='14' a='d'/> 27 </e> 28 <m id='15'> 29 <n id='16'> 30 <o id='17'> 31 <p id='18'/> 32 </o> 33 </n> 34 </m> 35 </a> 36 EOF 37 JENI_TENNISON = <<-EOJT 38 <a> 39 <b> 40 <c> 41 <d> 42 <e id='x'> 43 <f/> 44 </e> 45 </d> 46 </c> 47 <c> 48 <d> 49 <e id='y'/> 50 </d> 51 </c> 52 </b> 53 <b> 54 <c> 55 <d> 56 <e id='z'/> 57 </d> 58 </c> 59 </b> 60 </a> 61 EOJT 62 63 def setup 64 @@doc = Document.new(SOURCE) unless defined? @@doc 65 @@jeni = Document.new( JENI_TENNISON ) unless defined? @@jeni 66 end 67 68 def each_test( element, xpath ) 69 count = 0 70 XPath::each( element, xpath ) { |child| 71 count += 1 72 yield child if block_given? 73 } 74 count 75 end 76 77 def test_descendant 78 doc = Document.new("<a><b><c id='1'/></b><d><b><c id='2'/></b></d></a>") 79 p = XPath.match( doc, "//c" ) 80 assert_equal( 2, p.size ) 81 p = XPath.first( @@doc, "//p" ) 82 assert_equal "p", p.name 83 c = each_test( @@doc, "//c" ) { |child| assert_equal "c", child.name } 84 assert_equal 5, c 85 c = each_test( @@doc.root, "b//c" ) { |child| 86 assert_equal "c", child.name 87 } 88 assert_equal 2, c 89 90 doc = Document.new( "<a><z id='1'/><b><z id='11'/><z id='12'/></b><c><z id='21'/><z id='22'/><d><z id='31'/><z id='32'/></d></c></a>" ) 91 # //para[1] : all descendants which are the first para child of their parent 92 assert_equal( 4, XPath.match( doc, "//z[1]" ).size ) 93 # /descendant::para[1] : the first descendant para element 94 assert_equal( 1, XPath.match( doc, "/descendant::z[1]" ).size ) 95 end 96 97 def test_root 98 source = "<a><b/></a>" 99 doc = Document.new( source ) 100 assert_equal doc, doc.root_node 101 assert_equal "a", XPath::first( doc, "/" ).elements[1].name 102 end 103 104 def test_abbreviated_simple_child 105 assert_equal "a", XPath::first(@@doc, "a").name 106 end 107 108 def test_child 109 c = XPath::first( @@doc, "a/b/c" ) 110 assert_equal "c", c.name 111 assert_equal "3", XPath::first(@@doc, "a/b/c").attributes["id"] 112 end 113 114 def test_root_child 115 assert_equal "a", XPath::first(@@doc, "/a").name 116 c = XPath::first( @@doc, "a/b/c" ) 117 assert_equal "a", XPath::first(c, "/a").name 118 end 119 120 def test_root_children 121 c = XPath::first( @@doc, "a/b/c" ) 122 assert_equal "2", XPath::first(c, "/a/b").attributes["id"] 123 end 124 125 def test_abbreviated_step 126 c = XPath::first( @@doc, "a/b/c" ) 127 assert_equal("c", c.name) 128 assert_equal("a", XPath::first(@@doc.root, ".").name) 129 assert_equal("b", XPath::first(c, "..").name) 130 assert_equal("a", XPath::first(@@doc, "a/b/..").name) 131 132 doc = REXML::Document.new(File.new(fixture_path("project.xml"))) 133 c = each_test(doc.root, "./Description" ) { |child| 134 assert_equal("Description",child.name) 135 } 136 assert_equal 1, c 137 end 138 139 # Things that aren't tested elsewhere 140 def test_predicates 141 assert_equal "12", XPath::first(@@doc, "a/e/f[3]").attributes["id"] 142 assert_equal "13", XPath::first(@@doc, "a/e/f[3]/g").attributes["id"] 143 assert_equal "14", XPath::first(@@doc, "a/e/f[@a='d'][2]").attributes["id"] 144 assert_equal "14", XPath::first(@@doc, "a/e/f[@a='d'][@id='14']").attributes["id"] 145 assert_equal "a", XPath::first( @@doc, "*[name()='a' and @id='1']" ).name 146 c=each_test( @@doc, "//*[name()='f' and @a='d']") { |i| 147 assert_equal "f", i.name 148 } 149 assert_equal 2, c 150 c=each_test( @@doc, "//*[name()='m' or @a='d']") { |i| 151 assert ["m","f"].include?(i.name) 152 } 153 assert_equal 3, c 154 155 assert_equal "b", XPath::first( @@doc, "//b[@x]" ).name 156 end 157 158 def test_node_type 159 doc = Document.new "<a><?foo bar?><!--comment-->text</a>" 160 #res = XPath::first(doc.root, "text()") 161 #assert_equal "text", res.to_s 162 163 #res = XPath::first(doc, "*") 164 #assert_equal "a", res.name 165 166 assert_equal( :processing_instruction, 167 XPath::first(doc.root, "processing-instruction()").node_type) 168 assert_equal( :comment, XPath::first(doc.root, "comment()").node_type) 169 end 170 171 def test_functions 172 # trivial text() test 173 # confuse-a-function 174 source = "<a>more <b id='1'/><b id='2'>dumb</b><b id='3'/><c/> text</a>" 175 doc = Document.new source 176 res = "" 177 #XPath::each(doc.root, "text()") {|val| res << val.to_s} 178 #assert_equal "more text", res 179 180 #res = XPath::first(doc.root, "b[last()]") 181 #assert_equal '3', res.attributes['id'] 182 res = XPath::first(doc.root, "b[position()=2]") 183 assert_equal '2', res.attributes['id'] 184 res = XPath::first(doc.root, "*[name()='c']") 185 assert_equal "c", res.name 186 end 187 188 def no_test_ancestor 189 doc = REXML::Document.new(File.new(fixture_path("testsrc.xml"))) 190 doc.elements.each("//item") { |el| print el.name 191 if el.attributes['x'] 192 puts " -- "+el.attributes['x'] 193 else 194 puts 195 end 196 } 197 doc.elements.each("//item/ancestor::") { |el| print el.name 198 if el.attributes['x'] 199 puts " -- "+el.attributes['x'] 200 else 201 puts 202 end 203 } 204 end 205 206 # Here are some XPath tests that were originally submitted by ... 207 # The code has changed some, but the logic and the source documents are the 208 # same. 209 # This method reads a document from a file, and then a series of xpaths, 210 # also from a file. It then checks each xpath against the source file. 211 def test_more 212 xmlsource = fixture_path("testsrc.xml") 213 xpathtests = fixture_path("xp.tst") 214 215 doc = REXML::Document.new(File.new(xmlsource)) 216 #results = "" 217 results = REXML::Document.new 218 results.add_element "test-results" 219 for line in File.new(xpathtests) 220 line.strip! 221 begin 222 doc.root 223 #puts "#"*80 224 #print "\nDoing #{line} " ; $stdout.flush 225 doc.elements.each(line) do |el| 226 #print "." ; $stdout.flush 227 results.root << el.clone 228 #results << el.to_s 229 end 230 #ObjectSpace.garbage_collect 231 GC::start 232 rescue Exception => z 233 #puts "\n'#{line}' failed" 234 fail("Error on line #{line}:\n#{z.message}\n"+z.backtrace[0,10].join("\n")) 235 #results.root.add_element( "error", {"path"=>line}).text = z.message+"\n"+z.backtrace[0,10].join("\n") 236 #results << "<error path='"+line+"'>"+z.message+"</error>" 237 end 238 end 239 end 240 241 def test_axe_descendant 242 assert_equal "f", XPath::first( @@doc, "descendant::f").name 243 end 244 245 def test_axe_parent 246 q = XPath.first( @@doc, "a/d/c/parent::*/q" ) 247 assert_equal 19, q.attributes["id"].to_i 248 end 249 250 def test_abbreviated_attribute 251 assert_equal 'a', XPath::first( @@doc, "a[@id='1']" ).name 252 c = XPath::first( @@doc, "a/b/c[@id='4']" ) 253 assert_equal 'c', c.name 254 assert_equal '4', c.attributes['id'] 255 256 result = XPath::first( @@doc, "descendant::f[@a='c']") 257 assert_equal "11", result.attributes['id'] 258 259 assert_equal "11", XPath::first(@@doc, "a/e/f[@a='c']").attributes["id"] 260 assert_equal "11", XPath::first(@@doc, "a/e/*[@a='c']").attributes["id"] 261 end 262 263 def test_axe_self 264 c = XPath::first( @@doc, "a/b/c" ) 265 assert c 266 assert_equal "c", c.name 267 assert_equal "c", XPath::first( c, "self::node()" ).name 268 end 269 270 def test_axe_ancestor 271 doc = REXML::Document.new " 272 <a> 273 <b id='1'> 274 <c> 275 <b id='2'> 276 <d/> 277 </b> 278 </c> 279 </b> 280 </a>" 281 282 d = XPath.first( doc, "//d" ) 283 assert_equal "d", d.name 284 b = each_test( d, "ancestor::b" ) { |el| 285 assert((1..2) === el.attributes['id'].to_i, 286 "Expected #{el.attributes['id']} to be either 1 or 2" 287 ) 288 } 289 assert_equal 2, b 290 end 291 292 def test_axe_child 293 m = XPath.first( @@doc, "a/child::m" ) 294 assert_equal 15, m.attributes['id'].to_i 295 end 296 297 def test_axe_attribute 298 a = XPath.first( @@doc, "a/attribute::id" ) 299 assert_equal "1", a.value 300 a = XPath.first( @@doc, "a/e/f[@id='14']/attribute::a" ) 301 assert_equal "d", a.value 302 end 303 304 def test_axe_sibling 305 doc = Document.new "<a><b><c/></b><e><f id='10'/><f id='11'/><f id='12'/></e></a>" 306 first_f = XPath.first( doc, "a/e/f" ) 307 assert first_f 308 assert_equal '10', first_f.attributes['id'] 309 next_f = XPath.first( doc, "a/e/f/following-sibling::node()" ) 310 assert_equal '11', next_f.attributes['id'] 311 312 b = XPath.first( doc, "a/e/preceding-sibling::node()" ) 313 assert_equal 'b', b.name 314 end 315 316 def test_lang 317 doc = Document.new(File.new(fixture_path("lang0.xml"))) 318 #puts IO.read( "test/lang.xml" ) 319 320 #puts XPath.match( doc, "//language/*" ).size 321 c = each_test( doc, "//language/*" ) { |element| 322 #puts "#{element.name}: #{element.text}" 323 } 324 assert_equal 4, c 325 end 326 327 def test_namespaces_1 328 source = <<-EOF 329 <foo xmlns:ts="this" xmlns:tt="that"> 330 <ts:bar>this bar</ts:bar> 331 <tt:bar>that bar</tt:bar> 332 </foo> 333 EOF 334 doc = Document.new source 335 XPath.each( doc, "//bar" ) { 336 fail "'bar' should match nothing in this case" 337 } 338 339 namespace = {"t"=>"this"} 340 results = XPath.first( doc, "//t:bar", namespace ) 341 assert_equal "this bar", results.text 342 end 343 344 def test_namespaces_2 345 source = <<-EOF 346 <foo xmlns:ts="this" xmlns:tt="that"> 347 <ts:bar>this bar</ts:bar> 348 <tt:bar>that bar</tt:bar> 349 </foo> 350 EOF 351 doc = Document.new source 352 res = XPath::first(doc, "//*[local_name()='bar']") 353 assert res, "looking for //*[name()='bar']" 354 assert_equal 'this', res.namespace 355 res = XPath::first(doc.root, "*[namespace_uri()='that']") 356 assert_equal 'that bar', res.text 357 end 358 359 def test_complex 360 next_f = XPath.first( @@doc, "a/e/f[@id='11']/following-sibling::*" ) 361 assert_equal 12, next_f.attributes['id'].to_i 362 prev_f = XPath.first( @@doc, "a/e/f[@id='11']/preceding-sibling::*" ) 363 assert_equal 10, prev_f.attributes['id'].to_i 364 c = each_test( @@doc, "descendant-or-self::*[@x='y']" ) 365 assert_equal 2, c 366 end 367 368 def test_grouping 369 t = XPath.first( @@doc, "a/d/*[name()='d' and (name()='f' or name()='q')]" ) 370 assert_nil t 371 t = XPath.first( @@doc, "a/d/*[(name()='d' and name()='f') or name()='q']" ) 372 assert_equal 'q', t.name 373 end 374 375 def test_preceding 376 d = Document.new "<a><b id='0'/><b id='2'/><b><c id='0'/><c id='1'/><c id='2'/></b><b id='1'/></a>" 377 start = XPath.first( d, "/a/b[@id='1']" ) 378 assert_equal 'b', start.name 379 c = XPath.first( start, "preceding::c" ) 380 assert_equal '2', c.attributes['id'] 381 382 c1, c0 = XPath.match( d, "/a/b/c[@id='2']/preceding::node()" ) 383 assert_equal '1', c1.attributes['id'] 384 assert_equal '0', c0.attributes['id'] 385 386 c2, c1, c0, b, b2, b0 = XPath.match( start, "preceding::node()" ) 387 388 assert_equal 'c', c2.name 389 assert_equal 'c', c1.name 390 assert_equal 'c', c0.name 391 assert_equal 'b', b.name 392 assert_equal 'b', b2.name 393 assert_equal 'b', b0.name 394 395 assert_equal '2', c2.attributes['id'] 396 assert_equal '1', c1.attributes['id'] 397 assert_equal '0', c0.attributes['id'] 398 assert b.attributes.empty? 399 assert_equal '2', b2.attributes['id'] 400 assert_equal '0', b0.attributes['id'] 401 402 d = REXML::Document.new("<a><b/><c/><d/></a>") 403 matches = REXML::XPath.match(d, "/a/d/preceding::node()") 404 assert_equal("c", matches[0].name) 405 assert_equal("b", matches[1].name) 406 407 s = "<a><b><c id='1'/></b><b><b><c id='2'/><c id='3'/></b><c id='4'/></b><c id='NOMATCH'><c id='5'/></c></a>" 408 d = REXML::Document.new(s) 409 c = REXML::XPath.match( d, "//c[@id = '5']") 410 cs = REXML::XPath.match( c, "preceding::c" ) 411 assert_equal( 4, cs.length ) 412 end 413 414 def test_following 415 d = Document.new "<a><b id='0'/><b/><b><c id='1'/><c id='2'/></b><b id='1'/></a>" 416 start = XPath.first( d, "/a/b[@id='0']" ) 417 assert_equal 'b', start.name 418 c = XPath.first( start, "following::c" ) 419 assert_equal '1', c.attributes['id'] 420 421 s = "<a><b><c><d/></c><e/></b><f><g><h/><i/></g></f><i/></a>" 422 d = Document.new(s) 423 c = XPath.first(d, '/a/b/c') 424 assert_equal 'c', c.name 425 res = XPath.match( c, 'following::*' ) 426 assert_equal 6, res.size 427 res = XPath.match( c, 'following::i' ) 428 assert_equal 2, res.size 429 end 430 431 # The following three paths were provided by 432 # Jeni Tennison <jeni@jenitennison.com> 433 # a consultant who is also an XSL and XPath expert 434 #def test_child_cubed 435 # els = @@jeni.elements.to_a("*****") 436 # assert_equal 3, els.size 437 #end 438 439 #def test_div_2 440 # results = doc.elements.to_a("/ div 2") 441 #end 442 443 #def test_nested_predicates 444 # puts @@jeni.root.elements[1].elements[1].name 445 # results = @@jeni.root.elements[1].elements[1].elements.to_a("../following-sibling::*[*[name() = name(current())]]") 446 # puts results 447 #end 448 449 # Contributed by Mike Stok 450 def test_starts_with 451 source = <<-EOF 452 <foo> 453 <a href="mailto:a@b.c">a@b.c</a> 454 <a href="http://www.foo.com">http://www.foo.com</a> 455 </foo> 456 EOF 457 doc = Document.new source 458 mailtos = doc.elements.to_a("//a[starts-with(@href, 'mailto:')]") 459 assert_equal 1, mailtos.size 460 assert_equal "mailto:a@b.c", mailtos[0].attributes['href'] 461 462 ailtos = doc.elements.to_a("//a[starts-with(@href, 'ailto:')]") 463 assert_equal 0, ailtos.size 464 end 465 466 def test_toms_text_node 467 file = "<a>A<b>B</b><c>C<d>D</d>E</c>F</a>" 468 doc = Document.new(file) 469 assert_equal 'A', XPath.first(doc[0], 'text()').to_s 470 assert_equal 'AF', XPath.match(doc[0], 'text()').collect { |n| 471 n.to_s 472 }.join('') 473 assert_equal 'B', XPath.first(doc[0], 'b/text()').to_s 474 assert_equal 'D', XPath.first(doc[0], '//d/text()').to_s 475 assert_equal 'ABCDEF', XPath.match(doc[0], '//text()').collect {|n| 476 n.to_s 477 }.join('') 478 end 479 480 def test_string_length 481 doc = Document.new <<-EOF 482 <AAA> 483 <Q/> 484 <SSSS/> 485 <BB/> 486 <CCC/> 487 <DDDDDDDD/> 488 <EEEE/> 489 </AAA> 490 EOF 491 assert doc, "create doc" 492 493 set = doc.elements.to_a("//*[string-length(name()) = 3]") 494 assert_equal 2, set.size, "nodes with names length = 3" 495 496 set = doc.elements.to_a("//*[string-length(name()) < 3]") 497 assert_equal 2, set.size, "nodes with names length < 3" 498 499 set = doc.elements.to_a("//*[string-length(name()) > 3]") 500 assert_equal 3, set.size, "nodes with names length > 3" 501 end 502 503 # Test provided by Mike Stok 504 def test_contains 505 source = <<-EOF 506 <foo> 507 <a href="mailto:a@b.c">a@b.c</a> 508 <a href="http://www.foo.com">http://www.foo.com</a> 509 </foo> 510 EOF 511 doc = Document.new source 512 513 [ 514 #['o', 2], 515 ['foo', 1], ['bar', 0]].each { |search, expected| 516 set = doc.elements.to_a("//a[contains(@href, '#{search}')]") 517 assert_equal expected, set.size 518 } 519 end 520 521 # Mike Stok and Sean Russell 522 def test_substring 523 # examples from http://www.w3.org/TR/xpath#function-substring 524 doc = Document.new('<test string="12345" />') 525 526 Document.new("<a b='1'/>") 527 #puts XPath.first(d, 'node()[0 + 1]') 528 #d = Document.new("<a b='1'/>") 529 #puts XPath.first(d, 'a[0 mod 0]') 530 [ [1.5, 2.6, '234'], 531 [0, 3, '12'], 532 [0, '0 div 0', ''], 533 [1, '0 div 0', ''], 534 ['-42', '1 div 0', '12345'], 535 ['-1 div 0', '1 div 0', ''] 536 ].each { |start, length, expected| 537 set = doc.elements.to_a("//test[substring(@string, #{start}, #{length}) = '#{expected}']") 538 assert_equal 1, set.size, "#{start}, #{length}, '#{expected}'" 539 } 540 end 541 542 def test_translate 543 source = <<-EOF 544 <doc> 545 <case name='w3c one' result='BAr' /> <!-- w3c --> 546 <case name='w3c two' result='AAA' /> <!-- w3c --> 547 <case name='alchemy' result="gold" /> <!-- mike --> 548 <case name='vbxml one' result='A Space Odyssey' /> 549 <case name='vbxml two' result='AbCdEf' /> 550 </doc> 551 EOF 552 553 doc = Document.new(source) 554 555 [ ['bar', 'abc', 'ABC', 'w3c one'], 556 ['--aaa--','abc-','ABC', 'w3c two'], 557 ['lead', 'dear language', 'doll groover', 'alchemy'], 558 ['A Space Odissei', 'i', 'y', 'vbxml one'], 559 ['abcdefg', 'aceg', 'ACE', 'vbxml two'], 560 ].each { |arg1, arg2, arg3, name| 561 translate = "translate('#{arg1}', '#{arg2}', '#{arg3}')" 562 set = doc.elements.to_a("//case[@result = #{translate}]") 563 assert_equal 1, set.size, translate 564 assert_equal name, set[0].attributes['name'] 565 } 566 end 567 568 def test_math 569 d = Document.new( '<a><b/><c/></a>' ) 570 assert XPath.first( d.root, 'node()[1]' ) 571 assert_equal 'b', XPath.first( d.root, 'node()[1]' ).name 572 assert XPath.first( d.root, 'node()[0 + 1]' ) 573 assert_equal 'b', XPath.first( d.root, './node()[0 + 1]' ).name 574 assert XPath.first( d.root, 'node()[1 + 1]' ) 575 assert_equal 'c', XPath.first( d.root, './node()[1 + 1]' ).name 576 assert XPath.first( d.root, 'node()[4 div 2]' ) 577 assert_equal 'c', XPath.first( d.root, './node()[4 div 2]' ).name 578 assert XPath.first( d.root, 'node()[2 - 1]' ) 579 assert_equal 'b', XPath.first( d.root, './node()[2 - 1]' ).name 580 assert XPath.first( d.root, 'node()[5 mod 2]' ) 581 assert_equal 'b', XPath.first( d.root, './node()[5 mod 2]' ).name 582 assert XPath.first( d.root, 'node()[8 mod 3]' ) 583 assert_equal 'c', XPath.first( d.root, './node()[8 mod 3]' ).name 584 assert XPath.first( d.root, 'node()[1 * 2]' ) 585 assert_equal 'c', XPath.first( d.root, './node()[1 * 2]' ).name 586 assert XPath.first( d.root, 'node()[2 + -1]' ) 587 assert_equal 'b', XPath.first( d.root, './node()[2 + -1]' ).name 588 end 589 590 def test_name 591 assert_raise( UndefinedNamespaceException, "x should be undefined" ) { 592 REXML::Document.new("<a x='foo'><b/><x:b/></a>") 593 } 594 d = REXML::Document.new("<a xmlns:x='foo'><b/><x:b/></a>") 595 assert_equal 1, d.root.elements.to_a('*[name() = "b"]').size 596 assert_equal 1, d.elements.to_a('//*[name() = "x:b"]').size 597 end 598 599 def test_local_name 600 d = REXML::Document.new("<a xmlns:x='foo'><b/><x:b/></a>") 601 assert_equal 2, d.root.elements.to_a('*[local_name() = "b"]').size 602 assert_equal 2, d.elements.to_a('//*[local_name() = "b"]').size 603 end 604 605 def test_comparisons 606 source = "<a><b id='1'/><b id='2'/><b id='3'/></a>" 607 doc = REXML::Document.new(source) 608 609 # NOTE TO SER: check that number() is required 610 assert_equal 2, REXML::XPath.match(doc, "//b[number(@id) > 1]").size 611 assert_equal 3, REXML::XPath.match(doc, "//b[number(@id) >= 1]").size 612 assert_equal 1, REXML::XPath.match(doc, "//b[number(@id) <= 1]").size 613 assert_equal 1, REXML::XPath.match(doc, "//b[number(@id) = (1 * 1)]").size 614 assert_equal 1, REXML::XPath.match(doc, "//b[number(@id) = (1 mod 2)]").size 615 assert_equal 1, REXML::XPath.match(doc, "//b[number(@id) = (4 div 2)]").size 616 end 617 618 # Contributed by Kouhei 619 def test_substring_before 620 doc = Document.new("<r><a/><b/><c/></r>") 621 assert_equal("a", doc.root.elements.to_a("*[name()=substring-before('abc', 'b')]")[0].name) 622 assert_equal("c", doc.root.elements.to_a("*[name()=substring-after('abc', 'b')]")[0].name) 623 end 624 625 def test_spaces 626 doc = Document.new("<a> 627 <b> 628 <c id='a'/> 629 </b> 630 <c id='b'/> 631 </a>") 632 assert_equal( 1, REXML::XPath.match(doc, 633 "//*[local-name()='c' and @id='b']").size ) 634 assert_equal( 1, REXML::XPath.match(doc, 635 "//*[ local-name()='c' and @id='b' ]").size ) 636 assert_equal( 1, REXML::XPath.match(doc, 637 "//*[ local-name() = 'c' and @id = 'b' ]").size ) 638 assert_equal( 1, 639 REXML::XPath.match(doc, '/a/c[@id]').size ) 640 assert_equal( 1, 641 REXML::XPath.match(doc, '/a/c[(@id)]').size ) 642 assert_equal( 1, 643 REXML::XPath.match(doc, '/a/c[ @id ]').size ) 644 assert_equal( 1, 645 REXML::XPath.match(doc, '/a/c[ (@id) ]').size ) 646 assert_equal( 1, 647 REXML::XPath.match(doc, '/a/c[( @id )]').size ) 648 assert_equal( 1, REXML::XPath.match(doc.root, 649 '/a/c[ ( @id ) ]').size ) 650 assert_equal( 1, REXML::XPath.match(doc, 651 '/a/c [ ( @id ) ] ').size ) 652 assert_equal( 1, REXML::XPath.match(doc, 653 ' / a / c [ ( @id ) ] ').size ) 654 end 655 656 def test_text_nodes 657 # source = "<root> 658 #<child/> 659 #<child>test</child> 660 #</root>" 661 source = "<root><child>test</child></root>" 662 d = REXML::Document.new( source ) 663 r = REXML::XPath.match( d, %q{/root/child[text()="test"]} ) 664 assert_equal( 1, r.size ) 665 assert_equal( "child", r[0].name ) 666 assert_equal( "test", r[0].text ) 667 end 668 669 def test_auto_string_value 670 source = "<root><foo/><title>Introduction</title></root>" 671 d = REXML::Document.new( source ) 672 #r = REXML::XPath.match( d, %q{/root[title="Introduction"]} ) 673 #assert_equal( 1, r.size ) 674 source = "<a><b/><c/><c>test</c></a>" 675 d = REXML::Document.new( source ) 676 r = REXML::XPath.match( d, %q{/a[c='test']} ) 677 assert_equal( 1, r.size ) 678 r = REXML::XPath.match( d, %q{a[c='test']} ) 679 assert_equal( 1, r.size ) 680 r = d.elements["/a[c='test']"] 681 assert_not_nil( r ) 682 r = d.elements["a[c='test']"] 683 assert_not_nil( r ) 684 r = d.elements["a[c='xtest']"] 685 assert_nil( r ) 686 r = REXML::XPath.match( d, %q{a[c='xtest']} ) 687 assert_equal( 0, r.size ) 688 end 689 690 def test_ordering 691 source = "<a><b><c id='1'/><c id='2'/></b><b><d id='1'/><d id='2'/></b></a>" 692 d = REXML::Document.new( source ) 693 r = REXML::XPath.match( d, %q{/a/*/*[1]} ) 694 assert_equal( 1, r.size ) 695 r.each { |el| assert_equal( '1', el.attribute('id').value ) } 696 end 697 698 def test_descendant_or_self_ordering 699 source = "<a> 700 <b> 701 <c id='1'/> 702 <c id='2'/> 703 </b> 704 <b> 705 <d id='1'> 706 <c id='3'/> 707 </d> 708 <d id='2'> 709 <e> 710 <c id='4'/> 711 </e> 712 </d> 713 </b> 714 </a>" 715 d = REXML::Document.new( source ) 716 cs = XPath.match( d, "/descendant-or-self::c" ) 717 assert_equal( 4, cs.length ) 718 1.upto(4) {|x| assert_equal( x.to_s, cs[x-1].attributes['id'] ) } 719 end 720 721 def test_and 722 d = Document.new %q{<doc><route run='*' title='HNO' 723 destination='debian_production1' date='*' edition='*' 724 source='debian_satellite1'/></doc>} 725 assert_equal( nil, d.root.elements["route[@run='0']"] ) 726 assert_equal( nil, d.root.elements["route[@run='0' and @title='HNO']"] ) 727 end 728 729 730 def test_numbers 731 d = Document.new %q{<a x="0" y="*" z="4e" w="e4" v="a"/>} 732 733 xp1 = "/a[ @x = 0 ]" 734 xp2 = "/a[ @x = '0' ]" 735 xp3 = "/a[ (@x + 1) = 1 ]" 736 xp4 = "/a[ @y = 0 ]" 737 xp5 = "/a[ (@z + 1) = 5 ]" 738 xp6 = "/a[ (@w + 1) = 5 ]" 739 xp7 = "/a[ (@v + 1) = 1 ]" 740 xp8 = "/a[ @n = 0 ]" 741 742 assert_equal( 1, XPath.match( d, xp1 ).length ) 743 assert_equal( 1, XPath.match( d, xp2 ).length ) 744 assert_equal( 1, XPath.match( d, xp3 ).length ) 745 assert_equal( 0, XPath.match( d, xp4 ).length ) 746 assert_equal( 0, XPath.match( d, xp5 ).length ) 747 assert_equal( 0, XPath.match( d, xp6 ).length ) 748 assert_equal( 0, XPath.match( d, xp7 ).length ) 749 assert_equal( 0, XPath.match( d, xp8 ).length ) 750 end 751 752 def test_tobis_preceding 753 doc_string = '<a> 754 <b/> 755 <c> 756 <d/> 757 <e/> 758 </c> 759</a>' 760 761 doc = Document.new(doc_string) 762 763 # e = REXML::XPath.first(doc,'/a/c/e') 764 e = doc.root.get_elements('/a/c/e')[0] 765 assert_equal( 1, e.get_elements('preceding-sibling::*').length ) 766 assert_equal( 2, XPath.match(e, 'preceding::*').length ) 767 end 768 769 770 def test_filtering 771 #doc=Document.new("<a><b><c1/><c2/></b><b><c3/><c4/></b><b><c5/><c6/></b></a>") 772 #assert_equal( 3, XPath.match( doc, '/a/b/*[1]' ).length ) 773 #assert_equal( 2, XPath.match( doc, '/a/b/following-sibling::*[1]' ).length ) 774 end 775 776 # Submitted by Alex 777 def test_union 778 data = %Q{<div id="the_div"> 779 <span id="the_span"> 780 <strong id="the_strong">a</strong> 781 </span> 782 <em id="the_em2">b</em> 783</div>} 784 rd = REXML::Document.new( data ) 785 #union = rd.get_elements("/div/span | /div/em") 786 #assert_equal(2, union.length, "/div/span | /div/em" ) 787 union = rd.get_elements('//*[name()="em" or name()="strong"]') 788 assert_equal(2, union.length, 'name() and "or" failed') 789 union = rd.get_elements('//em|//strong') 790 assert_equal(2, union.length, 791 'Both tag types are returned by XPath union operator') 792 end 793 794 795 def test_union2 796 src = <<-EOL 797<div id="the_div"> 798<span id="the_span"> 799<strong id="the_strong">a</strong> 800</span> 801<em id="the_em2">b</em> 802</div> 803 EOL 804 rd = REXML::Document.new( src ) 805 union = rd.get_elements('//em|//strong') 806 assert_equal(2, union.length, 807 'Both tag types are returned by XPath union operator') 808 end 809 810 811 def test_a_star_star_one 812 string = <<-EOL 813<a> 814 <b> 815 <c1/> 816 <d/> 817 <e/> 818 <f/> 819 </b> 820 <b> 821 <c2/> 822 <d/> 823 <e/> 824 <f/> 825 </b> 826</a> 827 EOL 828 d = REXML::Document.new( string ) 829 c1 = XPath.match( d, '/a/*/*[1]' ) 830 assert_equal( 1, c1.length ) 831 assert_equal( 'c1', c1[0].name ) 832 end 833 834 def test_sum 835 d = Document.new("<a>"+ 836 "<b>1</b><b>2</b><b>3</b>"+ 837 "<c><d>1</d><d>2</d></c>"+ 838 "<e att='1'/><e att='2'/>"+ 839 "</a>") 840 841 for v,p in [[6, "sum(/a/b)"], 842 [9, "sum(//b | //d)"], 843 [3, "sum(/a/e/@*)"] ] 844 assert_equal( v, XPath::match( d, p ).first ) 845 end 846 end 847 848 def test_xpath_namespace 849 d = REXML::Document.new("<tag1 xmlns='ns1'><tag2 xmlns='ns2'/><tada>xa</tada></tag1>") 850 x = d.root 851 num = 0 852 x.each_element('tada') { num += 1 } 853 assert_equal(1, num) 854 end 855 856 def test_ticket_39 857 doc = REXML::Document.new( <<-EOL ) 858 <rss> 859 <channel> 860 <!-- removing the namespace declaration makes the test pass --> 861 <convertLineBreaks xmlns="http://www.blogger.com/atom/ns#">true</convertLineBreaks> 862 <item> 863 <title>Item 1</title> 864 </item> 865 <item> 866 <title>Item 2</title> 867 <pubDate>Thu, 13 Oct 2005 19:59:00 +0000</pubDate> 868 </item> 869 <item> 870 <title>Item 3</title> 871 </item> 872 </channel> 873 </rss> 874 EOL 875 root_node = XPath.first(doc, "rss") 876 assert_not_nil root_node 877 channel_node = XPath.first(root_node, "channel") 878 assert_not_nil channel_node 879 items = XPath.match(channel_node, "*") 880 assert_equal 4, items.size 881 items = XPath.match(channel_node, "item") 882 assert_equal 3, items.size # fails 883 end 884 885 886 def test_ticket_42 887 source = "<a></a>" 888 doc = Document.new(source) 889 bElem = Element.new('b') 890 doc.root.add_element(bElem) 891 doc.elements.each('//b[name(..) = "a"]') { |x| 892 assert_equal x,bElem 893 } 894 end 895 896 def test_ticket_56 897 namespaces = {'h' => 'http://www.w3.org/1999/xhtml'} 898 899 finaldoc = REXML::Document.new(File.read(fixture_path('google.2.xml'))) 900 901 column_headers = [] 902 903 REXML::XPath.each(finaldoc, '//h:form[@action="ModifyCampaign"]//h:th', 904 namespaces) do |el| 905 node = REXML::XPath.first(el, 'h:a/text()', namespaces) 906 column_headers << (node ? node.value : nil) 907 end 908 column_headers.map! { |h| h.to_s.strip.chomp } 909 expected = ["", "", "Current Status", "Current Budget", 910 "Clicks", "Impr.", "CTR", "Avg. CPC", "Cost", "Conv. Rate", 911 "Cost/Conv."] 912 assert_equal( expected, column_headers ) 913 end 914 915 916 def test_ticket_70 917 string = <<EOF 918 919<mydoc> 920 921 <someelement attribute="1.10">Text1, text, 922text</someelement> 923 924 <someelement attribute="1.11">Text2, text, 925text</someelement> 926 927 928</mydoc> 929 930EOF 931 doc = Document.new string 932 assert_equal( 1, XPath.match( doc, "//someelement[contains(@attribute,'1.10')]" ).length ) 933 end 934 935 def test_ticket_43 936 #url = http://news.search.yahoo.com/news/rss?p=market&ei=UTF-8&fl=0&x=wrt 937 938 sum = Document.new(File.new(fixture_path("yahoo.xml"))).elements.to_a("//item").size 939 assert_equal( 10, sum ) 940 941 text = Document.new(File.new(fixture_path("yahoo.xml"))).elements.to_a(%Q{//title[contains(text(), "'")]}).collect{|e| e.text}.join 942 assert_equal( "Broward labor market's a solid performer (Miami Herald)", text ) 943 end 944 945 def test_ticket_57 946 data = "<?xml version='1.0'?><a:x xmlns:a='1'><a:y p='p' q='q'><a:z>zzz</a:z></a:y></a:x>" 947 948 r = Document.new(data) 949 950 assert_equal(Text, REXML::XPath.first(r,"a:x/a:y[@p='p' and @q='q']/a:z/text()").class) 951 assert_equal("zzz", REXML::XPath.first(r,"a:x/a:y[@p='p' and @q='q']/a:z/text()").to_s) 952 end 953 954 def test_ticket_59 955 data = "<a> 956 <c id='1'/> 957 <c id='2'/> 958 <b> 959 <c id='3'/> 960 </b> 961 <c id='4'/> 962 <b> 963 <b> 964 <c id='5'/> 965 </b> 966 <c id='6'/> 967 </b> 968 <c id='7'/> 969 <b> 970 <b> 971 <c id='8'/> 972 <b> 973 <c id='9'/> 974 <b> 975 <c id='10'/> 976 </b> 977 <c id='11'/> 978 </b> 979 </b> 980 </b> 981 <c id='12'/> 982 </a>" 983 d = Document.new(data) 984 res = d.elements.to_a( "//c" ).collect {|e| e.attributes['id'].to_i} 985 assert_equal( res, res.sort ) 986 end 987 988 def ticket_61_fixture(doc, xpath) 989 matches = [] 990 doc.elements.each(xpath) do |element| 991 matches << element 992 assert_equal('Add', element.text) 993 assert_equal('ButtonText', element.attributes['class']) 994 end 995 assert_equal(1, matches.length) 996 end 997 998 def test_ticket_61_text 999 file = File.open(fixture_path("ticket_61.xml")) 1000 doc = REXML::Document.new file 1001 ticket_61_fixture( doc, "//div[text()='Add' and @class='ButtonText']" ) 1002 end 1003 1004 def test_ticket_61_contains 1005 file = File.open(fixture_path("ticket_61.xml")) 1006 doc = REXML::Document.new file 1007 ticket_61_fixture( doc, "//div[contains(.,'Add') and @class='ButtonText']" ) 1008 end 1009 1010 def test_namespaces_0 1011 d = Document.new(%q{<x:a xmlns:x="y"/>}) 1012 assert_equal( 1, XPath.match( d, "//x:a" ).size ) 1013 assert_equal( 1, XPath.match( d, "//x:*" ).size ) 1014 end 1015 1016 def test_ticket_71 1017 doc = Document.new(%Q{<root xmlns:ns1="xyz" xmlns:ns2="123"><element ns1:attrname="foo" ns2:attrname="bar"/></root>}) 1018 el = doc.root.elements[1] 1019 assert_equal( "element", el.name ) 1020 el2 = XPath.first( doc.root, "element[@ns:attrname='foo']", { 'ns' => "xyz" } ) 1021 assert_equal( el, el2 ) 1022 end 1023 1024 def test_ticket_78 1025 doc = <<-EOT 1026 <root> 1027 <element> 1028 <tag x='1'>123</tag> 1029 </element> 1030 <element> 1031 <tag x='2'>123a</tag> 1032 </element> 1033 </root> 1034 EOT 1035 seq = %w{BEGIN 123 END BEGIN 123a END} 1036 1037 xmlDoc = Document.new(doc) 1038 1039 ["//element[tag='123']/tag", "//element[tag='123a']/tag"].each do |query| 1040 assert_equal( "BEGIN", seq.shift ) 1041 XPath.each(xmlDoc, query) { |element| 1042 assert_equal( seq.shift, element.text ) 1043 } 1044 assert_equal( "END", seq.shift ) 1045 end 1046 end 1047 1048 def test_ticket_79 1049 source = "<a><b><c>test</c></b><b><c>3</c></b></a>" 1050 d = REXML::Document.new( source ) 1051 r = REXML::XPath.match( d, %q{/a/b[c='test']} ) 1052 assert_equal(1, r.size()) 1053 r = REXML::XPath.match( d, %q{/a/b[c='3']} ) 1054 assert_equal(1, r.size()) 1055 end 1056 1057 def test_or_and 1058 doc = " 1059<html> 1060 <head> 1061 <title>test</title> 1062 </head> 1063 <body> 1064 <p> 1065 A <a rel=\"sub\" href=\"/\">link</a>. 1066 </p> 1067 </body> 1068</html> 1069" 1070 1071 xmldoc = REXML::Document.new(doc) 1072 xpath = "descendant::node()[(local-name()='link' or local-name()='a') and @rel='sub']" 1073 hrefs = [] 1074 xmldoc.elements.each(xpath) do |element| 1075 hrefs << element.attributes["href"] 1076 end 1077 assert_equal(["/"], hrefs, "Bug #3842 [ruby-core:32447]") 1078 end 1079end 1080