diff --git a/lib/rexml/parsers/xpathparser.rb b/lib/rexml/parsers/xpathparser.rb
index a6d76fdc..de260da3 100644
--- a/lib/rexml/parsers/xpathparser.rb
+++ b/lib/rexml/parsers/xpathparser.rb
@@ -19,9 +19,6 @@ def namespaces=( namespaces )
end
def parse path
- path = path.dup
- path.gsub!(/([\(\[])\s+/, '\1') # Strip ignorable spaces
- path.gsub!( /\s+([\]\)])/, '\1')
parsed = []
rest = OrExpr(path, parsed)
if rest
@@ -359,17 +356,15 @@ def NodeTest path, parsed
path = $'
parsed << type.tr('-', '_').intern
when PI
- path = $'
+ path = $'.lstrip
literal = nil
- if path =~ /^\s*\)/
- path = $'
- else
+ unless path.start_with?(')')
path =~ LITERAL
literal = $1
- path = $'
+ path = $'.lstrip
raise ParseException.new("Missing ')' after processing instruction") if path[0] != ?)
- path = path[1..-1]
end
+ path = path[1..-1]
parsed << :processing_instruction
parsed << (literal || '')
when LOCAL_NAME_WILDCARD
@@ -400,6 +395,7 @@ def Predicate path, parsed
while path[0] == ?[
path, expr = get_group(path)
predicates << expr[1..-2] if expr
+ path = path.lstrip
end
predicates.each{ |pred|
preds = []
@@ -678,12 +674,20 @@ def get_group string
depth = 0
st = string[0,1]
en = (st == "(" ? ")" : "]")
+ quote = nil
begin
- case string[ind,1]
- when st
- depth += 1
- when en
- depth -= 1
+ if quote
+ # ignore () [] inside quotes
+ quote = nil if string[ind] == quote
+ else
+ case string[ind]
+ when st
+ depth += 1
+ when en
+ depth -= 1
+ when '"', "'"
+ quote = string[ind]
+ end
end
ind += 1
end while depth > 0 and ind < string.length
diff --git a/test/xpath/test_base.rb b/test/xpath/test_base.rb
index 1c6eb624..f8a50b28 100644
--- a/test/xpath/test_base.rb
+++ b/test/xpath/test_base.rb
@@ -803,6 +803,42 @@ def test_spaces
match.call('/ a / child:: c [( @id )] /'))
end
+ def test_space_inside_xpath
+ # REXML doesn't support space between function-name and opening paren.
+ # Spaces after `(` and spaces around `)`, `[`, `]` are tested here.
+ parser = Parsers::XPathParser.new
+ assert_equal(
+ parser.parse('//a/b[c][d]/e'),
+ parser.parse(' // a / b [ c ] [ d ] / e '),
+ )
+ assert_equal(
+ parser.parse('/a/b[string-length("1")<(2+3)]/c'),
+ parser.parse(' / a / b [ string-length( "1" ) < ( 2 + 3 ) ] / c '),
+ )
+ assert_equal(
+ parser.parse('//processing-instruction("a")'),
+ parser.parse('//processing-instruction( "a" )'),
+ )
+ end
+
+ def test_space_paren_brace_inside_xpath_string
+ doc = Document.new(<<~XML)
+
+
+
+
+ XML
+
+ assert_equal(
+ [" [ ' 1 ) "],
+ REXML::XPath.match(doc, "/a/b[@id=\" [ ' 1 ) \"]").map { |e| e.attributes['id'] }
+ )
+ assert_equal(
+ [' ( " 2 ] '],
+ REXML::XPath.match(doc, "/a/b[@id=' ( \" 2 ] ']").map { |e| e.attributes['id'] }
+ )
+ end
+
def test_text_nodes
# source = "
#