PageRenderTime 149ms CodeModel.GetById 76ms app.highlight 66ms RepoModel.GetById 1ms app.codeStats 0ms

/tools/Ruby/lib/ruby/1.8/rexml/xpath_parser.rb

http://github.com/agross/netopenspace
Ruby | 792 lines | 723 code | 16 blank | 53 comment | 38 complexity | a61e48eb468b67d28b01382d8eb55556 MD5 | raw file
  1require 'rexml/namespace'
  2require 'rexml/xmltokens'
  3require 'rexml/attribute'
  4require 'rexml/syncenumerator'
  5require 'rexml/parsers/xpathparser'
  6
  7class Object
  8  def dclone
  9    clone
 10  end
 11end
 12class Symbol
 13  def dclone ; self ; end
 14end
 15class Fixnum
 16  def dclone ; self ; end
 17end
 18class Float
 19  def dclone ; self ; end
 20end
 21class Array
 22  def dclone
 23    klone = self.clone
 24    klone.clear
 25    self.each{|v| klone << v.dclone}
 26    klone
 27  end
 28end
 29
 30module 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
792end