mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00

GitHub: fix #17
There is a bug when they are used against node set. They should return
boolean value but they returned node set.
Reported by Mirko Budszuhn. Thanks!!!
a02bf38440
1125 lines
34 KiB
Ruby
1125 lines
34 KiB
Ruby
# frozen_string_literal: false
|
|
require_relative "../rexml_test_utils"
|
|
|
|
require "rexml/document"
|
|
|
|
module REXMLTests
|
|
class TestXPathBase < Test::Unit::TestCase
|
|
include REXMLTestUtils
|
|
include REXML
|
|
SOURCE = <<-EOF
|
|
<a id='1'>
|
|
<b id='2' x='y'>
|
|
<c id='3'/>
|
|
<c id='4'/>
|
|
</b>
|
|
<d id='5'>
|
|
<c id='6' x='y'/>
|
|
<c id='7'/>
|
|
<c id='8'/>
|
|
<q id='19'/>
|
|
</d>
|
|
<e id='9'>
|
|
<f id='10' a='b'/>
|
|
<f id='11' a='c'/>
|
|
<f id='12' a='d'>
|
|
<g id='13'/>
|
|
</f>
|
|
<f id='14' a='d'/>
|
|
</e>
|
|
<m id='15'>
|
|
<n id='16'>
|
|
<o id='17'>
|
|
<p id='18'/>
|
|
</o>
|
|
</n>
|
|
</m>
|
|
</a>
|
|
EOF
|
|
JENI_TENNISON = <<-EOJT
|
|
<a>
|
|
<b>
|
|
<c>
|
|
<d>
|
|
<e id='x'>
|
|
<f/>
|
|
</e>
|
|
</d>
|
|
</c>
|
|
<c>
|
|
<d>
|
|
<e id='y'/>
|
|
</d>
|
|
</c>
|
|
</b>
|
|
<b>
|
|
<c>
|
|
<d>
|
|
<e id='z'/>
|
|
</d>
|
|
</c>
|
|
</b>
|
|
</a>
|
|
EOJT
|
|
|
|
def setup
|
|
@@doc = Document.new(SOURCE) unless defined? @@doc
|
|
@@jeni = Document.new( JENI_TENNISON ) unless defined? @@jeni
|
|
end
|
|
|
|
def each_test( element, xpath )
|
|
count = 0
|
|
XPath::each( element, xpath ) { |child|
|
|
count += 1
|
|
yield child if block_given?
|
|
}
|
|
count
|
|
end
|
|
|
|
def test_descendant
|
|
doc = Document.new("<a><b><c id='1'/></b><d><b><c id='2'/></b></d></a>")
|
|
p = XPath.match( doc, "//c" )
|
|
assert_equal( 2, p.size )
|
|
p = XPath.first( @@doc, "//p" )
|
|
assert_equal "p", p.name
|
|
c = each_test( @@doc, "//c" ) { |child| assert_equal "c", child.name }
|
|
assert_equal 5, c
|
|
c = each_test( @@doc.root, "b//c" ) { |child|
|
|
assert_equal "c", child.name
|
|
}
|
|
assert_equal 2, c
|
|
|
|
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>" )
|
|
# //para[1] : all descendants which are the first para child of their parent
|
|
assert_equal( 4, XPath.match( doc, "//z[1]" ).size )
|
|
# /descendant::para[1] : the first descendant para element
|
|
assert_equal( 1, XPath.match( doc, "/descendant::z[1]" ).size )
|
|
end
|
|
|
|
def test_root
|
|
source = "<a><b/></a>"
|
|
doc = Document.new( source )
|
|
assert_equal doc, doc.root_node
|
|
assert_equal "a", XPath::first( doc, "/" ).elements[1].name
|
|
end
|
|
|
|
def test_abbreviated_simple_child
|
|
assert_equal "a", XPath::first(@@doc, "a").name
|
|
end
|
|
|
|
def test_child
|
|
c = XPath::first( @@doc, "a/b/c" )
|
|
assert_equal "c", c.name
|
|
assert_equal "3", XPath::first(@@doc, "a/b/c").attributes["id"]
|
|
end
|
|
|
|
def test_root_child
|
|
assert_equal "a", XPath::first(@@doc, "/a").name
|
|
c = XPath::first( @@doc, "a/b/c" )
|
|
assert_equal "a", XPath::first(c, "/a").name
|
|
end
|
|
|
|
def test_root_children
|
|
c = XPath::first( @@doc, "a/b/c" )
|
|
assert_equal "2", XPath::first(c, "/a/b").attributes["id"]
|
|
end
|
|
|
|
def test_abbreviated_step
|
|
c = XPath::first( @@doc, "a/b/c" )
|
|
assert_equal("c", c.name)
|
|
assert_equal("a", XPath::first(@@doc.root, ".").name)
|
|
assert_equal("b", XPath::first(c, "..").name)
|
|
assert_equal("a", XPath::first(@@doc, "a/b/..").name)
|
|
|
|
doc = File.open(fixture_path("project.xml")) do |f|
|
|
REXML::Document.new(f)
|
|
end
|
|
c = each_test(doc.root, "./Description" ) { |child|
|
|
assert_equal("Description",child.name)
|
|
}
|
|
assert_equal 1, c
|
|
end
|
|
|
|
# Things that aren't tested elsewhere
|
|
def test_predicates
|
|
assert_equal "12", XPath::first(@@doc, "a/e/f[3]").attributes["id"]
|
|
assert_equal "13", XPath::first(@@doc, "a/e/f[3]/g").attributes["id"]
|
|
assert_equal "14", XPath::first(@@doc, "a/e/f[@a='d'][2]").attributes["id"]
|
|
assert_equal "14", XPath::first(@@doc, "a/e/f[@a='d'][@id='14']").attributes["id"]
|
|
assert_equal "a", XPath::first( @@doc, "*[name()='a' and @id='1']" ).name
|
|
c=each_test( @@doc, "//*[name()='f' and @a='d']") { |i|
|
|
assert_equal "f", i.name
|
|
}
|
|
assert_equal 2, c
|
|
c=each_test( @@doc, "//*[name()='m' or @a='d']") { |i|
|
|
assert ["m","f"].include?(i.name)
|
|
}
|
|
assert_equal 3, c
|
|
|
|
assert_equal "b", XPath::first( @@doc, "//b[@x]" ).name
|
|
end
|
|
|
|
def test_node_type
|
|
doc = Document.new "<a><?foo bar?><!--comment-->text</a>"
|
|
#res = XPath::first(doc.root, "text()")
|
|
#assert_equal "text", res.to_s
|
|
|
|
#res = XPath::first(doc, "*")
|
|
#assert_equal "a", res.name
|
|
|
|
assert_equal( :processing_instruction,
|
|
XPath::first(doc.root, "processing-instruction()").node_type)
|
|
assert_equal( :comment, XPath::first(doc.root, "comment()").node_type)
|
|
end
|
|
|
|
def test_functions
|
|
# trivial text() test
|
|
# confuse-a-function
|
|
source = "<a>more <b id='1'/><b id='2'>dumb</b><b id='3'/><c/> text</a>"
|
|
doc = Document.new source
|
|
res = ""
|
|
#XPath::each(doc.root, "text()") {|val| res << val.to_s}
|
|
#assert_equal "more text", res
|
|
|
|
#res = XPath::first(doc.root, "b[last()]")
|
|
#assert_equal '3', res.attributes['id']
|
|
res = XPath::first(doc.root, "b[position()=2]")
|
|
assert_equal '2', res.attributes['id']
|
|
res = XPath::first(doc.root, "*[name()='c']")
|
|
assert_equal "c", res.name
|
|
end
|
|
|
|
def no_test_ancestor
|
|
doc = REXML::Document.new(File.new(fixture_path("testsrc.xml")))
|
|
doc.elements.each("//item") { |el| print el.name
|
|
if el.attributes['x']
|
|
puts " -- "+el.attributes['x']
|
|
else
|
|
puts
|
|
end
|
|
}
|
|
doc.elements.each("//item/ancestor::") { |el| print el.name
|
|
if el.attributes['x']
|
|
puts " -- "+el.attributes['x']
|
|
else
|
|
puts
|
|
end
|
|
}
|
|
end
|
|
|
|
# Here are some XPath tests that were originally submitted by ...
|
|
# The code has changed some, but the logic and the source documents are the
|
|
# same.
|
|
# This method reads a document from a file, and then a series of xpaths,
|
|
# also from a file. It then checks each xpath against the source file.
|
|
def test_more
|
|
xmlsource = fixture_path("testsrc.xml")
|
|
xpathtests = fixture_path("xp.tst")
|
|
|
|
doc = File.open(xmlsource) {|f| REXML::Document.new(f) }
|
|
#results = ""
|
|
results = REXML::Document.new
|
|
results.add_element "test-results"
|
|
File.foreach(xpathtests) do |line|
|
|
line.strip!
|
|
begin
|
|
doc.root
|
|
#puts "#"*80
|
|
#print "\nDoing #{line} " ; $stdout.flush
|
|
doc.elements.each(line) do |el|
|
|
#print "." ; $stdout.flush
|
|
results.root << el.clone
|
|
#results << el.to_s
|
|
end
|
|
#ObjectSpace.garbage_collect
|
|
GC::start
|
|
rescue Exception => z
|
|
#puts "\n'#{line}' failed"
|
|
fail("Error on line #{line}:\n#{z.message}\n"+z.backtrace[0,10].join("\n"))
|
|
#results.root.add_element( "error", {"path"=>line}).text = z.message+"\n"+z.backtrace[0,10].join("\n")
|
|
#results << "<error path='"+line+"'>"+z.message+"</error>"
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_axe_descendant
|
|
assert_equal "f", XPath::first( @@doc, "descendant::f").name
|
|
end
|
|
|
|
def test_axe_parent
|
|
q = XPath.first( @@doc, "a/d/c/parent::*/q" )
|
|
assert_equal 19, q.attributes["id"].to_i
|
|
end
|
|
|
|
def test_abbreviated_attribute
|
|
assert_equal 'a', XPath::first( @@doc, "a[@id='1']" ).name
|
|
c = XPath::first( @@doc, "a/b/c[@id='4']" )
|
|
assert_equal 'c', c.name
|
|
assert_equal '4', c.attributes['id']
|
|
|
|
result = XPath::first( @@doc, "descendant::f[@a='c']")
|
|
assert_equal "11", result.attributes['id']
|
|
|
|
assert_equal "11", XPath::first(@@doc, "a/e/f[@a='c']").attributes["id"]
|
|
assert_equal "11", XPath::first(@@doc, "a/e/*[@a='c']").attributes["id"]
|
|
end
|
|
|
|
def test_axe_self
|
|
c = XPath::first( @@doc, "a/b/c" )
|
|
assert c
|
|
assert_equal "c", c.name
|
|
assert_equal "c", XPath::first( c, "self::node()" ).name
|
|
end
|
|
|
|
def test_axe_ancestor
|
|
doc = REXML::Document.new "
|
|
<a>
|
|
<b id='1'>
|
|
<c>
|
|
<b id='2'>
|
|
<d/>
|
|
</b>
|
|
</c>
|
|
</b>
|
|
</a>"
|
|
|
|
d = XPath.first( doc, "//d" )
|
|
assert_equal "d", d.name
|
|
b = each_test( d, "ancestor::b" ) { |el|
|
|
assert((1..2) === el.attributes['id'].to_i,
|
|
"Expected #{el.attributes['id']} to be either 1 or 2"
|
|
)
|
|
}
|
|
assert_equal 2, b
|
|
end
|
|
|
|
def test_axe_child
|
|
m = XPath.first( @@doc, "a/child::m" )
|
|
assert_equal 15, m.attributes['id'].to_i
|
|
end
|
|
|
|
def test_axe_attribute
|
|
a = XPath.first( @@doc, "a/attribute::id" )
|
|
assert_equal "1", a.value
|
|
a = XPath.first( @@doc, "a/e/f[@id='14']/attribute::a" )
|
|
assert_equal "d", a.value
|
|
end
|
|
|
|
def test_axe_sibling
|
|
doc = Document.new "<a><b><c/></b><e><f id='10'/><f id='11'/><f id='12'/></e></a>"
|
|
first_f = XPath.first( doc, "a/e/f" )
|
|
assert first_f
|
|
assert_equal '10', first_f.attributes['id']
|
|
next_f = XPath.first( doc, "a/e/f/following-sibling::node()" )
|
|
assert_equal '11', next_f.attributes['id']
|
|
|
|
b = XPath.first( doc, "a/e/preceding-sibling::node()" )
|
|
assert_equal 'b', b.name
|
|
end
|
|
|
|
def test_lang
|
|
doc = File.open(fixture_path("lang0.xml")) {|f| Document.new(f) }
|
|
#puts IO.read( "test/lang.xml" )
|
|
|
|
#puts XPath.match( doc, "//language/*" ).size
|
|
c = each_test( doc, "//language/*" ) { |element|
|
|
#puts "#{element.name}: #{element.text}"
|
|
}
|
|
assert_equal 4, c
|
|
end
|
|
|
|
def test_namespaces_1
|
|
source = <<-EOF
|
|
<foo xmlns:ts="this" xmlns:tt="that">
|
|
<ts:bar>this bar</ts:bar>
|
|
<tt:bar>that bar</tt:bar>
|
|
</foo>
|
|
EOF
|
|
doc = Document.new source
|
|
XPath.each( doc, "//bar" ) {
|
|
fail "'bar' should match nothing in this case"
|
|
}
|
|
|
|
namespace = {"t"=>"this"}
|
|
results = XPath.first( doc, "//t:bar", namespace )
|
|
assert_equal "this bar", results.text
|
|
end
|
|
|
|
def test_namespaces_2
|
|
source = <<-EOF
|
|
<foo xmlns:ts="this" xmlns:tt="that">
|
|
<ts:bar>this bar</ts:bar>
|
|
<tt:bar>that bar</tt:bar>
|
|
</foo>
|
|
EOF
|
|
doc = Document.new source
|
|
res = XPath::first(doc, "//*[local_name()='bar']")
|
|
assert res, "looking for //*[name()='bar']"
|
|
assert_equal 'this', res.namespace
|
|
res = XPath::first(doc.root, "*[namespace_uri()='that']")
|
|
assert_equal 'that bar', res.text
|
|
end
|
|
|
|
def test_complex
|
|
next_f = XPath.first( @@doc, "a/e/f[@id='11']/following-sibling::*" )
|
|
assert_equal 12, next_f.attributes['id'].to_i
|
|
prev_f = XPath.first( @@doc, "a/e/f[@id='11']/preceding-sibling::*" )
|
|
assert_equal 10, prev_f.attributes['id'].to_i
|
|
c = each_test( @@doc, "descendant-or-self::*[@x='y']" )
|
|
assert_equal 2, c
|
|
end
|
|
|
|
def match(xpath)
|
|
XPath.match(@@doc, xpath).collect(&:to_s)
|
|
end
|
|
|
|
def test_grouping
|
|
assert_equal([],
|
|
match("a/d/*[name()='d' and (name()='f' or name()='q')]"))
|
|
assert_equal(["<q id='19'/>"],
|
|
match("a/d/*[(name()='d' and name()='f') or name()='q']"))
|
|
end
|
|
|
|
def test_preceding
|
|
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>"
|
|
start = XPath.first( d, "/a/b[@id='1']" )
|
|
assert_equal 'b', start.name
|
|
c = XPath.first( start, "preceding::c" )
|
|
assert_equal '2', c.attributes['id']
|
|
|
|
c1, c0 = XPath.match( d, "/a/b/c[@id='2']/preceding::node()" )
|
|
assert_equal '1', c1.attributes['id']
|
|
assert_equal '0', c0.attributes['id']
|
|
|
|
c2, c1, c0, b, b2, b0 = XPath.match( start, "preceding::node()" )
|
|
|
|
assert_equal 'c', c2.name
|
|
assert_equal 'c', c1.name
|
|
assert_equal 'c', c0.name
|
|
assert_equal 'b', b.name
|
|
assert_equal 'b', b2.name
|
|
assert_equal 'b', b0.name
|
|
|
|
assert_equal '2', c2.attributes['id']
|
|
assert_equal '1', c1.attributes['id']
|
|
assert_equal '0', c0.attributes['id']
|
|
assert b.attributes.empty?
|
|
assert_equal '2', b2.attributes['id']
|
|
assert_equal '0', b0.attributes['id']
|
|
|
|
d = REXML::Document.new("<a><b/><c/><d/></a>")
|
|
matches = REXML::XPath.match(d, "/a/d/preceding::node()")
|
|
assert_equal("c", matches[0].name)
|
|
assert_equal("b", matches[1].name)
|
|
|
|
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>"
|
|
d = REXML::Document.new(s)
|
|
c = REXML::XPath.match( d, "//c[@id = '5']")
|
|
cs = REXML::XPath.match( c, "preceding::c" )
|
|
assert_equal( 4, cs.length )
|
|
end
|
|
|
|
def test_following
|
|
d = Document.new "<a><b id='0'/><b/><b><c id='1'/><c id='2'/></b><b id='1'/></a>"
|
|
start = XPath.first( d, "/a/b[@id='0']" )
|
|
assert_equal 'b', start.name
|
|
c = XPath.first( start, "following::c" )
|
|
assert_equal '1', c.attributes['id']
|
|
|
|
s = "<a><b><c><d/></c><e/></b><f><g><h/><i/></g></f><i/></a>"
|
|
d = Document.new(s)
|
|
c = XPath.first(d, '/a/b/c')
|
|
assert_equal 'c', c.name
|
|
res = XPath.match( c, 'following::*' )
|
|
assert_equal 6, res.size
|
|
res = XPath.match( c, 'following::i' )
|
|
assert_equal 2, res.size
|
|
end
|
|
|
|
# The following three paths were provided by
|
|
# Jeni Tennison <jeni@jenitennison.com>
|
|
# a consultant who is also an XSL and XPath expert
|
|
#def test_child_cubed
|
|
# els = @@jeni.elements.to_a("*****")
|
|
# assert_equal 3, els.size
|
|
#end
|
|
|
|
#def test_div_2
|
|
# results = doc.elements.to_a("/ div 2")
|
|
#end
|
|
|
|
#def test_nested_predicates
|
|
# puts @@jeni.root.elements[1].elements[1].name
|
|
# results = @@jeni.root.elements[1].elements[1].elements.to_a("../following-sibling::*[*[name() = name(current())]]")
|
|
# puts results
|
|
#end
|
|
|
|
# Contributed by Mike Stok
|
|
def test_starts_with
|
|
source = <<-EOF
|
|
<foo>
|
|
<a href="mailto:a@b.c">a@b.c</a>
|
|
<a href="http://www.foo.com">http://www.foo.com</a>
|
|
</foo>
|
|
EOF
|
|
doc = Document.new source
|
|
mailtos = doc.elements.to_a("//a[starts-with(@href, 'mailto:')]")
|
|
assert_equal 1, mailtos.size
|
|
assert_equal "mailto:a@b.c", mailtos[0].attributes['href']
|
|
|
|
ailtos = doc.elements.to_a("//a[starts-with(@href, 'ailto:')]")
|
|
assert_equal 0, ailtos.size
|
|
end
|
|
|
|
def test_toms_text_node
|
|
file = "<a>A<b>B</b><c>C<d>D</d>E</c>F</a>"
|
|
doc = Document.new(file)
|
|
assert_equal 'A', XPath.first(doc[0], 'text()').to_s
|
|
assert_equal 'AF', XPath.match(doc[0], 'text()').collect { |n|
|
|
n.to_s
|
|
}.join('')
|
|
assert_equal 'B', XPath.first(doc[0], 'b/text()').to_s
|
|
assert_equal 'D', XPath.first(doc[0], '//d/text()').to_s
|
|
assert_equal 'ABCDEF', XPath.match(doc[0], '//text()').collect {|n|
|
|
n.to_s
|
|
}.join('')
|
|
end
|
|
|
|
def test_string_length
|
|
doc = Document.new <<-EOF
|
|
<AAA>
|
|
<Q/>
|
|
<SSSS/>
|
|
<BB/>
|
|
<CCC/>
|
|
<DDDDDDDD/>
|
|
<EEEE/>
|
|
</AAA>
|
|
EOF
|
|
assert doc, "create doc"
|
|
|
|
set = doc.elements.to_a("//*[string-length(name()) = 3]")
|
|
assert_equal 2, set.size, "nodes with names length = 3"
|
|
|
|
set = doc.elements.to_a("//*[string-length(name()) < 3]")
|
|
assert_equal 2, set.size, "nodes with names length < 3"
|
|
|
|
set = doc.elements.to_a("//*[string-length(name()) > 3]")
|
|
assert_equal 3, set.size, "nodes with names length > 3"
|
|
end
|
|
|
|
# Test provided by Mike Stok
|
|
def test_contains
|
|
source = <<-EOF
|
|
<foo>
|
|
<a href="mailto:a@b.c">a@b.c</a>
|
|
<a href="http://www.foo.com">http://www.foo.com</a>
|
|
</foo>
|
|
EOF
|
|
doc = Document.new source
|
|
|
|
[
|
|
#['o', 2],
|
|
['foo', 1], ['bar', 0]].each { |search, expected|
|
|
set = doc.elements.to_a("//a[contains(@href, '#{search}')]")
|
|
assert_equal expected, set.size
|
|
}
|
|
end
|
|
|
|
# Mike Stok and Sean Russell
|
|
def test_substring
|
|
# examples from http://www.w3.org/TR/xpath#function-substring
|
|
doc = Document.new('<test string="12345" />')
|
|
|
|
Document.new("<a b='1'/>")
|
|
#puts XPath.first(d, 'node()[0 + 1]')
|
|
#d = Document.new("<a b='1'/>")
|
|
#puts XPath.first(d, 'a[0 mod 0]')
|
|
[ [1.5, 2.6, '234'],
|
|
[0, 3, '12'],
|
|
[0, '0 div 0', ''],
|
|
[1, '0 div 0', ''],
|
|
['-42', '1 div 0', '12345'],
|
|
['-1 div 0', '1 div 0', '']
|
|
].each { |start, length, expected|
|
|
set = doc.elements.to_a("//test[substring(@string, #{start}, #{length}) = '#{expected}']")
|
|
assert_equal 1, set.size, "#{start}, #{length}, '#{expected}'"
|
|
}
|
|
end
|
|
|
|
def test_translate
|
|
source = <<-EOF
|
|
<doc>
|
|
<case name='w3c one' result='BAr' /> <!-- w3c -->
|
|
<case name='w3c two' result='AAA' /> <!-- w3c -->
|
|
<case name='alchemy' result="gold" /> <!-- mike -->
|
|
<case name='vbxml one' result='A Space Odyssey' />
|
|
<case name='vbxml two' result='AbCdEf' />
|
|
</doc>
|
|
EOF
|
|
|
|
doc = Document.new(source)
|
|
|
|
[ ['bar', 'abc', 'ABC', 'w3c one'],
|
|
['--aaa--','abc-','ABC', 'w3c two'],
|
|
['lead', 'dear language', 'doll groover', 'alchemy'],
|
|
['A Space Odissei', 'i', 'y', 'vbxml one'],
|
|
['abcdefg', 'aceg', 'ACE', 'vbxml two'],
|
|
].each { |arg1, arg2, arg3, name|
|
|
translate = "translate('#{arg1}', '#{arg2}', '#{arg3}')"
|
|
set = doc.elements.to_a("//case[@result = #{translate}]")
|
|
assert_equal 1, set.size, translate
|
|
assert_equal name, set[0].attributes['name']
|
|
}
|
|
end
|
|
|
|
def test_math
|
|
d = Document.new( '<a><b/><c/></a>' )
|
|
assert XPath.first( d.root, 'node()[1]' )
|
|
assert_equal 'b', XPath.first( d.root, 'node()[1]' ).name
|
|
assert XPath.first( d.root, 'node()[0 + 1]' )
|
|
assert_equal 'b', XPath.first( d.root, './node()[0 + 1]' ).name
|
|
assert XPath.first( d.root, 'node()[1 + 1]' )
|
|
assert_equal 'c', XPath.first( d.root, './node()[1 + 1]' ).name
|
|
assert XPath.first( d.root, 'node()[4 div 2]' )
|
|
assert_equal 'c', XPath.first( d.root, './node()[4 div 2]' ).name
|
|
assert XPath.first( d.root, 'node()[2 - 1]' )
|
|
assert_equal 'b', XPath.first( d.root, './node()[2 - 1]' ).name
|
|
assert XPath.first( d.root, 'node()[5 mod 2]' )
|
|
assert_equal 'b', XPath.first( d.root, './node()[5 mod 2]' ).name
|
|
assert XPath.first( d.root, 'node()[8 mod 3]' )
|
|
assert_equal 'c', XPath.first( d.root, './node()[8 mod 3]' ).name
|
|
assert XPath.first( d.root, 'node()[1 * 2]' )
|
|
assert_equal 'c', XPath.first( d.root, './node()[1 * 2]' ).name
|
|
assert XPath.first( d.root, 'node()[2 + -1]' )
|
|
assert_equal 'b', XPath.first( d.root, './node()[2 + -1]' ).name
|
|
end
|
|
|
|
def test_name
|
|
assert_raise( UndefinedNamespaceException, "x should be undefined" ) {
|
|
REXML::Document.new("<a x='foo'><b/><x:b/></a>")
|
|
}
|
|
d = REXML::Document.new("<a xmlns:x='foo'><b/><x:b/></a>")
|
|
assert_equal 1, d.root.elements.to_a('*[name() = "b"]').size
|
|
assert_equal 1, d.elements.to_a('//*[name() = "x:b"]').size
|
|
end
|
|
|
|
def test_local_name
|
|
d = REXML::Document.new("<a xmlns:x='foo'><b/><x:b/></a>")
|
|
assert_equal 2, d.root.elements.to_a('*[local_name() = "b"]').size
|
|
assert_equal 2, d.elements.to_a('//*[local_name() = "b"]').size
|
|
end
|
|
|
|
def test_comparisons
|
|
source = "<a><b id='1'/><b id='2'/><b id='3'/></a>"
|
|
doc = REXML::Document.new(source)
|
|
|
|
# NOTE TO SER: check that number() is required
|
|
assert_equal 2, REXML::XPath.match(doc, "//b[number(@id) > 1]").size
|
|
assert_equal 3, REXML::XPath.match(doc, "//b[number(@id) >= 1]").size
|
|
assert_equal 1, REXML::XPath.match(doc, "//b[number(@id) <= 1]").size
|
|
assert_equal 1, REXML::XPath.match(doc, "//b[number(@id) = (1 * 1)]").size
|
|
assert_equal 1, REXML::XPath.match(doc, "//b[number(@id) = (1 mod 2)]").size
|
|
assert_equal 1, REXML::XPath.match(doc, "//b[number(@id) = (4 div 2)]").size
|
|
end
|
|
|
|
# Contributed by Kouhei
|
|
def test_substring_before
|
|
doc = Document.new("<r><a/><b/><c/></r>")
|
|
assert_equal("a", doc.root.elements.to_a("*[name()=substring-before('abc', 'b')]")[0].name)
|
|
assert_equal("c", doc.root.elements.to_a("*[name()=substring-after('abc', 'b')]")[0].name)
|
|
end
|
|
|
|
def test_spaces
|
|
doc = Document.new("<a>
|
|
<b>
|
|
<c id='a'/>
|
|
</b>
|
|
<c id='b'/>
|
|
<c id='c'/>
|
|
<c/>
|
|
</a>")
|
|
match = lambda do |xpath|
|
|
REXML::XPath.match(doc, xpath).collect(&:to_s)
|
|
end
|
|
assert_equal(["<c id='b'/>"],
|
|
match.call("//*[local-name()='c' and @id='b']"))
|
|
assert_equal(["<c id='b'/>"],
|
|
match.call("//*[ local-name()='c' and @id='b' ]"))
|
|
assert_equal(["<c id='b'/>"],
|
|
match.call("//*[ local-name() = 'c' and @id = 'b' ]"))
|
|
assert_equal(["<c id='b'/>", "<c id='c'/>"],
|
|
match.call('/a/c[@id]'))
|
|
assert_equal(["<c id='b'/>", "<c id='c'/>"],
|
|
match.call('/a/c[(@id)]'))
|
|
assert_equal(["<c id='b'/>", "<c id='c'/>"],
|
|
match.call('/a/c[ @id ]'))
|
|
assert_equal(["<c id='b'/>", "<c id='c'/>"],
|
|
match.call('/a/c[ (@id) ]'))
|
|
assert_equal(["<c id='b'/>", "<c id='c'/>"],
|
|
match.call('/a/c[( @id )]'))
|
|
assert_equal(["<c id='b'/>", "<c id='c'/>"],
|
|
match.call('/a/c[ ( @id ) ]'))
|
|
assert_equal(["<c id='b'/>", "<c id='c'/>"],
|
|
match.call('/a/c [ ( @id ) ] '))
|
|
assert_equal(["<c id='b'/>", "<c id='c'/>"],
|
|
match.call(' / a / c [ ( @id ) ] '))
|
|
assert_equal(["<c id='b'/>", "<c id='c'/>"],
|
|
match.call('/ a / child:: c [( @id )] /'))
|
|
end
|
|
|
|
def test_text_nodes
|
|
# source = "<root>
|
|
#<child/>
|
|
#<child>test</child>
|
|
#</root>"
|
|
source = "<root><child>test</child></root>"
|
|
d = REXML::Document.new( source )
|
|
r = REXML::XPath.match( d, %q{/root/child[text()="test"]} )
|
|
assert_equal( 1, r.size )
|
|
assert_equal( "child", r[0].name )
|
|
assert_equal( "test", r[0].text )
|
|
end
|
|
|
|
def test_auto_string_value
|
|
source = "<root><foo/><title>Introduction</title></root>"
|
|
d = REXML::Document.new( source )
|
|
#r = REXML::XPath.match( d, %q{/root[title="Introduction"]} )
|
|
#assert_equal( 1, r.size )
|
|
source = "<a><b/><c/><c>test</c></a>"
|
|
d = REXML::Document.new( source )
|
|
r = REXML::XPath.match( d, %q{/a[c='test']} )
|
|
assert_equal( 1, r.size )
|
|
r = REXML::XPath.match( d, %q{a[c='test']} )
|
|
assert_equal( 1, r.size )
|
|
r = d.elements["/a[c='test']"]
|
|
assert_not_nil( r )
|
|
r = d.elements["a[c='test']"]
|
|
assert_not_nil( r )
|
|
r = d.elements["a[c='xtest']"]
|
|
assert_nil( r )
|
|
r = REXML::XPath.match( d, %q{a[c='xtest']} )
|
|
assert_equal( 0, r.size )
|
|
end
|
|
|
|
def test_ordering
|
|
source = <<-XML
|
|
<a>
|
|
<b>
|
|
<c id='1'/>
|
|
<c id='2'/>
|
|
</b>
|
|
<b>
|
|
<d id='3'/>
|
|
<d id='4'/>
|
|
</b>
|
|
</a>
|
|
XML
|
|
d = REXML::Document.new( source )
|
|
r = REXML::XPath.match( d, %q{/a/*/*[1]} )
|
|
assert_equal(["1", "3"],
|
|
r.collect {|element| element.attribute("id").value})
|
|
end
|
|
|
|
def test_descendant_or_self_ordering
|
|
source = "<a>
|
|
<b>
|
|
<c id='1'/>
|
|
<c id='2'/>
|
|
</b>
|
|
<b>
|
|
<d id='1'>
|
|
<c id='3'/>
|
|
</d>
|
|
<d id='2'>
|
|
<e>
|
|
<c id='4'/>
|
|
</e>
|
|
</d>
|
|
</b>
|
|
</a>"
|
|
d = REXML::Document.new( source )
|
|
cs = XPath.match( d, "/descendant-or-self::c" )
|
|
assert_equal( 4, cs.length )
|
|
1.upto(4) {|x| assert_equal( x.to_s, cs[x-1].attributes['id'] ) }
|
|
end
|
|
|
|
def test_and
|
|
d = Document.new %q{<doc><route run='*' title='HNO'
|
|
destination='debian_production1' date='*' edition='*'
|
|
source='debian_satellite1'/></doc>}
|
|
assert_equal( nil, d.root.elements["route[@run='0']"] )
|
|
assert_equal( nil, d.root.elements["route[@run='0' and @title='HNO']"] )
|
|
end
|
|
|
|
|
|
def test_numbers
|
|
d = Document.new %q{<a x="0" y="*" z="4e" w="e4" v="a"/>}
|
|
|
|
xp1 = "/a[ @x = 0 ]"
|
|
xp2 = "/a[ @x = '0' ]"
|
|
xp3 = "/a[ (@x + 1) = 1 ]"
|
|
xp4 = "/a[ @y = 0 ]"
|
|
xp5 = "/a[ (@z + 1) = 5 ]"
|
|
xp6 = "/a[ (@w + 1) = 5 ]"
|
|
xp7 = "/a[ (@v + 1) = 1 ]"
|
|
xp8 = "/a[ @n = 0 ]"
|
|
|
|
assert_equal( 1, XPath.match( d, xp1 ).length )
|
|
assert_equal( 1, XPath.match( d, xp2 ).length )
|
|
assert_equal( 1, XPath.match( d, xp3 ).length )
|
|
assert_equal( 0, XPath.match( d, xp4 ).length )
|
|
assert_equal( 0, XPath.match( d, xp5 ).length )
|
|
assert_equal( 0, XPath.match( d, xp6 ).length )
|
|
assert_equal( 0, XPath.match( d, xp7 ).length )
|
|
assert_equal( 0, XPath.match( d, xp8 ).length )
|
|
end
|
|
|
|
def test_tobis_preceding
|
|
doc_string = '<a>
|
|
<b/>
|
|
<c>
|
|
<d/>
|
|
<e/>
|
|
</c>
|
|
</a>'
|
|
|
|
doc = Document.new(doc_string)
|
|
|
|
# e = REXML::XPath.first(doc,'/a/c/e')
|
|
e = doc.root.get_elements('/a/c/e')[0]
|
|
assert_equal( 1, e.get_elements('preceding-sibling::*').length )
|
|
assert_equal( 2, XPath.match(e, 'preceding::*').length )
|
|
end
|
|
|
|
|
|
def test_filtering
|
|
#doc=Document.new("<a><b><c1/><c2/></b><b><c3/><c4/></b><b><c5/><c6/></b></a>")
|
|
#assert_equal( 3, XPath.match( doc, '/a/b/*[1]' ).length )
|
|
#assert_equal( 2, XPath.match( doc, '/a/b/following-sibling::*[1]' ).length )
|
|
end
|
|
|
|
# Submitted by Alex
|
|
def test_union
|
|
data = %Q{<div id="the_div">
|
|
<span id="the_span">
|
|
<strong id="the_strong">a</strong>
|
|
</span>
|
|
<em id="the_em2">b</em>
|
|
</div>}
|
|
rd = REXML::Document.new( data )
|
|
#union = rd.get_elements("/div/span | /div/em")
|
|
#assert_equal(2, union.length, "/div/span | /div/em" )
|
|
union = rd.get_elements('//*[name()="em" or name()="strong"]')
|
|
assert_equal(2, union.length, 'name() and "or" failed')
|
|
union = rd.get_elements('//em|//strong')
|
|
assert_equal(2, union.length,
|
|
'Both tag types are returned by XPath union operator')
|
|
end
|
|
|
|
|
|
def test_union2
|
|
src = <<-EOL
|
|
<div id="the_div">
|
|
<span id="the_span">
|
|
<strong id="the_strong">a</strong>
|
|
</span>
|
|
<em id="the_em2">b</em>
|
|
</div>
|
|
EOL
|
|
rd = REXML::Document.new( src )
|
|
union = rd.get_elements('//em|//strong')
|
|
assert_equal(2, union.length,
|
|
'Both tag types are returned by XPath union operator')
|
|
end
|
|
|
|
|
|
def test_a_star_star_one
|
|
string = <<-EOL
|
|
<a>
|
|
<b>
|
|
<c1/>
|
|
<d/>
|
|
<e/>
|
|
<f/>
|
|
</b>
|
|
<b>
|
|
<c2/>
|
|
<d/>
|
|
<e/>
|
|
<f/>
|
|
</b>
|
|
</a>
|
|
EOL
|
|
d = REXML::Document.new( string )
|
|
cs = XPath.match( d, '/a/*/*[1]' )
|
|
assert_equal(["c1", "c2"], cs.collect(&:name))
|
|
end
|
|
|
|
def test_sum
|
|
d = Document.new(<<-XML)
|
|
<a>
|
|
<b>1</b>
|
|
<b>2</b>
|
|
<b>3</b>
|
|
<c>
|
|
<d>1</d>
|
|
<d>2</d>
|
|
</c>
|
|
<e att='1'/>
|
|
<e att='2'/>
|
|
</a>
|
|
XML
|
|
|
|
assert_equal([6], XPath::match(d, "sum(/a/b)"))
|
|
assert_equal([9], XPath::match(d, "sum(//b | //d)"))
|
|
assert_equal([3], XPath::match(d, "sum(/a/e/@*)"))
|
|
end
|
|
|
|
def test_xpath_namespace
|
|
d = REXML::Document.new(<<-XML)
|
|
<tag1 xmlns='ns1'>
|
|
<tag2 xmlns='ns2'/>
|
|
<tada>xa</tada>
|
|
<tada xmlns=''>xb</tada>
|
|
</tag1>
|
|
XML
|
|
actual = []
|
|
d.root.each_element('tada') do |element|
|
|
actual << element.to_s
|
|
end
|
|
assert_equal(["<tada>xa</tada>", "<tada xmlns=''>xb</tada>"],
|
|
actual)
|
|
end
|
|
|
|
def test_ticket_39
|
|
doc = REXML::Document.new( <<-EOL )
|
|
<rss>
|
|
<channel>
|
|
<!-- removing the namespace declaration makes the test pass -->
|
|
<convertLineBreaks xmlns="http://www.blogger.com/atom/ns#">true</convertLineBreaks>
|
|
<item>
|
|
<title>Item 1</title>
|
|
</item>
|
|
<item>
|
|
<title>Item 2</title>
|
|
<pubDate>Thu, 13 Oct 2005 19:59:00 +0000</pubDate>
|
|
</item>
|
|
<item>
|
|
<title>Item 3</title>
|
|
</item>
|
|
</channel>
|
|
</rss>
|
|
EOL
|
|
root_node = XPath.first(doc, "rss")
|
|
assert_not_nil root_node
|
|
channel_node = XPath.first(root_node, "channel")
|
|
assert_not_nil channel_node
|
|
items = XPath.match(channel_node, "*")
|
|
assert_equal 4, items.size
|
|
items = XPath.match(channel_node, "item")
|
|
assert_equal 3, items.size # fails
|
|
end
|
|
|
|
|
|
def test_ticket_42
|
|
source = "<a></a>"
|
|
doc = Document.new(source)
|
|
bElem = Element.new('b')
|
|
doc.root.add_element(bElem)
|
|
doc.elements.each('//b[name(..) = "a"]') { |x|
|
|
assert_equal x,bElem
|
|
}
|
|
end
|
|
|
|
def test_ticket_56
|
|
namespaces = {'h' => 'http://www.w3.org/1999/xhtml'}
|
|
|
|
finaldoc = REXML::Document.new(File.read(fixture_path('google.2.xml')))
|
|
|
|
column_headers = []
|
|
|
|
REXML::XPath.each(finaldoc, '//h:form[@action="ModifyCampaign"]//h:th',
|
|
namespaces) do |el|
|
|
node = REXML::XPath.first(el, 'h:a/text()', namespaces)
|
|
column_headers << (node ? node.value : nil)
|
|
end
|
|
column_headers.map! { |h| h.to_s.strip.chomp }
|
|
expected = ["", "", "Current Status", "Current Budget",
|
|
"Clicks", "Impr.", "CTR", "Avg. CPC", "Cost", "Conv. Rate",
|
|
"Cost/Conv."]
|
|
assert_equal( expected, column_headers )
|
|
end
|
|
|
|
|
|
def test_ticket_70
|
|
string = <<EOF
|
|
|
|
<mydoc>
|
|
|
|
<someelement attribute="1.10">Text1, text,
|
|
text</someelement>
|
|
|
|
<someelement attribute="1.11">Text2, text,
|
|
text</someelement>
|
|
|
|
|
|
</mydoc>
|
|
|
|
EOF
|
|
doc = Document.new string
|
|
assert_equal( 1, XPath.match( doc, "//someelement[contains(@attribute,'1.10')]" ).length )
|
|
end
|
|
|
|
def test_ticket_43
|
|
#url = http://news.search.yahoo.com/news/rss?p=market&ei=UTF-8&fl=0&x=wrt
|
|
|
|
sum = File.open(fixture_path("yahoo.xml")) do |f|
|
|
Document.new(f).elements.to_a("//item").size
|
|
end
|
|
assert_equal( 10, sum )
|
|
|
|
text = File.open(fixture_path("yahoo.xml")) do |f|
|
|
Document.new(f).elements.to_a(%Q{//title[contains(text(), "'")]}).collect{|e| e.text}.join
|
|
end
|
|
assert_equal( "Broward labor market's a solid performer (Miami Herald)", text )
|
|
end
|
|
|
|
def test_ticket_57
|
|
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>"
|
|
|
|
r = Document.new(data)
|
|
|
|
assert_equal(Text, REXML::XPath.first(r,"a:x/a:y[@p='p' and @q='q']/a:z/text()").class)
|
|
assert_equal("zzz", REXML::XPath.first(r,"a:x/a:y[@p='p' and @q='q']/a:z/text()").to_s)
|
|
end
|
|
|
|
def test_ticket_59
|
|
data = "<a>
|
|
<c id='1'/>
|
|
<c id='2'/>
|
|
<b>
|
|
<c id='3'/>
|
|
</b>
|
|
<c id='4'/>
|
|
<b>
|
|
<b>
|
|
<c id='5'/>
|
|
</b>
|
|
<c id='6'/>
|
|
</b>
|
|
<c id='7'/>
|
|
<b>
|
|
<b>
|
|
<c id='8'/>
|
|
<b>
|
|
<c id='9'/>
|
|
<b>
|
|
<c id='10'/>
|
|
</b>
|
|
<c id='11'/>
|
|
</b>
|
|
</b>
|
|
</b>
|
|
<c id='12'/>
|
|
</a>"
|
|
d = Document.new(data)
|
|
res = d.elements.to_a( "//c" ).collect {|e| e.attributes['id'].to_i}
|
|
assert_equal((1..12).to_a, res)
|
|
end
|
|
|
|
def ticket_61_fixture(doc, xpath)
|
|
matches = []
|
|
doc.elements.each(xpath) do |element|
|
|
matches << element
|
|
assert_equal('Add', element.text)
|
|
assert_equal('ButtonText', element.attributes['class'])
|
|
end
|
|
assert_equal(1, matches.length)
|
|
end
|
|
|
|
def test_ticket_61_text
|
|
doc = File.open(fixture_path("ticket_61.xml")) do |file|
|
|
REXML::Document.new file
|
|
end
|
|
ticket_61_fixture( doc, "//div[text()='Add' and @class='ButtonText']" )
|
|
end
|
|
|
|
def test_ticket_61_contains
|
|
doc = File.open(fixture_path("ticket_61.xml")) do |file|
|
|
REXML::Document.new file
|
|
end
|
|
ticket_61_fixture( doc, "//div[contains(.,'Add') and @class='ButtonText']" )
|
|
end
|
|
|
|
def test_namespaces_0
|
|
d = Document.new(%q{<x:a xmlns:x="y"/>})
|
|
assert_equal( 1, XPath.match( d, "//x:a" ).size )
|
|
assert_equal( 1, XPath.match( d, "//x:*" ).size )
|
|
end
|
|
|
|
def test_ticket_71
|
|
doc = Document.new(%Q{<root xmlns:ns1="xyz" xmlns:ns2="123"><element ns1:attrname="foo" ns2:attrname="bar"/></root>})
|
|
el = doc.root.elements[1]
|
|
assert_equal( "element", el.name )
|
|
el2 = XPath.first( doc.root, "element[@ns:attrname='foo']", { 'ns' => "xyz" } )
|
|
assert_equal( el, el2 )
|
|
end
|
|
|
|
def test_ticket_78
|
|
doc = <<-EOT
|
|
<root>
|
|
<element>
|
|
<tag x='1'>123</tag>
|
|
</element>
|
|
<element>
|
|
<tag x='2'>123a</tag>
|
|
</element>
|
|
</root>
|
|
EOT
|
|
seq = %w{BEGIN 123 END BEGIN 123a END}
|
|
|
|
xmlDoc = Document.new(doc)
|
|
|
|
["//element[tag='123']/tag", "//element[tag='123a']/tag"].each do |query|
|
|
assert_equal( "BEGIN", seq.shift )
|
|
XPath.each(xmlDoc, query) { |element|
|
|
assert_equal( seq.shift, element.text )
|
|
}
|
|
assert_equal( "END", seq.shift )
|
|
end
|
|
end
|
|
|
|
def test_ticket_79
|
|
source = "<a><b><c>test</c></b><b><c>3</c></b></a>"
|
|
d = REXML::Document.new( source )
|
|
r = REXML::XPath.match( d, %q{/a/b[c='test']} )
|
|
assert_equal(1, r.size())
|
|
r = REXML::XPath.match( d, %q{/a/b[c='3']} )
|
|
assert_equal(1, r.size())
|
|
end
|
|
|
|
def test_or_and
|
|
doc = "
|
|
<html>
|
|
<head>
|
|
<title>test</title>
|
|
</head>
|
|
<body>
|
|
<p>
|
|
A <a rel=\"sub\" href=\"/\">link</a>.
|
|
</p>
|
|
</body>
|
|
</html>
|
|
"
|
|
|
|
xmldoc = REXML::Document.new(doc)
|
|
xpath = "descendant::node()[(local-name()='link' or local-name()='a') and @rel='sub']"
|
|
hrefs = []
|
|
xmldoc.elements.each(xpath) do |element|
|
|
hrefs << element.attributes["href"]
|
|
end
|
|
assert_equal(["/"], hrefs, "Bug #3842 [ruby-core:32447]")
|
|
end
|
|
end
|
|
end
|