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