PageRenderTime 50ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/test/rexml/test_xpath.rb

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