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