ruby-changes:51029
From: kou <ko1@a...>
Date: Sun, 22 Apr 2018 17:09:15 +0900 (JST)
Subject: [ruby-changes:51029] kou:r63236 (trunk): rexml: Fix XPath bug of /#{ELEMENT_NAME}
kou 2018-04-22 17:09:04 +0900 (Sun, 22 Apr 2018) New Revision: 63236 https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=63236 Log: rexml: Fix XPath bug of /#{ELEMENT_NAME} It doesn't mean that all elements which name "ELEMENT_NAME" with any namespace URI including null namespace URI. It means that all elements which name "ELEMENT_NAME" with null namespace URI. https://www.w3.org/TR/1999/REC-xpath-19991116/#NT-NodeTest > if the QName does not have a prefix, then the namespace URI is null > (this is the same way attribute names are expanded). We need to use "*[local-name()='#{ELEMENT_NAME}']" for all elements which name "ELEMENT_NAME" with any namespace URI including null namespace URI in XPath 1.0. But it's inconvenient. So this change includes "*:#{LOCAL_NAME}" syntax support that is introduced since XPath 2.0. * lib/rexml/parsers/xpathparser.rb: Support "*:#{LOCAL_NAME}" syntax that is introduced since XPath 2.0. * lib/rexml/xpath_parser.rb: * Fix namespace URI processing for "#{ELEMENT_NAME}". Now, "#{ELEMENT_NAME}" doesn't accept elements with null namespace URI. * Add "*:#{LOCAL_NAME}" support. * test/rexml/test_contrib.rb, test/rexml/test_core.rb, test/rexml/xpath/test_base.rb: Follow this change. * test/rexml/test_jaxen.rb: Fix namespace processing. Modified files: trunk/lib/rexml/parsers/xpathparser.rb trunk/lib/rexml/xpath_parser.rb trunk/test/rexml/test_contrib.rb trunk/test/rexml/test_core.rb trunk/test/rexml/test_jaxen.rb trunk/test/rexml/xpath/test_base.rb Index: test/rexml/test_contrib.rb =================================================================== --- test/rexml/test_contrib.rb (revision 63235) +++ test/rexml/test_contrib.rb (revision 63236) @@ -451,7 +451,7 @@ EOL https://github.com/ruby/ruby/blob/trunk/test/rexml/test_contrib.rb#L451 end def test_external_entity - xp = '//channel/title' + xp = '//*:channel/*:title' %w{working.rss broken.rss}.each do |path| File.open(File.join(fixture_path(path))) do |file| Index: test/rexml/test_jaxen.rb =================================================================== --- test/rexml/test_jaxen.rb (revision 63235) +++ test/rexml/test_jaxen.rb (revision 63236) @@ -56,6 +56,8 @@ module REXMLTests https://github.com/ruby/ruby/blob/trunk/test/rexml/test_jaxen.rb#L56 def process_context(doc, context) test_context = XPath.match(doc, context.attributes["select"]) namespaces = context.namespaces + namespaces.delete("var") + namespaces = nil if namespaces.empty? variables = {} var_namespace = "http://jaxen.org/test-harness/var" XPath.each(context, Index: test/rexml/xpath/test_base.rb =================================================================== --- test/rexml/xpath/test_base.rb (revision 63235) +++ test/rexml/xpath/test_base.rb (revision 63236) @@ -877,6 +877,7 @@ module REXMLTests https://github.com/ruby/ruby/blob/trunk/test/rexml/xpath/test_base.rb#L877 <tag1 xmlns='ns1'> <tag2 xmlns='ns2'/> <tada>xa</tada> + <tada xmlns=''>xb</tada> </tag1> XML x = d.root Index: test/rexml/test_core.rb =================================================================== --- test/rexml/test_core.rb (revision 63235) +++ test/rexml/test_core.rb (revision 63236) @@ -877,18 +877,18 @@ EOL https://github.com/ruby/ruby/blob/trunk/test/rexml/test_core.rb#L877 EOL # The most common case. People not caring about the namespaces much. - assert_equal( "XY", XPath.match( doc, "/test/a/text()" ).join ) - assert_equal( "XY", XPath.match( doc, "/test/x:a/text()" ).join ) + assert_equal( "XY", XPath.match( doc, "/*:test/*:a/text()" ).join ) + assert_equal( "XY", XPath.match( doc, "/*:test/x:a/text()" ).join ) # Surprising? I don't think so, if you believe my definition of the "common case" - assert_equal( "XYZ", XPath.match( doc, "//a/text()" ).join ) + assert_equal( "XYZ", XPath.match( doc, "//*:a/text()" ).join ) # These are the uncommon cases. Namespaces are actually important, so we define our own # mappings, and pass them in. assert_equal( "XY", XPath.match( doc, "/f:test/f:a/text()", { "f" => "1" } ).join ) # The namespaces are defined, and override the original mappings - assert_equal( "", XPath.match( doc, "/test/a/text()", { "f" => "1" } ).join ) + assert_equal( "XY", XPath.match( doc, "/*:test/*:a/text()", { "f" => "1" } ).join ) assert_equal( "", XPath.match( doc, "/x:test/x:a/text()", { "f" => "1" } ).join ) - assert_equal( "", XPath.match( doc, "//a/text()", { "f" => "1" } ).join ) + assert_equal( "XYZ", XPath.match( doc, "//*:a/text()", { "f" => "1" } ).join ) end def test_processing_instruction @@ -1390,8 +1390,8 @@ ENDXML https://github.com/ruby/ruby/blob/trunk/test/rexml/test_core.rb#L1390 def test_ticket_102 doc = REXML::Document.new '<doc xmlns="ns"><item name="foo"/></doc>' - assert_equal( "foo", doc.root.elements["item"].attribute("name","ns").to_s ) - assert_equal( "item", doc.root.elements["item[@name='foo']"].name ) + assert_equal( "foo", doc.root.elements["*:item"].attribute("name","ns").to_s ) + assert_equal( "item", doc.root.elements["*:item[@name='foo']"].name ) end def test_ticket_14 @@ -1420,11 +1420,11 @@ ENDXML https://github.com/ruby/ruby/blob/trunk/test/rexml/test_core.rb#L1420 doc = REXML::Document.new( '<doc xmlns="ns" xmlns:phantom="ns"><item name="foo">text</item></doc>' ) - assert_equal 'text', doc.text( "/doc/item[@name='foo']" ) + assert_equal 'text', doc.text( "/*:doc/*:item[@name='foo']" ) assert_equal "name='foo'", - doc.root.elements["item"].attribute("name", "ns").inspect + doc.root.elements["*:item"].attribute("name", "ns").inspect assert_equal "<item name='foo'>text</item>", - doc.root.elements["item[@name='foo']"].to_s + doc.root.elements["*:item[@name='foo']"].to_s end def test_ticket_135 Index: lib/rexml/xpath_parser.rb =================================================================== --- lib/rexml/xpath_parser.rb (revision 63235) +++ lib/rexml/xpath_parser.rb (revision 63236) @@ -169,16 +169,20 @@ module REXML https://github.com/ruby/ruby/blob/trunk/lib/rexml/xpath_parser.rb#L169 prefix = path_stack.shift name = path_stack.shift # enter(:qname, path_stack, prefix, name, nodeset) - nodeset.delete_if do |node| - # FIXME: This DOUBLES the time XPath searches take - ns = get_namespace( node, prefix ) + nodeset.select! do |node| if node.node_type == :element - if node.name == name + if prefix.nil? + node.name == name + elsif prefix.empty? + node.name == name and node.namespace == "" + else + node.name == name and + # FIXME: This DOUBLES the time XPath searches take + node.namespace == get_namespace(node, prefix) end + else + false end - !(node.node_type == :element and - node.name == name and - node.namespace == ns ) end # leave(:qname, path_stack, nodeset) node_types = ELEMENTS Index: lib/rexml/parsers/xpathparser.rb =================================================================== --- lib/rexml/parsers/xpathparser.rb (revision 63235) +++ lib/rexml/parsers/xpathparser.rb (revision 63236) @@ -271,10 +271,12 @@ module REXML https://github.com/ruby/ruby/blob/trunk/lib/rexml/parsers/xpathparser.rb#L271 # String, if a name match #NodeTest # | ('*' | NCNAME ':' '*' | QNAME) NameTest - # | NODE_TYPE '(' ')' NodeType + # | '*' ':' NCNAME NameTest since XPath 2.0 + # | NODE_TYPE '(' ')' NodeType # | PI '(' LITERAL ')' PI # | '[' expr ']' Predicate - NCNAMETEST= /^(#{NCNAME_STR}):\*/u + PREFIX_WILDCARD = /^\*:(#{NCNAME_STR})/u + LOCAL_NAME_WILDCARD = /^(#{NCNAME_STR}):\*/u QNAME = Namespace::NAMESPLIT NODE_TYPE = /^(comment|text|node)\(\s*\)/m PI = /^processing-instruction\(/ @@ -282,6 +284,13 @@ module REXML https://github.com/ruby/ruby/blob/trunk/lib/rexml/parsers/xpathparser.rb#L284 original_path = path path = path.lstrip case path + when PREFIX_WILDCARD + prefix = nil + name = $1 + path = $' + parsed << :qname + parsed << prefix + parsed << name when /^\*/ path = $' parsed << :any @@ -301,7 +310,7 @@ module REXML https://github.com/ruby/ruby/blob/trunk/lib/rexml/parsers/xpathparser.rb#L310 end parsed << :processing_instruction parsed << (literal || '') - when NCNAMETEST + when LOCAL_NAME_WILDCARD prefix = $1 path = $' parsed << :namespace -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/