From 310a2a98601168aae8c071749b5cb572b55d5046 Mon Sep 17 00:00:00 2001 From: Kouhei Sutou Date: Sat, 25 May 2019 18:28:00 +0900 Subject: [ruby/rexml] xpath: add missing value conversions for equality and relational expressions GitHub: fix #18 Reported by Mirko Budszuhn. Thanks!!! https://github.com/ruby/rexml/commit/0dca2a2ba0 --- lib/rexml/xpath_parser.rb | 59 ++++++--- test/rexml/xpath/test_compare.rb | 256 ++++++++++++++++++++++++++++++++++++++ test/rexml/xpath/test_node_set.rb | 84 ------------- 3 files changed, 295 insertions(+), 104 deletions(-) create mode 100644 test/rexml/xpath/test_compare.rb delete mode 100644 test/rexml/xpath/test_node_set.rb diff --git a/lib/rexml/xpath_parser.rb b/lib/rexml/xpath_parser.rb index 8f107e5a2c..b989725403 100644 --- a/lib/rexml/xpath_parser.rb +++ b/lib/rexml/xpath_parser.rb @@ -864,32 +864,51 @@ module REXML # Else, convert to string # Else # Convert both to numbers and compare - set1 = unnode(set1) if set1.is_a?(Array) - set2 = unnode(set2) if set2.is_a?(Array) - s1 = Functions.string(set1) - s2 = Functions.string(set2) - if s1 == 'true' or s1 == 'false' or s2 == 'true' or s2 == 'false' - set1 = Functions::boolean( set1 ) - set2 = Functions::boolean( set2 ) + compare(set1, op, set2) + end + end + + def value_type(value) + case value + when true, false + :boolean + when Numeric + :number + when String + :string + else + raise "[BUG] Unexpected value type: <#{value.inspect}>" + end + end + + def normalize_compare_values(a, operator, b) + a_type = value_type(a) + b_type = value_type(b) + case operator + when :eq, :neq + if a_type == :boolean or b_type == :boolean + a = Functions.boolean(a) unless a_type == :boolean + b = Functions.boolean(b) unless b_type == :boolean + elsif a_type == :number or b_type == :number + a = Functions.number(a) unless a_type == :number + b = Functions.number(b) unless b_type == :number else - if op == :eq or op == :neq - if s1 =~ /^\d+(\.\d+)?$/ or s2 =~ /^\d+(\.\d+)?$/ - set1 = Functions::number( s1 ) - set2 = Functions::number( s2 ) - else - set1 = Functions::string( set1 ) - set2 = Functions::string( set2 ) - end - else - set1 = Functions::number( set1 ) - set2 = Functions::number( set2 ) - end + a = Functions.string(a) unless a_type == :string + b = Functions.string(b) unless b_type == :string end - compare( set1, op, set2 ) + when :lt, :lteq, :gt, :gteq + a = Functions.number(a) unless a_type == :number + b = Functions.number(b) unless b_type == :number + else + message = "[BUG] Unexpected compare operator: " + + "<#{operator.inspect}>: <#{a.inspect}>: <#{b.inspect}>" + raise message end + [a, b] end def compare(a, operator, b) + a, b = normalize_compare_values(a, operator, b) case operator when :eq a == b diff --git a/test/rexml/xpath/test_compare.rb b/test/rexml/xpath/test_compare.rb new file mode 100644 index 0000000000..bb666c9b12 --- /dev/null +++ b/test/rexml/xpath/test_compare.rb @@ -0,0 +1,256 @@ +# frozen_string_literal: false + +require_relative "../rexml_test_utils" + +require "rexml/document" + +module REXMLTests + class TestXPathCompare < Test::Unit::TestCase + def match(xml, xpath) + document = REXML::Document.new(xml) + REXML::XPath.match(document, xpath) + end + + class TestEqual < self + class TestNodeSet < self + def test_boolean_true + xml = <<-XML + + + + + + XML + assert_equal([true], + match(xml, "/root/child=true()")) + end + + def test_boolean_false + xml = <<-XML + + + + XML + assert_equal([false], + match(xml, "/root/child=true()")) + end + + def test_number_true + xml = <<-XML + + + 100 + 200 + + XML + assert_equal([true], + match(xml, "/root/child=100")) + end + + def test_number_false + xml = <<-XML + + + 100 + 200 + + XML + assert_equal([false], + match(xml, "/root/child=300")) + end + + def test_string_true + xml = <<-XML + + + text + string + + XML + assert_equal([true], + match(xml, "/root/child='string'")) + end + + def test_string_false + xml = <<-XML + + + text + string + + XML + assert_equal([false], + match(xml, "/root/child='nonexistent'")) + end + end + + class TestBoolean < self + def test_number_true + xml = "" + assert_equal([true], + match(xml, "true()=1")) + end + + def test_number_false + xml = "" + assert_equal([false], + match(xml, "true()=0")) + end + + def test_string_true + xml = "" + assert_equal([true], + match(xml, "true()='string'")) + end + + def test_string_false + xml = "" + assert_equal([false], + match(xml, "true()=''")) + end + end + + class TestNumber < self + def test_string_true + xml = "" + assert_equal([true], + match(xml, "1='1'")) + end + + def test_string_false + xml = "" + assert_equal([false], + match(xml, "1='2'")) + end + end + end + + class TestGreaterThan < self + class TestNodeSet < self + def test_boolean_truex + xml = <<-XML + + + + + XML + assert_equal([true], + match(xml, "/root/child>false()")) + end + + def test_boolean_false + xml = <<-XML + + + + + XML + assert_equal([false], + match(xml, "/root/child>true()")) + end + + def test_number_true + xml = <<-XML + + + 100 + 200 + + XML + assert_equal([true], + match(xml, "/root/child>199")) + end + + def test_number_false + xml = <<-XML + + + 100 + 200 + + XML + assert_equal([false], + match(xml, "/root/child>200")) + end + + def test_string_true + xml = <<-XML + + + 100 + 200 + + XML + assert_equal([true], + match(xml, "/root/child>'199'")) + end + + def test_string_false + xml = <<-XML + + + 100 + 200 + + XML + assert_equal([false], + match(xml, "/root/child>'200'")) + end + end + + class TestBoolean < self + def test_string_true + xml = "" + assert_equal([true], + match(xml, "true()>'0'")) + end + + def test_string_false + xml = "" + assert_equal([false], + match(xml, "true()>'1'")) + end + end + + class TestNumber < self + def test_boolean_true + xml = "" + assert_equal([true], + match(xml, "true()>0")) + end + + def test_number_false + xml = "" + assert_equal([false], + match(xml, "true()>1")) + end + + def test_string_true + xml = "" + assert_equal([true], + match(xml, "1>'0'")) + end + + def test_string_false + xml = "" + assert_equal([false], + match(xml, "1>'1'")) + end + end + + class TestString < self + def test_string_true + xml = "" + assert_equal([true], + match(xml, "'1'>'0'")) + end + + def test_string_false + xml = "" + assert_equal([false], + match(xml, "'1'>'1'")) + end + end + end + end +end diff --git a/test/rexml/xpath/test_node_set.rb b/test/rexml/xpath/test_node_set.rb deleted file mode 100644 index 77f26f7a6d..0000000000 --- a/test/rexml/xpath/test_node_set.rb +++ /dev/null @@ -1,84 +0,0 @@ -# frozen_string_literal: false - -require_relative "../rexml_test_utils" - -require "rexml/document" - -module REXMLTests - class TestXPathNodeSet < Test::Unit::TestCase - def match(xml, xpath) - document = REXML::Document.new(xml) - REXML::XPath.match(document, xpath) - end - - def test_boolean_true - xml = <<-XML - - - - - - XML - assert_equal([true], - match(xml, "/root/child=true()")) - end - - def test_boolean_false - xml = <<-XML - - - - XML - assert_equal([false], - match(xml, "/root/child=true()")) - end - - def test_number_true - xml = <<-XML - - - 100 - 200 - - XML - assert_equal([true], - match(xml, "/root/child=100")) - end - - def test_number_false - xml = <<-XML - - - 100 - 200 - - XML - assert_equal([false], - match(xml, "/root/child=300")) - end - - def test_string_true - xml = <<-XML - - - text - string - - XML - assert_equal([true], - match(xml, "/root/child='string'")) - end - - def test_string_false - xml = <<-XML - - - text - string - - XML - assert_equal([false], - match(xml, "/root/child='nonexistent'")) - end - end -end -- cgit v1.2.3