Skip to content

Commit 0dca2a2

Browse files
committed
xpath: add missing value conversions for equality and relational expressions
GitHub: fix #18 Reported by Mirko Budszuhn. Thanks!!!
1 parent b48f3af commit 0dca2a2

File tree

3 files changed

+295
-104
lines changed

3 files changed

+295
-104
lines changed

lib/rexml/xpath_parser.rb

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -864,32 +864,51 @@ def equality_relational_compare(set1, op, set2)
864864
# Else, convert to string
865865
# Else
866866
# Convert both to numbers and compare
867-
set1 = unnode(set1) if set1.is_a?(Array)
868-
set2 = unnode(set2) if set2.is_a?(Array)
869-
s1 = Functions.string(set1)
870-
s2 = Functions.string(set2)
871-
if s1 == 'true' or s1 == 'false' or s2 == 'true' or s2 == 'false'
872-
set1 = Functions::boolean( set1 )
873-
set2 = Functions::boolean( set2 )
867+
compare(set1, op, set2)
868+
end
869+
end
870+
871+
def value_type(value)
872+
case value
873+
when true, false
874+
:boolean
875+
when Numeric
876+
:number
877+
when String
878+
:string
879+
else
880+
raise "[BUG] Unexpected value type: <#{value.inspect}>"
881+
end
882+
end
883+
884+
def normalize_compare_values(a, operator, b)
885+
a_type = value_type(a)
886+
b_type = value_type(b)
887+
case operator
888+
when :eq, :neq
889+
if a_type == :boolean or b_type == :boolean
890+
a = Functions.boolean(a) unless a_type == :boolean
891+
b = Functions.boolean(b) unless b_type == :boolean
892+
elsif a_type == :number or b_type == :number
893+
a = Functions.number(a) unless a_type == :number
894+
b = Functions.number(b) unless b_type == :number
874895
else
875-
if op == :eq or op == :neq
876-
if s1 =~ /^\d+(\.\d+)?$/ or s2 =~ /^\d+(\.\d+)?$/
877-
set1 = Functions::number( s1 )
878-
set2 = Functions::number( s2 )
879-
else
880-
set1 = Functions::string( set1 )
881-
set2 = Functions::string( set2 )
882-
end
883-
else
884-
set1 = Functions::number( set1 )
885-
set2 = Functions::number( set2 )
886-
end
896+
a = Functions.string(a) unless a_type == :string
897+
b = Functions.string(b) unless b_type == :string
887898
end
888-
compare( set1, op, set2 )
899+
when :lt, :lteq, :gt, :gteq
900+
a = Functions.number(a) unless a_type == :number
901+
b = Functions.number(b) unless b_type == :number
902+
else
903+
message = "[BUG] Unexpected compare operator: " +
904+
"<#{operator.inspect}>: <#{a.inspect}>: <#{b.inspect}>"
905+
raise message
889906
end
907+
[a, b]
890908
end
891909

892910
def compare(a, operator, b)
911+
a, b = normalize_compare_values(a, operator, b)
893912
case operator
894913
when :eq
895914
a == b

