Support for the "following-sibling" axis.

This also comes with some small cleanups regarding
XPath::Evaluator#node_matches?. This change removes the need to, every time,
also use can_match_node?() to prevent NoMethodError errors from popping up.
This commit is contained in:
Yorick Peterse 2014-08-04 21:51:51 +02:00
parent 57c0f4b35e
commit a1f80b4995
3 changed files with 105 additions and 5 deletions

View File

@ -101,9 +101,7 @@ module Oga
nodes = XML::NodeSet.new
context.each do |xml_node|
if can_match_node?(xml_node) and node_matches?(xml_node, ast_node)
nodes << xml_node
end
nodes << xml_node if node_matches?(xml_node, ast_node)
end
return nodes
@ -144,7 +142,7 @@ module Oga
while has_parent?(xml_node)
xml_node = xml_node.parent
if can_match_node?(xml_node) and node_matches?(xml_node, ast_node)
if node_matches?(xml_node, ast_node)
nodes << xml_node
break
end
@ -265,8 +263,43 @@ module Oga
next unless check
if can_match_node?(doc_node) and node_matches?(doc_node, ast_node)
nodes << doc_node if node_matches?(doc_node, ast_node)
end
end
return nodes
end
##
# Evaluates the `following-sibling` axis.
#
# @param [Oga::XPath::Node] ast_node
# @param [Oga::XML::NodeSet] context
# @return [Oga::XML::NodeSet]
#
def on_axis_following_sibling(ast_node, context)
nodes = XML::NodeSet.new
context.each do |context_node|
check = false
parent = context_node.respond_to?(:parent) ? context_node.parent : nil
@document.each_node do |doc_node|
# Skip child nodes of the current context node, compare all
# following nodes.
if doc_node == context_node
check = true
throw :skip_children
end
if !check or parent != doc_node.parent
next
end
if node_matches?(doc_node, ast_node)
nodes << doc_node
throw :skip_children
end
end
end
@ -307,6 +340,8 @@ module Oga
# @return [Oga::XML::NodeSet]
#
def node_matches?(xml_node, ast_node)
return false unless can_match_node?(xml_node)
ns, name = *ast_node
name_matches = xml_node.name == name || name == '*'

View File

@ -0,0 +1,59 @@
require 'spec_helper'
describe Oga::XPath::Evaluator do
context 'following-sibling axis' do
before do
# Strip whitespace so it's easier to retrieve/compare elements.
@document = parse(<<-EOF.strip.gsub(/\s+/m, ''))
<root>
<foo>
<bar></bar>
<baz>
<baz></baz>
</baz>
</foo>
<baz></baz>
</root>
EOF
@first_baz = @document.children[0].children[0].children[1]
@second_baz = @first_baz.children[0]
@third_baz = @document.children[0].children[1]
@evaluator = described_class.new(@document)
end
# This should return an empty set since the document doesn't have any
# following nodes.
context 'using a document as the root' do
before do
@set = @evaluator.evaluate('following-sibling::foo')
end
it_behaves_like :empty_node_set
end
context 'matching nodes in the current context' do
before do
@set = @evaluator.evaluate('root/foo/following-sibling::baz')
end
it_behaves_like :node_set, :length => 1
example 'return the third <baz> node' do
@set[0].should == @third_baz
end
end
context 'matching nodes in other contexts' do
before do
@set = @evaluator.evaluate('root/foo/bar/following-sibling::baz')
end
it_behaves_like :node_set, :length => 1
example 'return the first <baz> node' do
@set[0].should == @first_baz
end
end
end
end

View File

@ -61,6 +61,12 @@ describe Oga::XPath::Evaluator do
example 'return true if a node is matched without having a namespace' do
@evaluator.node_matches?(@name_node, s(:test, '*', 'a')).should == true
end
example 'return false when trying to match an XML::Text instance' do
text = Oga::XML::Text.new(:text => 'Foobar')
@evaluator.node_matches?(text, s(:test, nil, 'a')).should == false
end
end
context '#can_match_node?' do