From d34e4697de9fd38c9ea86622a5bf1ef44d5bb194 Mon Sep 17 00:00:00 2001
From: Yorick Peterse <yorickpeterse@gmail.com>
Date: Thu, 14 Aug 2014 22:54:19 +0200
Subject: [PATCH] Match node types in node_matches?

The method XPath::Evaluator#node_matches? now has a special case to handle
"type-test" nodes. This in turn fixes a bunch of failing tests such as those for
the XPath query "parent::node()".
---
 lib/oga/xpath/evaluator.rb               | 26 ++++++++++++++++++++++--
 spec/oga/xpath/evaluator/general_spec.rb | 19 +++++++++++++++++
 2 files changed, 43 insertions(+), 2 deletions(-)

diff --git a/lib/oga/xpath/evaluator.rb b/lib/oga/xpath/evaluator.rb
index 730c27e..0ba6968 100644
--- a/lib/oga/xpath/evaluator.rb
+++ b/lib/oga/xpath/evaluator.rb
@@ -476,12 +476,19 @@ module Oga
       # Checks if a given {Oga::XML::Node} instance matches a {Oga::XPath::Node}
       # instance.
       #
-      # Checking if a node matches happens in two steps:
+      # This method can use both "test" and "type-test" nodes. In case of
+      # "type-test" nodes the procedure is as following:
+      #
+      # 1. Evaluate the expression
+      # 2. If the return value is non empty return `true`, otherwise return
+      #    `false`
+      #
+      # For "test" nodes the procedure is as following instead:
       #
       # 1. Match the name
       # 2. Match the namespace
       #
-      # In both cases a star (`*`) can be used as a wildcard.
+      # For both the name and namespace a wildcard (`*`) can be used.
       #
       # @param [Oga::XML::Node] xml_node
       # @param [Oga::XPath::Node] ast_node
@@ -490,6 +497,10 @@ module Oga
       def node_matches?(xml_node, ast_node)
         ns, name = *ast_node
 
+        if ast_node.type == :type_test
+          return type_matches?(xml_node, ast_node)
+        end
+
         # If only the name is given and is a wildcard then we'll also want to
         # match the namespace as a wildcard.
         if !ns and name == '*'
@@ -509,6 +520,17 @@ module Oga
         return name_matches && ns_matches
       end
 
+      ##
+      # @param [Oga::XML::Node] xml_node
+      # @param [Oga::XPath::Node] ast_node
+      # @return [TrueClass|FalseClass]
+      #
+      def type_matches?(xml_node, ast_node)
+        context = XML::NodeSet.new([xml_node])
+
+        return process(ast_node, context).length > 0
+      end
+
       ##
       # Returns `true` if the name of the XML node matches the given name *or*
       # matches a wildcard.
diff --git a/spec/oga/xpath/evaluator/general_spec.rb b/spec/oga/xpath/evaluator/general_spec.rb
index 382caf0..e4ed0f6 100644
--- a/spec/oga/xpath/evaluator/general_spec.rb
+++ b/spec/oga/xpath/evaluator/general_spec.rb
@@ -77,6 +77,25 @@ describe Oga::XPath::Evaluator do
     example 'return true if a node with a namespace is matched using a wildcard' do
       @evaluator.node_matches?(@name_ns_node, s(:test, nil, '*')).should == true
     end
+
+    example 'return true if the node type matches' do
+      @evaluator.node_matches?(@name_node, s(:type_test, 'node')).should == true
+    end
+  end
+
+  context '#type_matches?' do
+    before do
+      @element = Oga::XML::Element.new(:name => 'a')
+      @ns      = Oga::XML::Namespace.new(:name => 'a')
+    end
+
+    example 'return true if the type matches' do
+      @evaluator.type_matches?(@element, s(:type_test, 'node')).should == true
+    end
+
+    example 'return false if the type does not match' do
+      @evaluator.type_matches?(@ns, s(:type_test, 'node')).should == false
+    end
   end
 
   context '#has_parent?' do