mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
[ruby/rexml] xpath: fix a bug for equality or relational expressions
GitHub: fix #17 There is a bug when they are used against node set. They should return boolean value but they returned node set. Reported by Mirko Budszuhn. Thanks!!! https://github.com/ruby/rexml/commit/a02bf38440
This commit is contained in:
parent
c46ba8e9a3
commit
6ef8294397
4 changed files with 178 additions and 114 deletions
|
@ -1,33 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
module REXML
|
||||
class SyncEnumerator
|
||||
include Enumerable
|
||||
|
||||
# Creates a new SyncEnumerator which enumerates rows of given
|
||||
# Enumerable objects.
|
||||
def initialize(*enums)
|
||||
@gens = enums
|
||||
@length = @gens.collect {|x| x.size }.max
|
||||
end
|
||||
|
||||
# Returns the number of enumerated Enumerable objects, i.e. the size
|
||||
# of each row.
|
||||
def size
|
||||
@gens.size
|
||||
end
|
||||
|
||||
# Returns the number of enumerated Enumerable objects, i.e. the size
|
||||
# of each row.
|
||||
def length
|
||||
@gens.length
|
||||
end
|
||||
|
||||
# Enumerates rows of the Enumerable objects.
|
||||
def each
|
||||
@length.times {|i|
|
||||
yield @gens.collect {|x| x[i]}
|
||||
}
|
||||
self
|
||||
end
|
||||
end
|
||||
end
|
|
@ -5,7 +5,6 @@ require "pp"
|
|||
require_relative 'namespace'
|
||||
require_relative 'xmltokens'
|
||||
require_relative 'attribute'
|
||||
require_relative 'syncenumerator'
|
||||
require_relative 'parsers/xpathparser'
|
||||
|
||||
class Object
|
||||
|
@ -141,7 +140,7 @@ module REXML
|
|||
when Array # nodeset
|
||||
unnode(result)
|
||||
else
|
||||
result
|
||||
[result]
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -341,26 +340,24 @@ module REXML
|
|||
var_name = path_stack.shift
|
||||
return [@variables[var_name]]
|
||||
|
||||
# :and, :or, :eq, :neq, :lt, :lteq, :gt, :gteq
|
||||
# TODO: Special case for :or and :and -- not evaluate the right
|
||||
# operand if the left alone determines result (i.e. is true for
|
||||
# :or and false for :and).
|
||||
when :eq, :neq, :lt, :lteq, :gt, :gteq, :or
|
||||
when :eq, :neq, :lt, :lteq, :gt, :gteq
|
||||
left = expr( path_stack.shift, nodeset.dup, context )
|
||||
right = expr( path_stack.shift, nodeset.dup, context )
|
||||
res = equality_relational_compare( left, op, right )
|
||||
trace(op, left, right, res) if @debug
|
||||
return res
|
||||
|
||||
when :or
|
||||
left = expr(path_stack.shift, nodeset.dup, context)
|
||||
return true if Functions.boolean(left)
|
||||
right = expr(path_stack.shift, nodeset.dup, context)
|
||||
return Functions.boolean(right)
|
||||
|
||||
when :and
|
||||
left = expr( path_stack.shift, nodeset.dup, context )
|
||||
return [] unless left
|
||||
if left.respond_to?(:inject) and !left.inject(false) {|a,b| a | b}
|
||||
return []
|
||||
end
|
||||
right = expr( path_stack.shift, nodeset.dup, context )
|
||||
res = equality_relational_compare( left, op, right )
|
||||
return res
|
||||
left = expr(path_stack.shift, nodeset.dup, context)
|
||||
return false unless Functions.boolean(left)
|
||||
right = expr(path_stack.shift, nodeset.dup, context)
|
||||
return Functions.boolean(right)
|
||||
|
||||
when :div, :mod, :mult, :plus, :minus
|
||||
left = expr(path_stack.shift, nodeset, context)
|
||||
|
@ -397,31 +394,34 @@ module REXML
|
|||
when :function
|
||||
func_name = path_stack.shift.tr('-','_')
|
||||
arguments = path_stack.shift
|
||||
subcontext = context ? nil : { :size => nodeset.size }
|
||||
|
||||
res = []
|
||||
cont = context
|
||||
nodeset.each_with_index do |node, i|
|
||||
if subcontext
|
||||
if node.is_a?(XPathNode)
|
||||
subcontext[:node] = node.raw_node
|
||||
subcontext[:index] = node.position
|
||||
else
|
||||
subcontext[:node] = node
|
||||
subcontext[:index] = i
|
||||
end
|
||||
cont = subcontext
|
||||
end
|
||||
arg_clone = arguments.dclone
|
||||
args = arg_clone.collect do |arg|
|
||||
result = expr( arg, [node], cont )
|
||||
result = unnode(result) if result.is_a?(Array)
|
||||
result
|
||||
end
|
||||
Functions.context = cont
|
||||
res << Functions.send( func_name, *args )
|
||||
if nodeset.size != 1
|
||||
message = "[BUG] Node set size must be 1 for function call: "
|
||||
message += "<#{func_name}>: <#{nodeset.inspect}>: "
|
||||
message += "<#{arguments.inspect}>"
|
||||
raise message
|
||||
end
|
||||
return res
|
||||
|
||||
node = nodeset.first
|
||||
if context
|
||||
target_context = context
|
||||
else
|
||||
target_context = {:size => nodeset.size}
|
||||
if node.is_a?(XPathNode)
|
||||
target_context[:node] = node.raw_node
|
||||
target_context[:index] = node.position
|
||||
else
|
||||
target_context[:node] = node
|
||||
target_context[:index] = 1
|
||||
end
|
||||
end
|
||||
args = arguments.dclone.collect do |arg|
|
||||
result = expr(arg, nodeset, target_context)
|
||||
result = unnode(result) if result.is_a?(Array)
|
||||
result
|
||||
end
|
||||
Functions.context = target_context
|
||||
return Functions.send(func_name, *args)
|
||||
|
||||
else
|
||||
raise "[BUG] Unexpected path: <#{op.inspect}>: <#{path_stack.inspect}>"
|
||||
|
@ -806,31 +806,28 @@ module REXML
|
|||
end
|
||||
end
|
||||
|
||||
def equality_relational_compare( set1, op, set2 )
|
||||
def equality_relational_compare(set1, op, set2)
|
||||
set1 = unnode(set1) if set1.is_a?(Array)
|
||||
set2 = unnode(set2) if set2.is_a?(Array)
|
||||
|
||||
if set1.kind_of? Array and set2.kind_of? Array
|
||||
if set1.size == 0 or set2.size == 0
|
||||
nd = set1.size==0 ? set2 : set1
|
||||
rv = nd.collect { |il| compare( il, op, nil ) }
|
||||
return rv
|
||||
else
|
||||
res = []
|
||||
SyncEnumerator.new( set1, set2 ).each { |i1, i2|
|
||||
i1 = norm( i1 )
|
||||
i2 = norm( i2 )
|
||||
res << compare( i1, op, i2 )
|
||||
}
|
||||
return res
|
||||
# If both objects to be compared are node-sets, then the
|
||||
# comparison will be true if and only if there is a node in the
|
||||
# first node-set and a node in the second node-set such that the
|
||||
# result of performing the comparison on the string-values of
|
||||
# the two nodes is true.
|
||||
set1.product(set2).any? do |node1, node2|
|
||||
node_string1 = Functions.string(node1)
|
||||
node_string2 = Functions.string(node2)
|
||||
compare(node_string1, op, node_string2)
|
||||
end
|
||||
end
|
||||
# If one is nodeset and other is number, compare number to each item
|
||||
# in nodeset s.t. number op number(string(item))
|
||||
# If one is nodeset and other is string, compare string to each item
|
||||
# in nodeset s.t. string op string(item)
|
||||
# If one is nodeset and other is boolean, compare boolean to each item
|
||||
# in nodeset s.t. boolean op boolean(item)
|
||||
if set1.kind_of? Array or set2.kind_of? Array
|
||||
elsif set1.kind_of? Array or set2.kind_of? Array
|
||||
# If one is nodeset and other is number, compare number to each item
|
||||
# in nodeset s.t. number op number(string(item))
|
||||
# If one is nodeset and other is string, compare string to each item
|
||||
# in nodeset s.t. string op string(item)
|
||||
# If one is nodeset and other is boolean, compare boolean to each item
|
||||
# in nodeset s.t. boolean op boolean(item)
|
||||
if set1.kind_of? Array
|
||||
a = set1
|
||||
b = set2
|
||||
|
@ -841,15 +838,23 @@ module REXML
|
|||
|
||||
case b
|
||||
when true, false
|
||||
return unnode(a) {|v| compare( Functions::boolean(v), op, b ) }
|
||||
each_unnode(a).any? do |unnoded|
|
||||
compare(Functions.boolean(unnoded), op, b)
|
||||
end
|
||||
when Numeric
|
||||
return unnode(a) {|v| compare( Functions::number(v), op, b )}
|
||||
when /^\d+(\.\d+)?$/
|
||||
b = Functions::number( b )
|
||||
return unnode(a) {|v| compare( Functions::number(v), op, b )}
|
||||
each_unnode(a).any? do |unnoded|
|
||||
compare(Functions.number(unnoded), op, b)
|
||||
end
|
||||
when /\A\d+(\.\d+)?\z/
|
||||
b = Functions.number(b)
|
||||
each_unnode(a).any? do |unnoded|
|
||||
compare(Functions.number(unnoded), op, b)
|
||||
end
|
||||
else
|
||||
b = Functions::string( b )
|
||||
return unnode(a) { |v| compare( Functions::string(v), op, b ) }
|
||||
b = Functions::string(b)
|
||||
each_unnode(a).any? do |unnoded|
|
||||
compare(Functions::string(unnoded), op, b)
|
||||
end
|
||||
end
|
||||
else
|
||||
# If neither is nodeset,
|
||||
|
@ -880,13 +885,12 @@ module REXML
|
|||
set2 = Functions::number( set2 )
|
||||
end
|
||||
end
|
||||
return compare( set1, op, set2 )
|
||||
compare( set1, op, set2 )
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
def compare a, op, b
|
||||
case op
|
||||
def compare(a, operator, b)
|
||||
case operator
|
||||
when :eq
|
||||
a == b
|
||||
when :neq
|
||||
|
@ -899,22 +903,27 @@ module REXML
|
|||
a > b
|
||||
when :gteq
|
||||
a >= b
|
||||
when :and
|
||||
a and b
|
||||
when :or
|
||||
a or b
|
||||
else
|
||||
false
|
||||
message = "[BUG] Unexpected compare operator: " +
|
||||
"<#{operator.inspect}>: <#{a.inspect}>: <#{b.inspect}>"
|
||||
raise message
|
||||
end
|
||||
end
|
||||
|
||||
def unnode(nodeset)
|
||||
nodeset.collect do |node|
|
||||
def each_unnode(nodeset)
|
||||
return to_enum(__method__, nodeset) unless block_given?
|
||||
nodeset.each do |node|
|
||||
if node.is_a?(XPathNode)
|
||||
unnoded = node.raw_node
|
||||
else
|
||||
unnoded = node
|
||||
end
|
||||
yield(unnoded)
|
||||
end
|
||||
end
|
||||
|
||||
def unnode(nodeset)
|
||||
each_unnode(nodeset).collect do |unnoded|
|
||||
unnoded = yield(unnoded) if block_given?
|
||||
unnoded
|
||||
end
|
||||
|
|
|
@ -369,11 +369,15 @@ module REXMLTests
|
|||
assert_equal 2, c
|
||||
end
|
||||
|
||||
def match(xpath)
|
||||
XPath.match(@@doc, xpath).collect(&:to_s)
|
||||
end
|
||||
|
||||
def test_grouping
|
||||
t = XPath.first( @@doc, "a/d/*[name()='d' and (name()='f' or name()='q')]" )
|
||||
assert_nil t
|
||||
t = XPath.first( @@doc, "a/d/*[(name()='d' and name()='f') or name()='q']" )
|
||||
assert_equal 'q', t.name
|
||||
assert_equal([],
|
||||
match("a/d/*[name()='d' and (name()='f' or name()='q')]"))
|
||||
assert_equal(["<q id='19'/>"],
|
||||
match("a/d/*[(name()='d' and name()='f') or name()='q']"))
|
||||
end
|
||||
|
||||
def test_preceding
|
||||
|
|
84
test/rexml/xpath/test_node_set.rb
Normal file
84
test/rexml/xpath/test_node_set.rb
Normal file
|
@ -0,0 +1,84 @@
|
|||
# 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
|
Loading…
Add table
Reference in a new issue