1 require 'rexml/namespace'
2 require 'rexml/xmltokens'
3 require 'rexml/attribute'
4 require 'rexml/syncenumerator'
5 require 'rexml/parsers/xpathparser'
6
7 class Object
8 def dclone
9 clone
10 end
11 end
12 class Symbol
13 def dclone ; self ; end
14 end
15 class Fixnum
16 def dclone ; self ; end
17 end
18 class Float
19 def dclone ; self ; end
20 end
21 class Array
22 def dclone
23 klone = self.clone
24 klone.clear
25 self.each{|v| klone << v.dclone}
26 klone
27 end
28 end
29
30 module REXML
31 # You don't want to use this class. Really. Use XPath, which is a wrapper
32 # for this class. Believe me. You don't want to poke around in here.
33 # There is strange, dark magic at work in this code. Beware. Go back! Go
34 # back while you still can!
35 class XPathParser
36 include XMLTokens
37 LITERAL = /^'([^']*)'|^"([^"]*)"/u
38
39 def initialize( )
40 @parser = REXML::Parsers::XPathParser.new
41 @namespaces = nil
42 @variables = {}
43 end
44
45 def namespaces=( namespaces={} )
46 Functions::namespace_context = namespaces
47 @namespaces = namespaces
48 end
49
50 def variables=( vars={} )
51 Functions::variables = vars
52 @variables = vars
53 end
54
55 def parse path, nodeset
56 #puts "#"*40
57 path_stack = @parser.parse( path )
58 #puts "PARSE: #{path} => #{path_stack.inspect}"
59 #puts "PARSE: nodeset = #{nodeset.inspect}"
60 match( path_stack, nodeset )
61 end
62
63 def get_first path, nodeset
64 #puts "#"*40
65 path_stack = @parser.parse( path )
66 #puts "PARSE: #{path} => #{path_stack.inspect}"
67 #puts "PARSE: nodeset = #{nodeset.inspect}"
68 first( path_stack, nodeset )
69 end
70
71 def predicate path, nodeset
72 path_stack = @parser.parse( path )
73 expr( path_stack, nodeset )
74 end
75
76 def []=( variable_name, value )
77 @variables[ variable_name ] = value
78 end
79
80
81 # Performs a depth-first (document order) XPath search, and returns the
82 # first match. This is the fastest, lightest way to return a single result.
83 #
84 # FIXME: This method is incomplete!
85 def first( path_stack, node )
86 #puts "#{depth}) Entering match( #{path.inspect}, #{tree.inspect} )"
87 return nil if path.size == 0
88
89 case path[0]
90 when :document
91 # do nothing
92 return first( path[1..-1], node )
93 when :child
94 for c in node.children
95 #puts "#{depth}) CHILD checking #{name(c)}"
96 r = first( path[1..-1], c )
97 #puts "#{depth}) RETURNING #{r.inspect}" if r
98 return r if r
99 end
100 when :qname
101 name = path[2]
102 #puts "#{depth}) QNAME #{name(tree)} == #{name} (path => #{path.size})"
103 if node.name == name
104 #puts "#{depth}) RETURNING #{tree.inspect}" if path.size == 3
105 return node if path.size == 3
106 return first( path[3..-1], node )
107 else
108 return nil
109 end
110 when :descendant_or_self
111 r = first( path[1..-1], node )
112 return r if r
113 for c in node.children
114 r = first( path, c )
115 return r if r
116 end
117 when :node
118 return first( path[1..-1], node )
119 when :any
120 return first( path[1..-1], node )
121 end
122 return nil
123 end
124
125
126 def match( path_stack, nodeset )
127 #puts "MATCH: path_stack = #{path_stack.inspect}"
128 #puts "MATCH: nodeset = #{nodeset.inspect}"
129 r = expr( path_stack, nodeset )
130 #puts "MAIN EXPR => #{r.inspect}"
131 r
132 end
133
134 private
135
136
137 # Returns a String namespace for a node, given a prefix
138 # The rules are:
139 #
140 # 1. Use the supplied namespace mapping first.
141 # 2. If no mapping was supplied, use the context node to look up the namespace
142 def get_namespace( node, prefix )
143 if @namespaces
144 return @namespaces[prefix] || ''
145 else
146 return node.namespace( prefix ) if node.node_type == :element
147 return ''
148 end
149 end
150
151
152 # Expr takes a stack of path elements and a set of nodes (either a Parent
153 # or an Array and returns an Array of matching nodes
154 ALL = [ :attribute, :element, :text, :processing_instruction, :comment ]
155 ELEMENTS = [ :element ]
156 def expr( path_stack, nodeset, context=nil )
157 #puts "#"*15
158 #puts "In expr with #{path_stack.inspect}"
159 #puts "Returning" if path_stack.length == 0 || nodeset.length == 0
160 node_types = ELEMENTS
161 return nodeset if path_stack.length == 0 || nodeset.length == 0
162 while path_stack.length > 0
163 #puts "#"*5
164 #puts "Path stack = #{path_stack.inspect}"
165 #puts "Nodeset is #{nodeset.inspect}"
166 if nodeset.length == 0
167 path_stack.clear
168 return []
169 end
170 case (op = path_stack.shift)
171 when :document
172 nodeset = [ nodeset[0].root_node ]
173 #puts ":document, nodeset = #{nodeset.inspect}"
174
175 when :qname
176 #puts "IN QNAME"
177 prefix = path_stack.shift
178 name = path_stack.shift
179 nodeset.delete_if do |node|
180 # FIXME: This DOUBLES the time XPath searches take
181 ns = get_namespace( node, prefix )
182 #puts "NS = #{ns.inspect}"
183 #puts "node.node_type == :element => #{node.node_type == :element}"
184 if node.node_type == :element
185 #puts "node.name == #{name} => #{node.name == name}"
186 if node.name == name
187 #puts "node.namespace == #{ns.inspect} => #{node.namespace == ns}"
188 end
189 end
190 !(node.node_type == :element and
191 node.name == name and
192 node.namespace == ns )
193 end
194 node_types = ELEMENTS
195
196 when :any
197 #puts "ANY 1: nodeset = #{nodeset.inspect}"
198 #puts "ANY 1: node_types = #{node_types.inspect}"
199 nodeset.delete_if { |node| !node_types.include?(node.node_type) }
200 #puts "ANY 2: nodeset = #{nodeset.inspect}"
201
202 when :self
203 # This space left intentionally blank
204
205 when :processing_instruction
206 target = path_stack.shift
207 nodeset.delete_if do |node|
208 (node.node_type != :processing_instruction) or
209 ( target!='' and ( node.target != target ) )
210 end
211
212 when :text
213 nodeset.delete_if { |node| node.node_type != :text }
214
215 when :comment
216 nodeset.delete_if { |node| node.node_type != :comment }
217
218 when :node
219 # This space left intentionally blank
220 node_types = ALL
221
222 when :child
223 new_nodeset = []
224 nt = nil
225 for node in nodeset
226 nt = node.node_type
227 new_nodeset += node.children if nt == :element or nt == :document
228 end
229 nodeset = new_nodeset
230 node_types = ELEMENTS
231
232 when :literal
233 return path_stack.shift
234
235 when :attribute
236 new_nodeset = []
237 case path_stack.shift
238 when :qname
239 prefix = path_stack.shift
240 name = path_stack.shift
241 for element in nodeset
242 if element.node_type == :element
243 #puts "Element name = #{element.name}"
244 #puts "get_namespace( #{element.inspect}, #{prefix} ) = #{get_namespace(element, prefix)}"
245 attrib = element.attribute( name, get_namespace(element, prefix) )
246 #puts "attrib = #{attrib.inspect}"
247 new_nodeset << attrib if attrib
248 end
249 end
250 when :any
251 #puts "ANY"
252 for element in nodeset
253 if element.node_type == :element
254 new_nodeset += element.attributes.to_a
255 end
256 end
257 end
258 nodeset = new_nodeset
259
260 when :parent
261 #puts "PARENT 1: nodeset = #{nodeset}"
262 nodeset = nodeset.collect{|n| n.parent}.compact
263 #nodeset = expr(path_stack.dclone, nodeset.collect{|n| n.parent}.compact)
264 #puts "PARENT 2: nodeset = #{nodeset.inspect}"
265 node_types = ELEMENTS
266
267 when :ancestor
268 new_nodeset = []
269 for node in nodeset
270 while node.parent
271 node = node.parent
272 new_nodeset << node unless new_nodeset.include? node
273 end
274 end
275 nodeset = new_nodeset
276 node_types = ELEMENTS
277
278 when :ancestor_or_self
279 new_nodeset = []
280 for node in nodeset
281 if node.node_type == :element
282 new_nodeset << node
283 while ( node.parent )
284 node = node.parent
285 new_nodeset << node unless new_nodeset.include? node
286 end
287 end
288 end
289 nodeset = new_nodeset
290 node_types = ELEMENTS
291
292 when :predicate
293 new_nodeset = []
294 subcontext = { :size => nodeset.size }
295 pred = path_stack.shift
296 nodeset.each_with_index { |node, index|
297 subcontext[ :node ] = node
298 #puts "PREDICATE SETTING CONTEXT INDEX TO #{index+1}"
299 subcontext[ :index ] = index+1
300 pc = pred.dclone
301 #puts "#{node.hash}) Recursing with #{pred.inspect} and [#{node.inspect}]"
302 result = expr( pc, [node], subcontext )
303 result = result[0] if result.kind_of? Array and result.length == 1
304 #puts "#{node.hash}) Result = #{result.inspect} (#{result.class.name})"
305 if result.kind_of? Numeric
306 #puts "Adding node #{node.inspect}" if result == (index+1)
307 new_nodeset << node if result == (index+1)
308 elsif result.instance_of? Array
309 if result.size > 0 and result.inject(false) {|k,s| s or k}
310 #puts "Adding node #{node.inspect}" if result.size > 0
311 new_nodeset << node if result.size > 0
312 end
313 else
314 #puts "Adding node #{node.inspect}" if result
315 new_nodeset << node if result
316 end
317 }
318 #puts "New nodeset = #{new_nodeset.inspect}"
319 #puts "Path_stack = #{path_stack.inspect}"
320 nodeset = new_nodeset
321 =begin
322 predicate = path_stack.shift
323 ns = nodeset.clone
324 result = expr( predicate, ns )
325 #puts "Result = #{result.inspect} (#{result.class.name})"
326 #puts "nodeset = #{nodeset.inspect}"
327 if result.kind_of? Array
328 nodeset = result.zip(ns).collect{|m,n| n if m}.compact
329 else
330 nodeset = result ? nodeset : []
331 end
332 #puts "Outgoing NS = #{nodeset.inspect}"
333 =end
334
335 when :descendant_or_self
336 rv = descendant_or_self( path_stack, nodeset )
337 path_stack.clear
338 nodeset = rv
339 node_types = ELEMENTS
340
341 when :descendant
342 results = []
343 nt = nil
344 for node in nodeset
345 nt = node.node_type
346 results += expr( path_stack.dclone.unshift( :descendant_or_self ),
347 node.children ) if nt == :element or nt == :document
348 end
349 nodeset = results
350 node_types = ELEMENTS
351
352 when :following_sibling
353 #puts "FOLLOWING_SIBLING 1: nodeset = #{nodeset}"
354 results = []
355 nodeset.each do |node|
356 next if node.parent.nil?
357 all_siblings = node.parent.children
358 current_index = all_siblings.index( node )
359 following_siblings = all_siblings[ current_index+1 .. -1 ]
360 results += expr( path_stack.dclone, following_siblings )
361 end
362 #puts "FOLLOWING_SIBLING 2: nodeset = #{nodeset}"
363 nodeset = results
364
365 when :preceding_sibling
366 results = []
367 nodeset.each do |node|
368 next if node.parent.nil?
369 all_siblings = node.parent.children
370 current_index = all_siblings.index( node )
371 preceding_siblings = all_siblings[ 0, current_index ].reverse
372 results += preceding_siblings
373 end
374 nodeset = results
375 node_types = ELEMENTS
376
377 when :preceding
378 new_nodeset = []
379 for node in nodeset
380 new_nodeset += preceding( node )
381 end
382 #puts "NEW NODESET => #{new_nodeset.inspect}"
383 nodeset = new_nodeset
384 node_types = ELEMENTS
385
386 when :following
387 new_nodeset = []
388 for node in nodeset
389 new_nodeset += following( node )
390 end
391 nodeset = new_nodeset
392 node_types = ELEMENTS
393
394 when :namespace
395 #puts "In :namespace"
396 new_nodeset = []
397 prefix = path_stack.shift
398 for node in nodeset
399 if (node.node_type == :element or node.node_type == :attribute)
400 if @namespaces
401 namespaces = @namespaces
402 elsif (node.node_type == :element)
403 namespaces = node.namespaces
404 else
405 namespaces = node.element.namesapces
406 end
407 #puts "Namespaces = #{namespaces.inspect}"
408 #puts "Prefix = #{prefix.inspect}"
409 #puts "Node.namespace = #{node.namespace}"
410 if (node.namespace == namespaces[prefix])
411 new_nodeset << node
412 end
413 end
414 end
415 nodeset = new_nodeset
416
417 when :variable
418 var_name = path_stack.shift
419 return @variables[ var_name ]
420
421 # :and, :or, :eq, :neq, :lt, :lteq, :gt, :gteq
422 # TODO: Special case for :or and :and -- not evaluate the right
423 # operand if the left alone determines result (i.e. is true for
424 # :or and false for :and).
425 when :eq, :neq, :lt, :lteq, :gt, :gteq, :and, :or
426 left = expr( path_stack.shift, nodeset.dup, context )
427 #puts "LEFT => #{left.inspect} (#{left.class.name})"
428 right = expr( path_stack.shift, nodeset.dup, context )
429 #puts "RIGHT => #{right.inspect} (#{right.class.name})"
430 res = equality_relational_compare( left, op, right )
431 #puts "RES => #{res.inspect}"
432 return res
433
434 when :and
435 left = expr( path_stack.shift, nodeset.dup, context )
436 #puts "LEFT => #{left.inspect} (#{left.class.name})"
437 if left == false || left.nil? || !left.inject(false) {|a,b| a | b}
438 return []
439 end
440 right = expr( path_stack.shift, nodeset.dup, context )
441 #puts "RIGHT => #{right.inspect} (#{right.class.name})"
442 res = equality_relational_compare( left, op, right )
443 #puts "RES => #{res.inspect}"
444 return res
445
446 when :div
447 left = Functions::number(expr(path_stack.shift, nodeset, context)).to_f
448 right = Functions::number(expr(path_stack.shift, nodeset, context)).to_f
449 return (left / right)
450
451 when :mod
452 left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
453 right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
454 return (left % right)
455
456 when :mult
457 left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
458 right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
459 return (left * right)
460
461 when :plus
462 left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
463 right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
464 return (left + right)
465
466 when :minus
467 left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
468 right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
469 return (left - right)
470
471 when :union
472 left = expr( path_stack.shift, nodeset, context )
473 right = expr( path_stack.shift, nodeset, context )
474 return (left | right)
475
476 when :neg
477 res = expr( path_stack, nodeset, context )
478 return -(res.to_f)
479
480 when :not
481 when :function
482 func_name = path_stack.shift.tr('-','_')
483 arguments = path_stack.shift
484 #puts "FUNCTION 0: #{func_name}(#{arguments.collect{|a|a.inspect}.join(', ')})"
485 subcontext = context ? nil : { :size => nodeset.size }
486
487 res = []
488 cont = context
489 nodeset.each_with_index { |n, i|
490 if subcontext
491 subcontext[:node] = n
492 subcontext[:index] = i
493 cont = subcontext
494 end
495 arg_clone = arguments.dclone
496 args = arg_clone.collect { |arg|
497 #puts "FUNCTION 1: Calling expr( #{arg.inspect}, [#{n.inspect}] )"
498 expr( arg, [n], cont )
499 }
500 #puts "FUNCTION 2: #{func_name}(#{args.collect{|a|a.inspect}.join(', ')})"
501 Functions.context = cont
502 res << Functions.send( func_name, *args )
503 #puts "FUNCTION 3: #{res[-1].inspect}"
504 }
505 return res
506
507 end
508 end # while
509 #puts "EXPR returning #{nodeset.inspect}"
510 return nodeset
511 end
512
513
514 ##########################################################
515 # FIXME
516 # The next two methods are BAD MOJO!
517 # This is my achilles heel. If anybody thinks of a better
518 # way of doing this, be my guest. This really sucks, but
519 # it is a wonder it works at all.
520 # ########################################################
521
522 def descendant_or_self( path_stack, nodeset )
523 rs = []
524 #puts "#"*80
525 #puts "PATH_STACK = #{path_stack.inspect}"
526 #puts "NODESET = #{nodeset.collect{|n|n.inspect}.inspect}"
527 d_o_s( path_stack, nodeset, rs )
528 #puts "RS = #{rs.collect{|n|n.inspect}.inspect}"
529 document_order(rs.flatten.compact)
530 #rs.flatten.compact
531 end
532
533 def d_o_s( p, ns, r )
534 #puts "IN DOS with #{ns.inspect}; ALREADY HAVE #{r.inspect}"
535 nt = nil
536 ns.each_index do |i|
537 n = ns[i]
538 #puts "P => #{p.inspect}"
539 x = expr( p.dclone, [ n ] )
540 nt = n.node_type
541 d_o_s( p, n.children, x ) if nt == :element or nt == :document and n.children.size > 0
542 r.concat(x) if x.size > 0
543 end
544 end
545
546
547 # Reorders an array of nodes so that they are in document order
548 # It tries to do this efficiently.
549 #
550 # FIXME: I need to get rid of this, but the issue is that most of the XPath
551 # interpreter functions as a filter, which means that we lose context going
552 # in and out of function calls. If I knew what the index of the nodes was,
553 # I wouldn't have to do this. Maybe add a document IDX for each node?
554 # Problems with mutable documents. Or, rewrite everything.
555 def document_order( array_of_nodes )
556 new_arry = []
557 array_of_nodes.each { |node|
558 node_idx = []
559 np = node.node_type == :attribute ? node.element : node
560 while np.parent and np.parent.node_type == :element
561 node_idx << np.parent.index( np )
562 np = np.parent
563 end
564 new_arry << [ node_idx.reverse, node ]
565 }
566 #puts "new_arry = #{new_arry.inspect}"
567 new_arry.sort{ |s1, s2| s1[0] <=> s2[0] }.collect{ |s| s[1] }
568 end
569
570
571 def recurse( nodeset, &block )
572 for node in nodeset
573 yield node
574 recurse( node, &block ) if node.node_type == :element
575 end
576 end
577
578
579
580 # Builds a nodeset of all of the preceding nodes of the supplied node,
581 # in reverse document order
582 # preceding:: includes every element in the document that precedes this node,
583 # except for ancestors
584 def preceding( node )
585 #puts "IN PRECEDING"
586 ancestors = []
587 p = node.parent
588 while p
589 ancestors << p
590 p = p.parent
591 end
592
593 acc = []
594 p = preceding_node_of( node )
595 #puts "P = #{p.inspect}"
596 while p
597 if ancestors.include? p
598 ancestors.delete(p)
599 else
600 acc << p
601 end
602 p = preceding_node_of( p )
603 #puts "P = #{p.inspect}"
604 end
605 acc
606 end
607
608 def preceding_node_of( node )
609 #puts "NODE: #{node.inspect}"
610 #puts "PREVIOUS NODE: #{node.previous_sibling_node.inspect}"
611 #puts "PARENT NODE: #{node.parent}"
612 psn = node.previous_sibling_node
613 if psn.nil?
614 if node.parent.nil? or node.parent.class == Document
615 return nil
616 end
617 return node.parent
618 #psn = preceding_node_of( node.parent )
619 end
620 while psn and psn.kind_of? Element and psn.children.size > 0
621 psn = psn.children[-1]
622 end
623 psn
624 end
625
626 def following( node )
627 #puts "IN PRECEDING"
628 acc = []
629 p = next_sibling_node( node )
630 #puts "P = #{p.inspect}"
631 while p
632 acc << p
633 p = following_node_of( p )
634 #puts "P = #{p.inspect}"
635 end
636 acc
637 end
638
639 def following_node_of( node )
640 #puts "NODE: #{node.inspect}"
641 #puts "PREVIOUS NODE: #{node.previous_sibling_node.inspect}"
642 #puts "PARENT NODE: #{node.parent}"
643 if node.kind_of? Element and node.children.size > 0
644 return node.children[0]
645 end
646 return next_sibling_node(node)
647 end
648
649 def next_sibling_node(node)
650 psn = node.next_sibling_node
651 while psn.nil?
652 if node.parent.nil? or node.parent.class == Document
653 return nil
654 end
655 node = node.parent
656 psn = node.next_sibling_node
657 #puts "psn = #{psn.inspect}"
658 end
659 return psn
660 end
661
662 def norm b
663 case b
664 when true, false
665 return b
666 when 'true', 'false'
667 return Functions::boolean( b )
668 when /^\d+(\.\d+)?$/
669 return Functions::number( b )
670 else
671 return Functions::string( b )
672 end
673 end
674
675 def equality_relational_compare( set1, op, set2 )
676 #puts "EQ_REL_COMP(#{set1.inspect} #{op.inspect} #{set2.inspect})"
677 if set1.kind_of? Array and set2.kind_of? Array
678 #puts "#{set1.size} & #{set2.size}"
679 if set1.size == 1 and set2.size == 1
680 set1 = set1[0]
681 set2 = set2[0]
682 elsif set1.size == 0 or set2.size == 0
683 nd = set1.size==0 ? set2 : set1
684 rv = nd.collect { |il| compare( il, op, nil ) }
685 #puts "RV = #{rv.inspect}"
686 return rv
687 else
688 res = []
689 enum = SyncEnumerator.new( set1, set2 ).each { |i1, i2|
690 #puts "i1 = #{i1.inspect} (#{i1.class.name})"
691 #puts "i2 = #{i2.inspect} (#{i2.class.name})"
692 i1 = norm( i1 )
693 i2 = norm( i2 )
694 res << compare( i1, op, i2 )
695 }
696 return res
697 end
698 end
699 #puts "EQ_REL_COMP: #{set1.inspect} (#{set1.class.name}), #{op}, #{set2.inspect} (#{set2.class.name})"
700 #puts "COMPARING VALUES"
701 # If one is nodeset and other is number, compare number to each item
702 # in nodeset s.t. number op number(string(item))
703 # If one is nodeset and other is string, compare string to each item
704 # in nodeset s.t. string op string(item)
705 # If one is nodeset and other is boolean, compare boolean to each item
706 # in nodeset s.t. boolean op boolean(item)
707 if set1.kind_of? Array or set2.kind_of? Array
708 #puts "ISA ARRAY"
709 if set1.kind_of? Array
710 a = set1
711 b = set2
712 else
713 a = set2
714 b = set1
715 end
716
717 case b
718 when true, false
719 return a.collect {|v| compare( Functions::boolean(v), op, b ) }
720 when Numeric
721 return a.collect {|v| compare( Functions::number(v), op, b )}
722 when /^\d+(\.\d+)?$/
723 b = Functions::number( b )
724 #puts "B = #{b.inspect}"
725 return a.collect {|v| compare( Functions::number(v), op, b )}
726 else
727 #puts "Functions::string( #{b}(#{b.class.name}) ) = #{Functions::string(b)}"
728 b = Functions::string( b )
729 return a.collect { |v| compare( Functions::string(v), op, b ) }
730 end
731 else
732 # If neither is nodeset,
733 # If op is = or !=
734 # If either boolean, convert to boolean
735 # If either number, convert to number
736 # Else, convert to string
737 # Else
738 # Convert both to numbers and compare
739 s1 = set1.to_s
740 s2 = set2.to_s
741 #puts "EQ_REL_COMP: #{set1}=>#{s1}, #{set2}=>#{s2}"
742 if s1 == 'true' or s1 == 'false' or s2 == 'true' or s2 == 'false'
743 #puts "Functions::boolean(#{set1})=>#{Functions::boolean(set1)}"
744 #puts "Functions::boolean(#{set2})=>#{Functions::boolean(set2)}"
745 set1 = Functions::boolean( set1 )
746 set2 = Functions::boolean( set2 )
747 else
748 if op == :eq or op == :neq
749 if s1 =~ /^\d+(\.\d+)?$/ or s2 =~ /^\d+(\.\d+)?$/
750 set1 = Functions::number( s1 )
751 set2 = Functions::number( s2 )
752 else
753 set1 = Functions::string( set1 )
754 set2 = Functions::string( set2 )
755 end
756 else
757 set1 = Functions::number( set1 )
758 set2 = Functions::number( set2 )
759 end
760 end
761 #puts "EQ_REL_COMP: #{set1} #{op} #{set2}"
762 #puts ">>> #{compare( set1, op, set2 )}"
763 return compare( set1, op, set2 )
764 end
765 return false
766 end
767
768 def compare a, op, b
769 #puts "COMPARE #{a.inspect}(#{a.class.name}) #{op} #{b.inspect}(#{b.class.name})"
770 case op
771 when :eq
772 a == b
773 when :neq
774 a != b
775 when :lt
776 a < b
777 when :lteq
778 a <= b
779 when :gt
780 a > b
781 when :gteq
782 a >= b
783 when :and
784 a and b
785 when :or
786 a or b
787 else
788 false
789 end
790 end
791 end
792 end