# class REXML::XPathParser

You don't want to use this class. Really. Use XPath, which is a wrapper for this class. Believe me. You don't want to poke around in here. There is strange, dark magic at work in this code. Beware. Go back! Go back while you still can!

### Constants

ALL

  Expr takes a stack of path elements and a set of nodes (either a Parent or an Array and returns an Array of matching nodes

ELEMENTS
LITERAL

### Public Class Methods

new( ) click to toggle source

    # File lib/rexml/xpath_parser.rb, line 49
    def initialize( )
      @parser = REXML::Parsers::XPathParser.new
      @namespaces = nil
      @variables = {}
    end

### Public Instance Methods

[]=( variable_name, value ) click to toggle source

    # File lib/rexml/xpath_parser.rb, line 80
    def []=( variable_name, value )
      @variables[ variable_name ] = value
    end

first( path_stack, node ) click to toggle source

Performs a depth-first (document order) XPath search, and returns the first match. This is the fastest, lightest way to return a single result.

FIXME: This method is incomplete!

    # File lib/rexml/xpath_parser.rb, line 89
    def first( path_stack, node )
      return nil if path.size == 0

      case path[0]
      when :document
        # do nothing
        return first( path[1..-1], node )
      when :child
        for c in node.children
          r = first( path[1..-1], c )
          return r if r
        end
      when :qname
        name = path[2]
        if node.name == name
          return node if path.size == 3
          return first( path[3..-1], node )
        else
          return nil
        end
      when :descendant_or_self
        r = first( path[1..-1], node )
        return r if r
        for c in node.children
          r = first( path, c )
          return r if r
        end
      when :node
        return first( path[1..-1], node )
      when :any
        return first( path[1..-1], node )
      end
      return nil
    end

get_first(path, nodeset) click to toggle source

    # File lib/rexml/xpath_parser.rb, line 70
    def get_first path, nodeset
      path_stack = @parser.parse( path )
      first( path_stack, nodeset )
    end

match( path_stack, nodeset ) click to toggle source

    # File lib/rexml/xpath_parser.rb, line 125
    def match( path_stack, nodeset )
      r = expr( path_stack, nodeset )
      r
    end

namespaces=( namespaces={} ) click to toggle source

    # File lib/rexml/xpath_parser.rb, line 55
    def namespaces=( namespaces={} )
      Functions::namespace_context = namespaces
      @namespaces = namespaces
    end

parse(path, nodeset) click to toggle source

    # File lib/rexml/xpath_parser.rb, line 65
    def parse path, nodeset
      path_stack = @parser.parse( path )
      match( path_stack, nodeset )
    end

predicate(path, nodeset) click to toggle source

    # File lib/rexml/xpath_parser.rb, line 75
    def predicate path, nodeset
      path_stack = @parser.parse( path )
      expr( path_stack, nodeset )
    end

variables=( vars={} ) click to toggle source

    # File lib/rexml/xpath_parser.rb, line 60
    def variables=( vars={} )
      Functions::variables = vars
      @variables = vars
    end

### Private Instance Methods

compare(a, op, b) click to toggle source

    # File lib/rexml/xpath_parser.rb, line 680
    def compare a, op, b
      case op
      when :eq
        a == b
      when :neq
        a != b
      when :lt
        a < b
      when :lteq
        a <= b
      when :gt
        a > b
      when :gteq
        a >= b
      when :and
        a and b
      when :or
        a or b
      else
        false
      end
    end

d_o_s( p, ns, r ) click to toggle source

    # File lib/rexml/xpath_parser.rb, line 476
    def d_o_s( p, ns, r )
      nt = nil
      ns.each_index do |i|
        n = ns[i]
        x = expr( p.dclone, [ n ] )
        nt = n.node_type
        d_o_s( p, n.children, x ) if nt == :element or nt == :document and n.children.size > 0
        r.concat(x) if x.size > 0
      end
    end

descendant_or_self( path_stack, nodeset ) click to toggle source

FIXME The next two methods are BAD MOJO! This is my achilles heel. If anybody thinks of a better way of doing this, be my guest. This really sucks, but it is a wonder it works at all. ########################################################

    # File lib/rexml/xpath_parser.rb, line 469
    def descendant_or_self( path_stack, nodeset )
      rs = []
      d_o_s( path_stack, nodeset, rs )
      document_order(rs.flatten.compact)
      #rs.flatten.compact
    end

document_order( array_of_nodes ) click to toggle source

Reorders an array of nodes so that they are in document order It tries to do this efficiently.

FIXME: I need to get rid of this, but the issue is that most of the XPath interpreter functions as a filter, which means that we lose context going in and out of function calls. If I knew what the index of the nodes was, I wouldn't have to do this. Maybe add a document IDX for each node? Problems with mutable documents. Or, rewrite everything.

    # File lib/rexml/xpath_parser.rb, line 496
    def document_order( array_of_nodes )
      new_arry = []
      array_of_nodes.each { |node|
        node_idx = []
        np = node.node_type == :attribute ? node.element : node
        while np.parent and np.parent.node_type == :element
          node_idx << np.parent.index( np )
          np = np.parent
        end
        new_arry << [ node_idx.reverse, node ]
      }
      new_arry.sort{ |s1, s2| s1[0] <=> s2[0] }.collect{ |s| s[1] }
    end

equality_relational_compare( set1, op, set2 ) click to toggle source

    # File lib/rexml/xpath_parser.rb, line 602
    def equality_relational_compare( set1, op, set2 )
      if set1.kind_of? Array and set2.kind_of? Array
        if set1.size == 1 and set2.size == 1
          set1 = set1[0]
          set2 = set2[0]
        elsif set1.size == 0 or set2.size == 0
          nd = set1.size==0 ? set2 : set1
          rv = nd.collect { |il| compare( il, op, nil ) }
          return rv
        else
          res = []
          SyncEnumerator.new( set1, set2 ).each { |i1, i2|
            i1 = norm( i1 )
            i2 = norm( i2 )
            res << compare( i1, op, i2 )
          }
          return res
        end
      end
      # If one is nodeset and other is number, compare number to each item
      # in nodeset s.t. number op number(string(item))
      # If one is nodeset and other is string, compare string to each item
      # in nodeset s.t. string op string(item)
      # If one is nodeset and other is boolean, compare boolean to each item
      # in nodeset s.t. boolean op boolean(item)
      if set1.kind_of? Array or set2.kind_of? Array
        if set1.kind_of? Array
          a = set1
          b = set2
        else
          a = set2
          b = set1
        end

        case b
        when true, false
          return a.collect {|v| compare( Functions::boolean(v), op, b ) }
        when Numeric
          return a.collect {|v| compare( Functions::number(v), op, b )}
        when /^\d+(\.\d+)?$/
          b = Functions::number( b )
          return a.collect {|v| compare( Functions::number(v), op, b )}
        else
          b = Functions::string( b )
          return a.collect { |v| compare( Functions::string(v), op, b ) }
        end
      else
        # If neither is nodeset,
        #   If op is = or !=
        #     If either boolean, convert to boolean
        #     If either number, convert to number
        #     Else, convert to string
        #   Else
        #     Convert both to numbers and compare
        s1 = set1.to_s
        s2 = set2.to_s
        if s1 == 'true' or s1 == 'false' or s2 == 'true' or s2 == 'false'
          set1 = Functions::boolean( set1 )
          set2 = Functions::boolean( set2 )
        else
          if op == :eq or op == :neq
            if s1 =~ /^\d+(\.\d+)?$/ or s2 =~ /^\d+(\.\d+)?$/
              set1 = Functions::number( s1 )
              set2 = Functions::number( s2 )
            else
              set1 = Functions::string( set1 )
              set2 = Functions::string( set2 )
            end
          else
            set1 = Functions::number( set1 )
            set2 = Functions::number( set2 )
          end
        end
        return compare( set1, op, set2 )
      end
      return false
    end

expr( path_stack, nodeset, context=nil ) click to toggle source

    # File lib/rexml/xpath_parser.rb, line 152
        def expr( path_stack, nodeset, context=nil )
          node_types = ELEMENTS
          return nodeset if path_stack.length == 0 || nodeset.length == 0
          while path_stack.length > 0
            if nodeset.length == 0
              path_stack.clear
              return []
            end
            case (op = path_stack.shift)
            when :document
              nodeset = [ nodeset[0].root_node ]

            when :qname
              prefix = path_stack.shift
              name = path_stack.shift
              nodeset.delete_if do |node|
                # FIXME: This DOUBLES the time XPath searches take
                ns = get_namespace( node, prefix )
                if node.node_type == :element
                  if node.name == name
                  end
                end
                !(node.node_type == :element and
                  node.name == name and
                  node.namespace == ns )
              end
              node_types = ELEMENTS

            when :any
              nodeset.delete_if { |node| !node_types.include?(node.node_type) }

            when :self
              # This space left intentionally blank

            when :processing_instruction
              target = path_stack.shift
              nodeset.delete_if do |node|
                (node.node_type != :processing_instruction) or
                ( target!='' and ( node.target != target ) )
              end

            when :text
              nodeset.delete_if { |node| node.node_type != :text }

            when :comment
              nodeset.delete_if { |node| node.node_type != :comment }

            when :node
              # This space left intentionally blank
              node_types = ALL

            when :child
              new_nodeset = []
              nt = nil
              nodeset.each do |node|
                nt = node.node_type
                new_nodeset += node.children if nt == :element or nt == :document
              end
              nodeset = new_nodeset
              node_types = ELEMENTS

            when :literal
              return path_stack.shift

            when :attribute
              new_nodeset = []
              case path_stack.shift
              when :qname
                prefix = path_stack.shift
                name = path_stack.shift
                for element in nodeset
                  if element.node_type == :element
                    attrib = element.attribute( name, get_namespace(element, prefix) )
                    new_nodeset << attrib if attrib
                  end
                end
              when :any
                for element in nodeset
                  if element.node_type == :element
                    new_nodeset += element.attributes.to_a
                  end
                end
              end
              nodeset = new_nodeset

            when :parent
              nodeset = nodeset.collect{|n| n.parent}.compact
              #nodeset = expr(path_stack.dclone, nodeset.collect{|n| n.parent}.compact)
              node_types = ELEMENTS

            when :ancestor
              new_nodeset = []
              nodeset.each do |node|
                while node.parent
                  node = node.parent
                  new_nodeset << node unless new_nodeset.include? node
                end
              end
              nodeset = new_nodeset
              node_types = ELEMENTS

            when :ancestor_or_self
              new_nodeset = []
              nodeset.each do |node|
                if node.node_type == :element
                  new_nodeset << node
                  while ( node.parent )
                    node = node.parent
                    new_nodeset << node unless new_nodeset.include? node
                  end
                end
              end
              nodeset = new_nodeset
              node_types = ELEMENTS

            when :predicate
              new_nodeset = []
              subcontext = { :size => nodeset.size }
              pred = path_stack.shift
              nodeset.each_with_index { |node, index|
                subcontext[ :node ] = node
                subcontext[ :index ] = index+1
                pc = pred.dclone
                result = expr( pc, [node], subcontext )
                result = result[0] if result.kind_of? Array and result.length == 1
                if result.kind_of? Numeric
                  new_nodeset << node if result == (index+1)
                elsif result.instance_of? Array
                  if result.size > 0 and result.inject(false) {|k,s| s or k}
                    new_nodeset << node if result.size > 0
                  end
                else
                  new_nodeset << node if result
                end
              }
              nodeset = new_nodeset
    =begin
              predicate = path_stack.shift
              ns = nodeset.clone
              result = expr( predicate, ns )
              if result.kind_of? Array
                nodeset = result.zip(ns).collect{|m,n| n if m}.compact
              else
                nodeset = result ? nodeset : []
              end
    =end

            when :descendant_or_self
              rv = descendant_or_self( path_stack, nodeset )
              path_stack.clear
              nodeset = rv
              node_types = ELEMENTS

            when :descendant
              results = []
              nt = nil
              nodeset.each do |node|
                nt = node.node_type
                results += expr( path_stack.dclone.unshift( :descendant_or_self ),
                  node.children ) if nt == :element or nt == :document
              end
              nodeset = results
              node_types = ELEMENTS

            when :following_sibling
              results = []
              nodeset.each do |node|
                next if node.parent.nil?
                all_siblings = node.parent.children
                current_index = all_siblings.index( node )
                following_siblings = all_siblings[ current_index+1 .. -1 ]
                results += expr( path_stack.dclone, following_siblings )
              end
              nodeset = results

            when :preceding_sibling
              results = []
              nodeset.each do |node|
                next if node.parent.nil?
                all_siblings = node.parent.children
                current_index = all_siblings.index( node )
                preceding_siblings = all_siblings[ 0, current_index ].reverse
                results += preceding_siblings
              end
              nodeset = results
              node_types = ELEMENTS

            when :preceding
              new_nodeset = []
              nodeset.each do |node|
                new_nodeset += preceding( node )
              end
              nodeset = new_nodeset
              node_types = ELEMENTS

            when :following
              new_nodeset = []
              nodeset.each do |node|
                new_nodeset += following( node )
              end
              nodeset = new_nodeset
              node_types = ELEMENTS

            when :namespace
              new_nodeset = []
              prefix = path_stack.shift
              nodeset.each do |node|
                if (node.node_type == :element or node.node_type == :attribute)
                  if @namespaces
                    namespaces = @namespaces
                  elsif (node.node_type == :element)
                    namespaces = node.namespaces
                  else
                    namespaces = node.element.namesapces
                  end
                  if (node.namespace == namespaces[prefix])
                    new_nodeset << node
                  end
                end
              end
              nodeset = new_nodeset

            when :variable
              var_name = path_stack.shift
              return @variables[ var_name ]

            # :and, :or, :eq, :neq, :lt, :lteq, :gt, :gteq
            # TODO: Special case for :or and :and -- not evaluate the right
            # operand if the left alone determines result (i.e. is true for
            # :or and false for :and).
            when :eq, :neq, :lt, :lteq, :gt, :gteq, :or
              left = expr( path_stack.shift, nodeset.dup, context )
              right = expr( path_stack.shift, nodeset.dup, context )
              res = equality_relational_compare( left, op, right )
              return res

            when :and
              left = expr( path_stack.shift, nodeset.dup, context )
              return [] unless left
              if left.respond_to?(:inject) and !left.inject(false) {|a,b| a | b}
                return []
              end
              right = expr( path_stack.shift, nodeset.dup, context )
              res = equality_relational_compare( left, op, right )
              return res

            when :div
              left = Functions::number(expr(path_stack.shift, nodeset, context)).to_f
              right = Functions::number(expr(path_stack.shift, nodeset, context)).to_f
              return (left / right)

            when :mod
              left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
              right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
              return (left % right)

            when :mult
              left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
              right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
              return (left * right)

            when :plus
              left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
              right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
              return (left + right)

            when :minus
              left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
              right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
              return (left - right)

            when :union
              left = expr( path_stack.shift, nodeset, context )
              right = expr( path_stack.shift, nodeset, context )
              return (left | right)

            when :neg
              res = expr( path_stack, nodeset, context )
              return -(res.to_f)

            when :not
            when :function
              func_name = path_stack.shift.tr('-','_')
              arguments = path_stack.shift
              subcontext = context ? nil : { :size => nodeset.size }

              res = []
              cont = context
              nodeset.each_with_index { |n, i|
                if subcontext
                  subcontext[:node]  = n
                  subcontext[:index] = i
                  cont = subcontext
                end
                arg_clone = arguments.dclone
                args = arg_clone.collect { |arg|
                  expr( arg, [n], cont )
                }
                Functions.context = cont
                res << Functions.send( func_name, *args )
              }
              return res

            end
          end # while
          return nodeset
        end

following( node ) click to toggle source

    # File lib/rexml/xpath_parser.rb, line 560
    def following( node )
      acc = []
      p = next_sibling_node( node )
      while p
        acc << p
        p = following_node_of( p )
      end
      acc
    end

following_node_of( node ) click to toggle source

    # File lib/rexml/xpath_parser.rb, line 570
    def following_node_of( node )
      if node.kind_of? Element and node.children.size > 0
        return node.children[0]
      end
      return next_sibling_node(node)
    end

get_namespace( node, prefix ) click to toggle source

Returns a String namespace for a node, given a prefix The rules are:

    1. Use the supplied namespace mapping first.
    2. If no mapping was supplied, use the context node to look up the namespace

    # File lib/rexml/xpath_parser.rb, line 138
    def get_namespace( node, prefix )
      if @namespaces
        return @namespaces[prefix] || ''
      else
        return node.namespace( prefix ) if node.node_type == :element
        return ''
      end
    end

next_sibling_node(node) click to toggle source

    # File lib/rexml/xpath_parser.rb, line 577
    def next_sibling_node(node)
      psn = node.next_sibling_node
      while psn.nil?
        if node.parent.nil? or node.parent.class == Document
          return nil
        end
        node = node.parent
        psn = node.next_sibling_node
      end
      return psn
    end

norm(b) click to toggle source

    # File lib/rexml/xpath_parser.rb, line 589
    def norm b
      case b
      when true, false
        return b
      when 'true', 'false'
        return Functions::boolean( b )
      when /^\d+(\.\d+)?$/
        return Functions::number( b )
      else
        return Functions::string( b )
      end
    end

preceding( node ) click to toggle source

Builds a nodeset of all of the preceding nodes of the supplied node, in reverse document order

preceding

  includes every element in the document that precedes this node,

except for ancestors

    # File lib/rexml/xpath_parser.rb, line 524
    def preceding( node )
      ancestors = []
      p = node.parent
      while p
        ancestors << p
        p = p.parent
      end

      acc = []
      p = preceding_node_of( node )
      while p
        if ancestors.include? p
          ancestors.delete(p)
        else
          acc << p
        end
        p = preceding_node_of( p )
      end
      acc
    end

preceding_node_of( node ) click to toggle source

    # File lib/rexml/xpath_parser.rb, line 545
    def preceding_node_of( node )
      psn = node.previous_sibling_node
      if psn.nil?
        if node.parent.nil? or node.parent.class == Document
          return nil
        end
        return node.parent
        #psn = preceding_node_of( node.parent )
      end
      while psn and psn.kind_of? Element and psn.children.size > 0
        psn = psn.children[-1]
      end
      psn
    end

recurse( nodeset ) { |node| ... } click to toggle source

    # File lib/rexml/xpath_parser.rb, line 511
    def recurse( nodeset, &block )
      for node in nodeset
        yield node
        recurse( node, &block ) if node.node_type == :element
      end
    end