[前][次][番号順一覧][スレッド一覧]

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/

[前][次][番号順一覧][スレッド一覧]