summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKouhei Sutou <kou@clear-code.com>2019-05-25 18:28:00 +0900
committerHiroshi SHIBATA <hsbt@ruby-lang.org>2019-08-04 11:55:55 +0900
commit310a2a98601168aae8c071749b5cb572b55d5046 (patch)
tree16b3e9ba7d8ca26e389eec34f91eda549810df86
parent39f275edf7284ef0c0f9b9391038ae9f2c019731 (diff)
[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
-rw-r--r--lib/rexml/xpath_parser.rb59
-rw-r--r--test/rexml/xpath/test_compare.rb256
-rw-r--r--test/rexml/xpath/test_node_set.rb84
3 files changed, 295 insertions, 104 deletions
diff --git a/lib/rexml/xpath_parser.rb b/lib/rexml/xpath_parser.rb
index 8f107e5a2c1..b9897254032 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 00000000000..bb666c9b12c
--- /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 version="1.0" encoding="UTF-8"?>
+<root>
+ <child/>
+ <child/>
+</root>
+ XML
+ assert_equal([true],
+ match(xml, "/root/child=true()"))
+ end
+
+ def test_boolean_false
+ xml = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+</root>
+ XML
+ assert_equal([false],
+ match(xml, "/root/child=true()"))
+ end
+
+ def test_number_true
+ xml = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+ <child>100</child>
+ <child>200</child>
+</root>
+ XML
+ assert_equal([true],
+ match(xml, "/root/child=100"))
+ end
+
+ def test_number_false
+ xml = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+ <child>100</child>
+ <child>200</child>
+</root>
+ XML
+ assert_equal([false],
+ match(xml, "/root/child=300"))
+ end
+
+ def test_string_true
+ xml = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+ <child>text</child>
+ <child>string</child>
+</root>
+ XML
+ assert_equal([true],
+ match(xml, "/root/child='string'"))
+ end
+
+ def test_string_false
+ xml = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+ <child>text</child>
+ <child>string</child>
+</root>
+ XML
+ assert_equal([false],
+ match(xml, "/root/child='nonexistent'"))
+ end
+ end
+
+ class TestBoolean < self
+ def test_number_true
+ xml = "<root/>"
+ assert_equal([true],
+ match(xml, "true()=1"))
+ end
+
+ def test_number_false
+ xml = "<root/>"
+ assert_equal([false],
+ match(xml, "true()=0"))
+ end
+
+ def test_string_true
+ xml = "<root/>"
+ assert_equal([true],
+ match(xml, "true()='string'"))
+ end
+
+ def test_string_false
+ xml = "<root/>"
+ assert_equal([false],
+ match(xml, "true()=''"))
+ end
+ end
+
+ class TestNumber < self
+ def test_string_true
+ xml = "<root/>"
+ assert_equal([true],
+ match(xml, "1='1'"))
+ end
+
+ def test_string_false
+ xml = "<root/>"
+ assert_equal([false],
+ match(xml, "1='2'"))
+ end
+ end
+ end
+
+ class TestGreaterThan < self
+ class TestNodeSet < self
+ def test_boolean_truex
+ xml = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+ <child/>
+</root>
+ XML
+ assert_equal([true],
+ match(xml, "/root/child>false()"))
+ end
+
+ def test_boolean_false
+ xml = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+ <child/>
+</root>
+ XML
+ assert_equal([false],
+ match(xml, "/root/child>true()"))
+ end
+
+ def test_number_true
+ xml = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+ <child>100</child>
+ <child>200</child>
+</root>
+ XML
+ assert_equal([true],
+ match(xml, "/root/child>199"))
+ end
+
+ def test_number_false
+ xml = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+ <child>100</child>
+ <child>200</child>
+</root>
+ XML
+ assert_equal([false],
+ match(xml, "/root/child>200"))
+ end
+
+ def test_string_true
+ xml = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+ <child>100</child>
+ <child>200</child>
+</root>
+ XML
+ assert_equal([true],
+ match(xml, "/root/child>'199'"))
+ end
+
+ def test_string_false
+ xml = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+ <child>100</child>
+ <child>200</child>
+</root>
+ XML
+ assert_equal([false],
+ match(xml, "/root/child>'200'"))
+ end
+ end
+
+ class TestBoolean < self
+ def test_string_true
+ xml = "<root/>"
+ assert_equal([true],
+ match(xml, "true()>'0'"))
+ end
+
+ def test_string_false
+ xml = "<root/>"
+ assert_equal([false],
+ match(xml, "true()>'1'"))
+ end
+ end
+
+ class TestNumber < self
+ def test_boolean_true
+ xml = "<root/>"
+ assert_equal([true],
+ match(xml, "true()>0"))
+ end
+
+ def test_number_false
+ xml = "<root/>"
+ assert_equal([false],
+ match(xml, "true()>1"))
+ end
+
+ def test_string_true
+ xml = "<root/>"
+ assert_equal([true],
+ match(xml, "1>'0'"))
+ end
+
+ def test_string_false
+ xml = "<root/>"
+ assert_equal([false],
+ match(xml, "1>'1'"))
+ end
+ end
+
+ class TestString < self
+ def test_string_true
+ xml = "<root/>"
+ assert_equal([true],
+ match(xml, "'1'>'0'"))
+ end
+
+ def test_string_false
+ xml = "<root/>"
+ 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 77f26f7a6dc..00000000000
--- 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 version="1.0" encoding="UTF-8"?>
-<root>
- <child/>
- <child/>
-</root>
- XML
- assert_equal([true],
- match(xml, "/root/child=true()"))
- end
-
- def test_boolean_false
- xml = <<-XML
-<?xml version="1.0" encoding="UTF-8"?>
-<root>
-</root>
- XML
- assert_equal([false],
- match(xml, "/root/child=true()"))
- end
-
- def test_number_true
- xml = <<-XML
-<?xml version="1.0" encoding="UTF-8"?>
-<root>
- <child>100</child>
- <child>200</child>
-</root>
- XML
- assert_equal([true],
- match(xml, "/root/child=100"))
- end
-
- def test_number_false
- xml = <<-XML
-<?xml version="1.0" encoding="UTF-8"?>
-<root>
- <child>100</child>
- <child>200</child>
-</root>
- XML
- assert_equal([false],
- match(xml, "/root/child=300"))
- end
-
- def test_string_true
- xml = <<-XML
-<?xml version="1.0" encoding="UTF-8"?>
-<root>
- <child>text</child>
- <child>string</child>
-</root>
- XML
- assert_equal([true],
- match(xml, "/root/child='string'"))
- end
-
- def test_string_false
- xml = <<-XML
-<?xml version="1.0" encoding="UTF-8"?>
-<root>
- <child>text</child>
- <child>string</child>
-</root>
- XML
- assert_equal([false],
- match(xml, "/root/child='nonexistent'"))
- end
- end
-end