XPath compiler support for ancestor-or-self

This also comes with some changes to the specs as the old behaviour of
the Evaluator was incorrect. The Evaluator would bail after matching a
single node but instead it's meant to continue until it runs out of
parent nodes.
This commit is contained in:
Yorick Peterse 2015-07-17 23:24:18 +02:00
parent d8fbaf75d8
commit db39b25546
3 changed files with 37 additions and 2 deletions

View File

@ -27,6 +27,8 @@ module Oga
# @private
#
class Node < BasicObject
undef_method :!, :!=
# @return [Symbol]
attr_reader :type

View File

@ -16,7 +16,11 @@ module Oga
STAR = '*'
# Node types that require a NodeSet to push nodes into.
USE_NODESET = [:path, :absolute_path, :axis]
USE_NODESET = [:path, :absolute_path, :axis, :predicate]
# Node types that require "compile" to define a block to call upon
# matching a node.
ADD_PUSH_BLOCK = [:axis, :predicate]
##
# Compiles and caches an AST.
@ -37,7 +41,7 @@ module Oga
document = node_literal
matched = matched_literal
if ast.type == :axis
if ADD_PUSH_BLOCK.include?(ast.type)
ruby_ast = process(ast, document) { |node| matched.push(node) }
else
ruby_ast = process(ast, document)
@ -167,6 +171,30 @@ module Oga
input.get(string(query))
end
##
# Processes the `ancestor-or-self` axis.
#
# @param [AST::Node] ast
# @param [Oga::Ruby::Node] input
# @return [Oga::Ruby::Node]
#
def on_axis_ancestor_or_self(ast, input, &block)
parent_var = literal('parent')
assign = parent_var.assign(input)
has_parent = parent_var.respond_to?(symbol(:parent))
.and(parent_var.parent)
body = has_parent.while_true do
if_statement = process(ast, parent_var, &block).if_true do
yield parent_var
end
if_statement.followed_by(parent_var.assign(parent_var.parent))
end
assign.followed_by(body)
end
##
# Processes a node test predicate.
#

View File

@ -26,6 +26,11 @@ describe Oga::XPath::Compiler do
evaluate_xpath(@c1, 'ancestor-or-self::*[1]').should == node_set(@c1)
end
it 'returns a node set containing all ancestors' do
evaluate_xpath(@c1, 'ancestor-or-self::*')
.should == node_set(@c1, @b1, @a1)
end
it 'returns an empty node set for non existing ancestors' do
evaluate_xpath(@c1, 'ancestor-or-self::foo').should == node_set
end