diff --git a/lib/oga/xpath/evaluator.rb b/lib/oga/xpath/evaluator.rb index df1eef7..12f871f 100644 --- a/lib/oga/xpath/evaluator.rb +++ b/lib/oga/xpath/evaluator.rb @@ -99,6 +99,8 @@ module Oga def on_absolute_path(ast_node, context) if @document.respond_to?(:root_node) context = XML::NodeSet.new([@document.root_node]) + else + context = XML::NodeSet.new([@document]) end return on_path(ast_node, context) @@ -651,6 +653,46 @@ module Oga return retval.length.to_f end + ## + # Processes the `id()` function call. + # + # The XPath specification states that this function's behaviour should be + # controlled by a DTD. If a DTD were to specify that the ID attribute for + # a certain element would be "foo" then this function should use said + # attribute. + # + # Oga does not support DTD parsing/evaluation and as such always uses the + # "id" attribute. + # + # This function searches the entire document for a matching node, + # regardless of the current position. + # + # @param [Oga::XML::NodeSet] context + # @param [Oga::XPath::Node] expression + # @return [Oga::XML::NodeSet] + # + def on_call_id(context, expression) + id = process(expression, context) + nodes = XML::NodeSet.new + + # Based on Nokogiri's/libxml behaviour it appears that when using a node + # set the text of the set is used as the ID. + id = id.is_a?(XML::NodeSet) ? id.text : id.to_s + ids = id.split(' ') + + @document.each_node do |node| + next unless node.is_a?(XML::Element) + + attr = node.attribute('id') + + if attr and ids.include?(attr.value) + nodes << node + end + end + + return nodes + end + ## # Processes an `(int)` node. This method simply returns the value as a # Float. @@ -663,6 +705,17 @@ module Oga return ast_node.children[0].to_f end + ## + # Processes a `(string)` node. + # + # @param [Oga::XPath::Node] ast_node + # @param [Oga::XML::NodeSet] context + # @return [String] + # + def on_string(ast_node, context) + return ast_node.children[0] + end + ## # Returns a node set containing all the child nodes of the given set of # nodes. diff --git a/spec/oga/xpath/evaluator/calls/id_spec.rb b/spec/oga/xpath/evaluator/calls/id_spec.rb new file mode 100644 index 0000000..7f17b75 --- /dev/null +++ b/spec/oga/xpath/evaluator/calls/id_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' + +describe Oga::XPath::Evaluator do + context 'id() function' do + before do + @document = parse('a1') + @first_a = @document.children[0].children[0] + @second_a = @document.children[0].children[1] + @evaluator = described_class.new(@document) + end + + context 'using a single string ID' do + before do + @set = @evaluator.evaluate('id("a1")') + end + + it_behaves_like :node_set, :length => 1 + + example 'return the first node' do + @set[0].should == @first_a + end + end + + context 'using multiple string IDs' do + before do + @set = @evaluator.evaluate('id("a1 a2")') + end + + it_behaves_like :node_set, :length => 2 + + example 'return the first node' do + @set[0].should == @first_a + end + + example 'return the second node' do + @set[1].should == @second_a + end + end + + context 'using a node set' do + before do + @set = @evaluator.evaluate('id(root/a[2])') + end + + it_behaves_like :node_set, :length => 1 + + example 'return the first node' do + @set[0].should == @first_a + end + end + end +end diff --git a/spec/oga/xpath/evaluator/types/string_spec.rb b/spec/oga/xpath/evaluator/types/string_spec.rb new file mode 100644 index 0000000..f310350 --- /dev/null +++ b/spec/oga/xpath/evaluator/types/string_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe Oga::XPath::Evaluator do + context 'string types' do + before do + document = parse('') + evaluator = described_class.new(document) + @string = evaluator.evaluate('"foo"') + end + + example 'return the literal string' do + @string.should == 'foo' + end + end +end