diff --git a/lib/oga/xpath/evaluator.rb b/lib/oga/xpath/evaluator.rb index 83f5f22..7b43cf7 100644 --- a/lib/oga/xpath/evaluator.rb +++ b/lib/oga/xpath/evaluator.rb @@ -706,6 +706,42 @@ module Oga return on_call_number(context, left) - on_call_number(context, right) end + ## + # Processes the `=` operator. + # + # This operator evaluates the expression on the left and right and returns + # `true` if they are equal. This operator can be used to compare strings, + # numbers and node sets. When using node sets the text of the set is + # compared instead of the nodes themselves. That is, nodes with different + # names but the same text are considered to be equal. + # + # @param [Oga::XPath::Node] ast_node + # @param [Oga::XML::NodeSet] context + # @return [TrueClass|FalseClass] + # + def on_eq(ast_node, context) + left = process(ast_node.children[0], context) + right = process(ast_node.children[1], context) + + if left.is_a?(XML::NodeSet) + left = left.text + end + + if right.is_a?(XML::NodeSet) + right = right.text + end + + if left.is_a?(Numeric) and !right.is_a?(Numeric) + right = to_float(right) + end + + if left.is_a?(String) and !right.is_a?(String) + right = to_string(right) + end + + return left == right + end + ## # Delegates function calls to specific handlers. # @@ -903,14 +939,7 @@ module Oga if convert.respond_to?(:text) return convert.text else - # If we have a number that has a zero decimal (e.g. 10.0) we want to - # get rid of that decimal. For this we'll first convert the number to - # an integer. - if convert.is_a?(Float) and convert.modulo(1).zero? - convert = convert.to_i - end - - return convert.to_s + return to_string(convert) end end @@ -950,7 +979,7 @@ module Oga convert = current_node.text end - return Float(convert) rescue Float::NAN + return to_float(convert) end ## @@ -1528,6 +1557,35 @@ module Oga return ast_node.respond_to?(:parent) && !!ast_node.parent end + ## + # Converts the given value to a float. If the value can't be converted to + # a float NaN is returned instead. + # + # @param [Mixed] value + # @return [Float] + # + def to_float(value) + return Float(value) rescue Float::NAN + end + + ## + # Converts the given value to a string according to the XPath string + # conversion rules. + # + # @param [Mixed] value + # @return [String] + # + def to_string(value) + # If we have a number that has a zero decimal (e.g. 10.0) we want to + # get rid of that decimal. For this we'll first convert the number to + # an integer. + if value.is_a?(Float) and value.modulo(1).zero? + value = value.to_i + end + + return value.to_s + end + ## # Stores the node in the node stack, yields the block and removes the node # from the stack. diff --git a/spec/oga/xpath/evaluator/general_spec.rb b/spec/oga/xpath/evaluator/general_spec.rb index 0842a98..fb3e393 100644 --- a/spec/oga/xpath/evaluator/general_spec.rb +++ b/spec/oga/xpath/evaluator/general_spec.rb @@ -154,4 +154,24 @@ describe Oga::XPath::Evaluator do @evaluator.has_parent?(@parent).should == false end end + + context '#to_string' do + example 'convert a float to a string' do + @evaluator.to_string(10.5).should == '10.5' + end + + example 'convert a float without decimals to a string' do + @evaluator.to_string(10.0).should == '10' + end + end + + context '#to_float' do + example 'convert a string to a float' do + @evaluator.to_float('10').should == 10.0 + end + + example "return NaN for values that can't be converted to floats" do + @evaluator.to_float('a').should be_nan + end + end end diff --git a/spec/oga/xpath/evaluator/operators/eq_spec.rb b/spec/oga/xpath/evaluator/operators/eq_spec.rb new file mode 100644 index 0000000..9d46f1d --- /dev/null +++ b/spec/oga/xpath/evaluator/operators/eq_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper' + +describe Oga::XPath::Evaluator do + context 'equal operator' do + before do + @document = parse('1010') + @evaluator = described_class.new(@document) + end + + example 'return true if two numbers are equal' do + @evaluator.evaluate('10 = 10').should == true + end + + example 'return false if two numbers are not equal' do + @evaluator.evaluate('10 = 15').should == false + end + + example 'return true if a number and a string are equal' do + @evaluator.evaluate('10 = "10"').should == true + end + + example 'return true if two strings are equal' do + @evaluator.evaluate('"10" = "10"').should == true + end + + example 'return true if a string and a number are equal' do + @evaluator.evaluate('"10" = 10').should == true + end + + example 'return false if two strings are not equal' do + @evaluator.evaluate('"a" = "b"').should == false + end + + example 'return true if two node sets are equal' do + @evaluator.evaluate('root/a = root/b').should == true + end + + example 'return false if two node sets are not equal' do + @evaluator.evaluate('root/a = root/c').should == false + end + + example 'return true if a node set and a number are equal' do + @evaluator.evaluate('root/a = 10').should == true + end + + example 'return true if a node set and a string are equal' do + @evaluator.evaluate('root/a = "10"').should == true + end + end +end