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 = " #