File: rexml/xpath_parser.rb

Overview
Module Structure
Class Hierarchy
Code

Overview

Module Structure

  module: <Toplevel Module>
  module: <Built-in Module>
  class: Symbol#12
inherits from
  Object ( Builtin-Module )
has properties
method: dclone #13
  class: Fixnum#15
inherits from
  Object ( Builtin-Module )
has properties
method: dclone #16
  class: Float#18
inherits from
  Object ( Builtin-Module )
has properties
method: dclone #19
  class: Array#21
inherits from
  Object ( Builtin-Module )
has properties
method: dclone #22
  module: REXML#30
  class: XPathParser#35
includes
  XMLTokens ( REXML )
inherits from
  Object ( Builtin-Module )
has properties
constant: LITERAL #37
method: initialize / 1 #39
method: namespaces= / 1 #45
method: variables= / 1 #50
method: parse #55
method: get_first #63
method: predicate #71
method: []= / 2 #76
method: first / 2 #85
method: match / 2 #126
method: get_namespace / 2 #142
constant: ALL #154
constant: ELEMENTS #155
method: expr / 3 #156
method: descendant_or_self / 2 #522
method: d_o_s / 3 #533
method: document_order / 1 #555
method: recurse / 2 #571
method: preceding / 1 #584
method: preceding_node_of / 1 #608
method: following / 1 #626
method: following_node_of / 1 #639
method: next_sibling_node / 1 #649
method: norm #662
method: equality_relational_compare / 3 #675
method: compare #768

Class Hierarchy

Object ( Builtin-Module )
  Symbol    #12
  Fixnum    #15
  Float    #18
  Array    #21
  XPathParser ( REXML ) #35

Code

   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