test/rexml/xpath/test_compare.rb

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
# frozen_string_literal: false
2+
3+
require_relative "../rexml_test_utils"
4+
5+
require "rexml/document"
6+
7+
module REXMLTests
8+
class TestXPathCompare < Test::Unit::TestCase
9+
def match(xml, xpath)
10+
document = REXML::Document.new(xml)
11+
REXML::XPath.match(document, xpath)
12+
end
13+
14+
class TestEqual < self
15+
class TestNodeSet < self
16+
def test_boolean_true
17+
xml = <<-XML
18+
<?xml version="1.0" encoding="UTF-8"?>
19+
<root>
20+
<child/>
21+
<child/>
22+
</root>
23+
XML
24+
assert_equal([true],
25+
match(xml, "/root/child=true()"))
26+
end
27+
28+
def test_boolean_false
29+
xml = <<-XML
30+
<?xml version="1.0" encoding="UTF-8"?>
31+
<root>
32+
</root>
33+
XML
34+
assert_equal([false],
35+
match(xml, "/root/child=true()"))
36+
end
37+
38+
def test_number_true
39+
xml = <<-XML
40+
<?xml version="1.0" encoding="UTF-8"?>
41+
<root>
42+
<child>100</child>
43+
<child>200</child>
44+
</root>
45+
XML
46+
assert_equal([true],
47+
match(xml, "/root/child=100"))
48+
end
49+
50+
def test_number_false
51+
xml = <<-XML
52+
<?xml version="1.0" encoding="UTF-8"?>
53+
<root>
54+
<child>100</child>
55+
<child>200</child>
56+
</root>
57+
XML
58+
assert_equal([false],
59+
match(xml, "/root/child=300"))
60+
end
61+
62+
def test_string_true
63+
xml = <<-XML
64+
<?xml version="1.0" encoding="UTF-8"?>
65+
<root>
66+
<child>text</child>
67+
<child>string</child>
68+
</root>
69+
XML
70+
assert_equal([true],
71+
match(xml, "/root/child='string'"))
72+
end
73+
74+
def test_string_false
75+
xml = <<-XML
76+
<?xml version="1.0" encoding="UTF-8"?>
77+
<root>
78+
<child>text</child>
79+
<child>string</child>
80+
</root>
81+
XML
82+
assert_equal([false],
83+
match(xml, "/root/child='nonexistent'"))
84+
end
85+
end
86+
87+
class TestBoolean < self
88+
def test_number_true
89+
xml = "<root/>"
90+
assert_equal([true],
91+
match(xml, "true()=1"))
92+
end
93+
94+
def test_number_false
95+
xml = "<root/>"
96+
assert_equal([false],
97+
match(xml, "true()=0"))
98+
end
99+
100+
def test_string_true
101+
xml = "<root/>"
102+
assert_equal([true],
103+
match(xml, "true()='string'"))
104+
end
105+
106+
def test_string_false
107+
xml = "<root/>"
108+
assert_equal([false],
109+
match(xml, "true()=''"))
110+
end
111+
end
112+
113+
class TestNumber < self
114+
def test_string_true
115+
xml = "<root/>"
116+
assert_equal([true],
117+
match(xml, "1='1'"))
118+
end
119+
120+
def test_string_false
121+
xml = "<root/>"
122+
assert_equal([false],
123+
match(xml, "1='2'"))
124+
end
125+
end
126+
end
127+
128+
class TestGreaterThan < self
129+
class TestNodeSet < self
130+
def test_boolean_truex
131+
xml = <<-XML
132+
<?xml version="1.0" encoding="UTF-8"?>
133+
<root>
134+
<child/>
135+
</root>
136+
XML
137+
assert_equal([true],
138+
match(xml, "/root/child>false()"))
139+
end
140+
141+
def test_boolean_false
142+
xml = <<-XML
143+
<?xml version="1.0" encoding="UTF-8"?>
144+
<root>
145+
<child/>
146+
</root>
147+
XML
148+
assert_equal([false],
149+
match(xml, "/root/child>true()"))
150+
end
151+
152+
def test_number_true
153+
xml = <<-XML
154+
<?xml version="1.0" encoding="UTF-8"?>
155+
<root>
156+
<child>100</child>
157+
<child>200</child>
158+
</root>
159+
XML
160+
assert_equal([true],
161+
match(xml, "/root/child>199"))
162+
end
163+
164+
def test_number_false
165+
xml = <<-XML
166+
<?xml version="1.0" encoding="UTF-8"?>
167+
<root>
168+
<child>100</child>
169+
<child>200</child>
170+
</root>
171+
XML
172+
assert_equal([false],
173+
match(xml, "/root/child>200"))
174+
end
175+
176+
def test_string_true
177+
xml = <<-XML
178+
<?xml version="1.0" encoding="UTF-8"?>
179+
<root>
180+
<child>100</child>
181+
<child>200</child>
182+
</root>
183+
XML
184+
assert_equal([true],
185+
match(xml, "/root/child>'199'"))
186+
end
187+
188+
def test_string_false
189+
xml = <<-XML
190+
<?xml version="1.0" encoding="UTF-8"?>
191+
<root>
192+
<child>100</child>
193+
<child>200</child>
194+
</root>
195+
XML
196+
assert_equal([false],
197+
match(xml, "/root/child>'200'"))
198+
end
199+
end
200+
201+
class TestBoolean < self
202+
def test_string_true
203+
xml = "<root/>"
204+
assert_equal([true],
205+
match(xml, "true()>'0'"))
206+
end
207+
208+
def test_string_false
209+
xml = "<root/>"
210+
assert_equal([false],
211+
match(xml, "true()>'1'"))
212+
end
213+
end
214+
215+
class TestNumber < self
216+
def test_boolean_true
217+
xml = "<root/>"
218+
assert_equal([true],
219+
match(xml, "true()>0"))
220+
end
221+
222+
def test_number_false
223+
xml = "<root/>"
224+
assert_equal([false],
225+
match(xml, "true()>1"))
226+
end
227+
228+
def test_string_true
229+
xml = "<root/>"
230+
assert_equal([true],
231+
match(xml, "1>'0'"))
232+
end
233+
234+
def test_string_false
235+
xml = "<root/>"
236+
assert_equal([false],
237+
match(xml, "1>'1'"))
238+
end
239+
end
240+
241+
class TestString < self
242+
def test_string_true
243+
xml = "<root/>"
244+
assert_equal([true],
245+
match(xml, "'1'>'0'"))
246+
end
247+
248+
def test_string_false
249+
xml = "<root/>"
250+
assert_equal([false],
251+
match(xml, "'1'>'1'"))
252+
end
253+
end
254+
end
255+
end
256+
end

0 commit comments

Comments
 (0)