Skip to content

Commit 524c847

Browse files
authored
Fix parsing parenthesized xpath (#322)
Fixes #316 Also fixes bug parsing `(1+2)*3` ```ruby REXML::Parsers::XPathParser.new.parse("(a|b)/c") #=> REXML::ParseException (before) #=> [:group, [:union, [:child, :qname, "", "a"], [:child, :qname, "", "b"]], :child, :qname, "", "c"] (after) REXML::Parsers::XPathParser.new.parse("(1+2) * 3") #=> [:mult, [:plus, [:literal, 1], [:literal, 2]], [:literal, 3]] REXML::Parsers::XPathParser.new.parse("(1+2)*3") #=> REXML::ParseException (before) #=> [:mult, [:plus, [:literal, 1], [:literal, 2]], [:literal, 3]] (after) ``` Source code comment: ```ruby #| LocationPath #| FilterExpr ('/' | '//') RelativeLocationPath ``` Actual implementation was: ```ruby #| LocationPath #| FilterExpr (RelativeLocationPath if this_part.start_with?('/')) #| FilterExpr (RelativeLocationPath if this_part.start_with?('/')) LocationPath #| FilterExpr LocationPath # Note that RelativeLocationPath never accepts string starting with `/` ``` With this pull request, the implementation will match to its source code comment.
1 parent a98066c commit 524c847

3 files changed

Lines changed: 52 additions & 5 deletions

File tree

lib/rexml/parsers/xpathparser.rb

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -587,14 +587,21 @@ def PathExpr path, parsed
587587
path = path.lstrip
588588
n = []
589589
rest = FilterExpr( path, n )
590-
if rest != path
591-
if rest and rest[0] == ?/
590+
if rest == path
591+
rest = LocationPath(rest, n)
592+
else
593+
if /\A\s*\//.match?(rest)
594+
rest = rest.lstrip
595+
if rest.start_with?('//')
596+
n << :descendant_or_self
597+
n << :node
598+
rest = rest[2..-1]
599+
else # starts with '/'
600+
rest = rest[1..-1]
601+
end
592602
rest = RelativeLocationPath(rest, n)
593-
parsed.concat(n)
594-
return rest
595603
end
596604
end
597-
rest = LocationPath(rest, n) if rest =~ /\A[\/\.\@\[\w*]/
598605
parsed.concat(n)
599606
rest
600607
end

test/parser/test_xpath.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,5 +129,18 @@ def test_spaces_between_tokens
129129
parser.parse('//processing-instruction( "a" )'),
130130
)
131131
end
132+
133+
def test_mult_asterisk_without_surrounding_spaces
134+
parser = REXML::Parsers::XPathParser.new
135+
assert_equal(parser.parse("1 * 2 * 3"), parser.parse("1*2*3"))
136+
assert_equal(parser.parse("a[( ( 1 + 2 ) * 3 + 4 * ( 5 + 6 ) ) * 7 < 8]"), parser.parse("a[((1+2)*3+4*(5+6))*7<8]"))
137+
# number(a/b) * 2
138+
assert_equal(parser.parse("(a/b) * 2"), parser.parse("(a/b)*2"))
139+
assert_equal(parser.parse("a/b * 2"), parser.parse("a/b*2"))
140+
# number(a/b/*) * 2
141+
assert_equal(parser.parse("a/b/* * 2"), parser.parse("a/b/**2"))
142+
# number(*) * number(*/*) * number(*)
143+
assert_equal(parser.parse("* * */* * *"), parser.parse("***/***"))
144+
end
132145
end
133146
end

test/xpath/test_base.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,33 @@ def test_nested_predicates
616616
assert_equal without_parentheses, with_parentheses
617617
end
618618

619+
def test_parenthesized_xpath
620+
doc = Document.new <<-EOF
621+
<div>
622+
<div id='1'>
623+
<test>ab</test>
624+
<div><test>cd</test></div>
625+
</div>
626+
<div id='2'>
627+
<test>ef</test>
628+
<div><test>gh</test></div>
629+
</div>
630+
<div>
631+
<test>hi</test>
632+
</div>
633+
</div>
634+
EOF
635+
parenthesized_xpath = '(//div[@id="1"] | //div[@id="2"])'
636+
assert_equal(%w[ab ef], XPath.match(doc, "#{parenthesized_xpath}/test").map(&:text))
637+
assert_equal(%w[ab ef], XPath.match(doc, "#{parenthesized_xpath} / test").map(&:text))
638+
assert_equal(%w[ab cd ef gh], XPath.match(doc, "#{parenthesized_xpath}//test").map(&:text))
639+
assert_equal(%w[ab cd ef gh], XPath.match(doc, "#{parenthesized_xpath} // test").map(&:text))
640+
assert_equal(%w[ab], XPath.match(doc, "#{parenthesized_xpath}[@id='1']/test").map(&:text))
641+
assert_equal(%w[ef gh], XPath.match(doc, "#{parenthesized_xpath}[@id='2']//test").map(&:text))
642+
assert_equal(%w[ef], XPath.match(doc, "#{parenthesized_xpath}[2] / test").map(&:text))
643+
assert_equal(%w[ab cd], XPath.match(doc, "#{parenthesized_xpath}[1] // test").map(&:text))
644+
end
645+
619646
# Contributed by Mike Stok
620647
def test_starts_with
621648
source = <<-EOF

0 commit comments

Comments
 (0)