Skip to content

Commit

Permalink
[ruby/rexml] xpath: add missing value conversions for equality and re…
Browse files Browse the repository at this point in the history
…lational expressions

GitHub: fix #18

Reported by Mirko Budszuhn. Thanks!!!

ruby/rexml@0dca2a2ba0
  • Loading branch information
kou authored and hsbt committed Aug 4, 2019
1 parent 39f275e commit 310a2a9
Show file tree
Hide file tree
Showing 3 changed files with 295 additions and 104 deletions.
59 changes: 39 additions & 20 deletions lib/rexml/xpath_parser.rb
Expand Up @@ -864,32 +864,51 @@ def equality_relational_compare(set1, op, set2)
# 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
Expand Down
256 changes: 256 additions & 0 deletions 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

0 comments on commit 310a2a9

Please sign in to comment.