diff --git a/lib/oga/xpath/compiler.rb b/lib/oga/xpath/compiler.rb index 12dc638..422ae70 100644 --- a/lib/oga/xpath/compiler.rb +++ b/lib/oga/xpath/compiler.rb @@ -447,7 +447,7 @@ module Oga # @return [Oga::Ruby::Node] # def on_predicate_index(test, predicate, input) - int1 = literal('1') + int1 = literal(1) index = to_int(predicate) index_var = literal(:index) @@ -732,9 +732,9 @@ module Oga raise TypeError, 'count() can only operate on NodeSet instances' end - count.assign(literal('0.0')) + count.assign(literal(0.0)) .followed_by do - process(arg, input) { count.assign(count + literal('1')) } + process(arg, input) { count.assign(count + literal(1)) } end .followed_by(count) end @@ -1063,6 +1063,46 @@ module Oga end end + # @param [Oga::Ruby::Node] input + # @param [AST::Node] haystack + # @param [AST::Node] start + # @param [AST::Node] length + # @return [Oga::Ruby::Node] + def on_call_substring(input, haystack, start, length = nil) + haystack_var = unique_literal(:haystack) + start_var = unique_literal(:start) + stop_var = unique_literal(:stop) + length_var = unique_literal(:length) + conversion = literal(Conversion) + + haystack_var.assign(try_match_first_node(haystack, input)) + .followed_by do + start_var.assign(try_match_first_node(start, input)) + .followed_by do + start_var.assign(start_var - literal(1)) + end + end + .followed_by do + if length + length_var.assign(try_match_first_node(length, input)) + .followed_by do + length_int = conversion.to_float(length_var) + .to_i - literal(1) + + stop_var.assign(start_var + length_int) + end + else + stop_var.assign(literal(-1)) + end + end + .followed_by do + substring = conversion + .to_string(haystack_var)[range(start_var, stop_var)] + + block_given? ? substring.empty?.not.if_true { yield } : substring + end + end + ## # Delegates type tests to specific handlers. # @@ -1108,6 +1148,13 @@ module Oga Ruby::Node.new(:lit, [value.to_s]) end + # @param [Oga::Ruby::Node] start + # @param [Oga::Ruby::Node] stop + # @return [Oga::Ruby::Node] + def range(start, stop) + Ruby::Node.new(:range, [start, stop]) + end + # @param [String] name # @return [Oga::Ruby::Node] def unique_literal(name) diff --git a/spec/oga/xpath/compiler/calls/substring_spec.rb b/spec/oga/xpath/compiler/calls/substring_spec.rb index 4f2ea62..172e465 100644 --- a/spec/oga/xpath/compiler/calls/substring_spec.rb +++ b/spec/oga/xpath/compiler/calls/substring_spec.rb @@ -4,26 +4,37 @@ describe Oga::XPath::Compiler do describe 'substring() function' do before do @document = parse('foobar3') + + @a1 = @document.children[0].children[0] end - it 'returns the substring of a string' do - evaluate_xpath(@document, 'substring("foo", 2)').should == 'oo' + describe 'at the top-level' do + it 'returns the substring of a string' do + evaluate_xpath(@document, 'substring("foo", 2)').should == 'oo' + end + + it 'returns the substring of a string using a custom length' do + evaluate_xpath(@document, 'substring("foo", 2, 1)').should == 'o' + end + + it 'returns the substring of a node set' do + evaluate_xpath(@document, 'substring(root/a, 2)').should == 'oobar' + end + + it 'returns the substring of a node set with a node set as the length' do + evaluate_xpath(@document, 'substring(root/a, 1, root/b)').should == 'foo' + end + + it 'returns an empty string when the source string is empty' do + evaluate_xpath(@document, 'substring("", 1, 3)').should == '' + end end - it 'returns the substring of a string using a custom length' do - evaluate_xpath(@document, 'substring("foo", 2, 1)').should == 'o' - end - - it 'returns the substring of a node set' do - evaluate_xpath(@document, 'substring(root/a, 2)').should == 'oobar' - end - - it 'returns the substring of a node set with a node set as the length' do - evaluate_xpath(@document, 'substring(root/a, 1, root/b)').should == 'foo' - end - - it 'returns an empty string when the source string is empty' do - evaluate_xpath(@document, 'substring("", 1, 3)').should == '' + describe 'in a predicate' do + it 'returns a NodeSet containing all matching nodes' do + evaluate_xpath(@document, 'root/a[substring("foo", 1, 2)]') + .should == node_set(@a1) + end end end end