123456789_123456789_123456789_123456789_123456789_

Class: REXML::XPathParser

Relationships & Source Files
Super Chains via Extension / Inclusion / Inheritance
Instance Chain:
self, XMLTokens
Inherits: Object
Defined in: lib/rexml/xpath_parser.rb

Overview

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!

Constant Summary

XMLTokens - Included

NAME, NAMECHAR, NAME_CHAR, NAME_START_CHAR, NAME_STR, NCNAME_STR, NMTOKEN, NMTOKENS, REFERENCE

Class Method Summary

Instance Attribute Summary

Instance Method Summary

Constructor Details

.new(strict: false) ⇒ XPathParser

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 60

def initialize(strict: false)
  @debug = DEBUG
  @parser = REXML::Parsers::XPathParser.new
  @namespaces = nil
  @variables = {}
  @nest = 0
  @strict = strict
end

Instance Attribute Details

#strict?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 154

def strict?
  @strict
end

Instance Method Details

#[]=(variable_name, value)

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 94

def []=( variable_name, value )
  @variables[ variable_name ] = value
end

#child(nodeset) (private)

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 775

def child(nodeset)
  nodesets = []
  nodeset.each do |node|
    raw_node = node.raw_node
    node_type = raw_node.node_type
    # trace(:child, node_type, node)
    case node_type
    when :element
      nodesets << raw_node.children.collect.with_index do |child_node, i|
        XPathNode.new(child_node, position: i + 1)
      end
    when :document
      new_nodeset = []
      raw_node.children.each do |child|
        case child
        when XMLDecl, Text
          # Ignore
        else
          new_nodeset << XPathNode.new(child, position: new_nodeset.size + 1)
        end
      end
      nodesets << new_nodeset unless new_nodeset.empty?
    end
  end
  nodesets
end

#compare(a, operator, b) (private)

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 916

def compare(a, operator, b)
  a, b = normalize_compare_values(a, operator, b)
  case operator
  when :eq
    a == b
  when :neq
    a != b
  when :lt
    a < b
  when :lteq
    a <= b
  when :gt
    a > b
  when :gteq
    a >= b
  else
    message = "[BUG] Unexpected compare operator: " +
      "<#{operator.inspect}>: <#{a.inspect}>: <#{b.inspect}>"
    raise message
  end
end

#descendant(nodeset, include_self) (private)

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 678

def descendant(nodeset, include_self)
  nodesets = []
  nodeset.each do |node|
    new_nodeset = []
    new_nodes = {}
    descendant_recursive(node.raw_node, new_nodeset, new_nodes, include_self)
    nodesets << new_nodeset unless new_nodeset.empty?
  end
  nodesets
end

#descendant_recursive(raw_node, new_nodeset, new_nodes, include_self) (private)

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 689

def descendant_recursive(raw_node, new_nodeset, new_nodes, include_self)
  if include_self
    return if new_nodes.key?(raw_node)
    new_nodeset << XPathNode.new(raw_node, position: new_nodeset.size + 1)
    new_nodes[raw_node] = true
  end

  node_type = raw_node.node_type
  if node_type == :element or node_type == :document
    raw_node.children.each do |child|
      descendant_recursive(child, new_nodeset, new_nodes, true)
    end
  end
end

#each_unnode(nodeset) (private)

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 938

def each_unnode(nodeset)
  return to_enum(__method__, nodeset) unless block_given?
  nodeset.each do |node|
    if node.is_a?(XPathNode)
      unnoded = node.raw_node
    else
      unnoded = node
    end
    yield(unnoded)
  end
end

#enter(tag, *args) (private)

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 637

def enter(tag, *args)
  trace(:enter, tag, *args)
  @nest += 1
end

#equality_relational_compare(set1, op, set2) (private)

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 815

def equality_relational_compare(set1, op, set2)
  set1 = unnode(set1) if set1.is_a?(Array)
  set2 = unnode(set2) if set2.is_a?(Array)

  if set1.kind_of? Array and set2.kind_of? Array
    # If both objects to be compared are node-sets, then the
    # comparison will be true if and only if there is a node in the
    # first node-set and a node in the second node-set such that the
    # result of performing the comparison on the string-values of
    # the two nodes is true.
    set1.product(set2).any? do |node1, node2|
      node_string1 = Functions.string(node1)
      node_string2 = Functions.string(node2)
      compare(node_string1, op, node_string2)
    end
  elsif set1.kind_of? Array or set2.kind_of? Array
    # 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
      a = set1
      b = set2
    else
      a = set2
      b = set1
    end

    case b
    when true, false
      each_unnode(a).any? do |unnoded|
        compare(Functions.boolean(unnoded), op, b)
      end
    when Numeric
      each_unnode(a).any? do |unnoded|
        compare(Functions.number(unnoded), op, b)
      end
    when /\A\d(\.\d)?\z/
      b = Functions.number(b)
      each_unnode(a).any? do |unnoded|
        compare(Functions.number(unnoded), op, b)
      end
    else
      b = Functions::string(b)
      each_unnode(a).any? do |unnoded|
        compare(Functions::string(unnoded), op, b)
      end
    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
    compare(set1, op, set2)
  end
end

#evaluate_predicate(expression, nodesets) (private)

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 591

def evaluate_predicate(expression, nodesets)
  enter(:predicate, expression, nodesets) if @debug
  new_nodesets = nodesets.collect do |nodeset|
    new_nodeset = []
    subcontext = { :size => nodeset.size }
    nodeset.each_with_index do |node, index|
      if node.is_a?(XPathNode)
        subcontext[:node] = node.raw_node
        subcontext[:index] = node.position
      else
        subcontext[:node] = node
        subcontext[:index] = index + 1
      end
      result = expr(expression.dclone, [node], subcontext)
      trace(:predicate_evaluate, expression, node, subcontext, result) if @debug
      result = result[0] if result.kind_of? Array and result.length == 1
      if result.kind_of? Numeric
        if result == node.position
          new_nodeset << XPathNode.new(node, position: new_nodeset.size + 1)
        end
      elsif result.instance_of? Array
        if result.size > 0 and result.inject(false) {|k,s| s or k}
          if result.size > 0
            new_nodeset << XPathNode.new(node, position: new_nodeset.size + 1)
          end
        end
      else
        if result
          new_nodeset << XPathNode.new(node, position: new_nodeset.size + 1)
        end
      end
    end
    new_nodeset
  end
  new_nodesets
ensure
  leave(:predicate, new_nodesets) if @debug
end

#expr(path_stack, nodeset, context = nil) (private)

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

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 175

def expr( path_stack, nodeset, context=nil )
  enter(:expr, path_stack, nodeset) if @debug
  return nodeset if path_stack.length == 0 || nodeset.length == 0
  while path_stack.length > 0
    trace(:while, path_stack, nodeset) if @debug
    if nodeset.length == 0
      path_stack.clear
      return []
    end
    op = path_stack.shift
    case op
    when :document
      first_raw_node = nodeset.first.raw_node
      nodeset = [XPathNode.new(first_raw_node.root_node, position: 1)]
    when :self
      nodeset = step(path_stack) do
        [nodeset]
      end
    when :child
      nodeset = step(path_stack) do
        child(nodeset)
      end
    when :literal
      trace(:literal, path_stack, nodeset) if @debug
      return path_stack.shift
    when :attribute
      nodeset = step(path_stack, any_type: :attribute) do
        nodesets = []
        nodeset.each do |node|
          raw_node = node.raw_node
          next unless raw_node.node_type == :element
          attributes = raw_node.attributes
          next if attributes.empty?
          nodesets << attributes.each_attribute.collect.with_index do |attribute, i|
            XPathNode.new(attribute, position: i + 1)
          end
        end
        nodesets
      end
    when :namespace
      pre_defined_namespaces = {
        "xml" => "http://www.w3.org/XML/1998/namespace",
      }
      nodeset = step(path_stack, any_type: :namespace) do
        nodesets = []
        nodeset.each do |node|
          raw_node = node.raw_node
          case raw_node.node_type
          when :element
            if @namespaces
              nodesets << pre_defined_namespaces.merge(@namespaces)
            else
              nodesets << pre_defined_namespaces.merge(raw_node.namespaces)
            end
          when :attribute
            if @namespaces
              nodesets << pre_defined_namespaces.merge(@namespaces)
            else
              nodesets << pre_defined_namespaces.merge(raw_node.element.namespaces)
            end
          end
        end
        nodesets
      end
    when :parent
      nodeset = step(path_stack) do
        nodesets = []
        nodeset.each do |node|
          raw_node = node.raw_node
          if raw_node.node_type == :attribute
            parent = raw_node.element
          else
            parent = raw_node.parent
          end
          nodesets << [XPathNode.new(parent, position: 1)] if parent
        end
        nodesets
      end
    when :ancestor
      nodeset = step(path_stack) do
        nodesets = []
        # new_nodes = {}
        nodeset.each do |node|
          raw_node = node.raw_node
          new_nodeset = []
          while raw_node.parent
            raw_node = raw_node.parent
            # next if new_nodes.key?(node)
            new_nodeset << XPathNode.new(raw_node,
                                         position: new_nodeset.size + 1)
            # new_nodes[node] = true
          end
          nodesets << new_nodeset unless new_nodeset.empty?
        end
        nodesets
      end
    when :ancestor_or_self
      nodeset = step(path_stack) do
        nodesets = []
        # new_nodes = {}
        nodeset.each do |node|
          raw_node = node.raw_node
          next unless raw_node.node_type == :element
          new_nodeset = [XPathNode.new(raw_node, position: 1)]
          # new_nodes[node] = true
          while raw_node.parent
            raw_node = raw_node.parent
            # next if new_nodes.key?(node)
            new_nodeset << XPathNode.new(raw_node,
                                         position: new_nodeset.size + 1)
            # new_nodes[node] = true
          end
          nodesets << new_nodeset unless new_nodeset.empty?
        end
        nodesets
      end
    when :descendant_or_self
      nodeset = step(path_stack) do
        descendant(nodeset, true)
      end
    when :descendant
      nodeset = step(path_stack) do
        descendant(nodeset, false)
      end
    when :following_sibling
      nodeset = step(path_stack) do
        nodesets = []
        nodeset.each do |node|
          raw_node = node.raw_node
          next unless raw_node.respond_to?(:parent)
          next if raw_node.parent.nil?
          all_siblings = raw_node.parent.children
          current_index = all_siblings.index(raw_node)
          following_siblings = all_siblings[(current_index + 1)..-1]
          next if following_siblings.empty?
          nodesets << following_siblings.collect.with_index do |sibling, i|
            XPathNode.new(sibling, position: i + 1)
          end
        end
        nodesets
      end
    when :preceding_sibling
      nodeset = step(path_stack, order: :reverse) do
        nodesets = []
        nodeset.each do |node|
          raw_node = node.raw_node
          next unless raw_node.respond_to?(:parent)
          next if raw_node.parent.nil?
          all_siblings = raw_node.parent.children
          current_index = all_siblings.index(raw_node)
          preceding_siblings = all_siblings[0, current_index].reverse
          next if preceding_siblings.empty?
          nodesets << preceding_siblings.collect.with_index do |sibling, i|
            XPathNode.new(sibling, position: i + 1)
          end
        end
        nodesets
      end
    when :preceding
      nodeset = step(path_stack, order: :reverse) do
        unnode(nodeset) do |node|
          preceding(node)
        end
      end
    when :following
      nodeset = step(path_stack) do
        unnode(nodeset) do |node|
          following(node)
        end
      end
    when :variable
      var_name = path_stack.shift
      return [@variables[var_name]]

    when :eq, :neq, :lt, :lteq, :gt, :gteq
      left = expr( path_stack.shift, nodeset.dup, context )
      right = expr( path_stack.shift, nodeset.dup, context )
      res = equality_relational_compare( left, op, right )
      trace(op, left, right, res) if @debug
      return res

    when :or
      left = expr(path_stack.shift, nodeset.dup, context)
      return true if Functions.boolean(left)
      right = expr(path_stack.shift, nodeset.dup, context)
      return Functions.boolean(right)

    when :and
      left = expr(path_stack.shift, nodeset.dup, context)
      return false unless Functions.boolean(left)
      right = expr(path_stack.shift, nodeset.dup, context)
      return Functions.boolean(right)

    when :div, :mod, :mult, :plus, :minus
      left = expr(path_stack.shift, nodeset, context)
      right = expr(path_stack.shift, nodeset, context)
      left = unnode(left) if left.is_a?(Array)
      right = unnode(right) if right.is_a?(Array)
      left = Functions::number(left)
      right = Functions::number(right)
      case op
      when :div
        return left / right
      when :mod
        return left % right
      when :mult
        return left * right
      when :plus
        return left + right
      when :minus
        return left - right
      else
        raise "[BUG] Unexpected operator: <#{op.inspect}>"
      end
    when :union
      left = expr( path_stack.shift, nodeset, context )
      right = expr( path_stack.shift, nodeset, context )
      left = unnode(left) if left.is_a?(Array)
      right = unnode(right) if right.is_a?(Array)
      return (left | right)
    when :neg
      res = expr( path_stack, nodeset, context )
      res = unnode(res) if res.is_a?(Array)
      return -Functions.number(res)
    when :not
    when :function
      func_name = path_stack.shift.tr('-','_')
      arguments = path_stack.shift

      if nodeset.size != 1
        message = "[BUG] Node set size must be 1 for function call: "
        message += "<#{func_name}>: <#{nodeset.inspect}>: "
        message += "<#{arguments.inspect}>"
        raise message
      end

      node = nodeset.first
      if context
        target_context = context
      else
        target_context = {:size => nodeset.size}
        if node.is_a?(XPathNode)
          target_context[:node]  = node.raw_node
          target_context[:index] = node.position
        else
          target_context[:node]  = node
          target_context[:index] = 1
        end
      end
      args = arguments.dclone.collect do |arg|
        result = expr(arg, nodeset, target_context)
        result = unnode(result) if result.is_a?(Array)
        result
      end
      Functions.context = target_context
      return Functions.send(func_name, *args)

    else
      raise "[BUG] Unexpected path: <#{op.inspect}>: <#{path_stack.inspect}>"
    end
  end # while
  return nodeset
ensure
  leave(:expr, path_stack, nodeset) if @debug
end

#filter_nodeset(nodeset) (private)

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 582

def filter_nodeset(nodeset)
  new_nodeset = []
  nodeset.each do |node|
    next unless yield(node)
    new_nodeset << XPathNode.new(node, position: new_nodeset.size + 1)
  end
  new_nodeset
end

#first(path_stack, node)

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!

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 103

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

#following(node) (private)

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 745

def following(node)
  followings = []
  following_node = next_sibling_node(node)
  while following_node
    followings << XPathNode.new(following_node,
                                position: followings.size + 1)
    following_node = following_node_of(following_node)
  end
  followings
end

#following_node_of(node) (private)

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 756

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_first(path, nodeset)

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 84

def get_first path, nodeset
  path_stack = @parser.parse( path )
  first( path_stack, nodeset )
end

#get_namespace(node, prefix) (private)

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
[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 163

def get_namespace( node, prefix )
  if @namespaces
    return @namespaces[prefix] || ''
  else
    return node.namespace( prefix ) if node.node_type == :element
    return ''
  end
end

#leave(tag, *args) (private)

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 642

def leave(tag, *args)
  @nest -= 1
  trace(:leave, tag, *args)
end

#match(path_stack, nodeset)

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 139

def match(path_stack, nodeset)
  nodeset = nodeset.collect.with_index do |node, i|
    position = i + 1
    XPathNode.new(node, position: position)
  end
  result = expr(path_stack, nodeset)
  case result
  when Array # nodeset
    unnode(result)
  else
    [result]
  end
end

#namespaces=(namespaces = {})

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 69

def namespaces=( namespaces={} )
  Functions::namespace_context = namespaces
  @namespaces = namespaces
end

#next_sibling_node(node) (private)

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 763

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

#node_test(path_stack, nodesets, any_type: :element) (private)

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 477

def node_test(path_stack, nodesets, any_type: :element)
  enter(:node_test, path_stack, nodesets) if @debug
  operator = path_stack.shift
  case operator
  when :qname
    prefix = path_stack.shift
    name = path_stack.shift
    new_nodesets = nodesets.collect do |nodeset|
      filter_nodeset(nodeset) do |node|
        raw_node = node.raw_node
        case raw_node.node_type
        when :element
          if prefix.nil?
            raw_node.name == name
          elsif prefix.empty?
            if strict?
              raw_node.name == name and raw_node.namespace == ""
            else
              # FIXME: This DOUBLES the time XPath searches take
              ns = get_namespace(raw_node, prefix)
              raw_node.name == name and raw_node.namespace == ns
            end
          else
            # FIXME: This DOUBLES the time XPath searches take
            ns = get_namespace(raw_node, prefix)
            raw_node.name == name and raw_node.namespace == ns
          end
        when :attribute
          if prefix.nil?
            raw_node.name == name
          elsif prefix.empty?
            raw_node.name == name and raw_node.namespace == ""
          else
            # FIXME: This DOUBLES the time XPath searches take
            ns = get_namespace(raw_node.element, prefix)
            raw_node.name == name and raw_node.namespace == ns
          end
        else
          false
        end
      end
    end
  when :namespace
    prefix = path_stack.shift
    new_nodesets = nodesets.collect do |nodeset|
      filter_nodeset(nodeset) do |node|
        raw_node = node.raw_node
        case raw_node.node_type
        when :element
          namespaces = @namespaces || raw_node.namespaces
          raw_node.namespace == namespaces[prefix]
        when :attribute
          namespaces = @namespaces || raw_node.element.namespaces
          raw_node.namespace == namespaces[prefix]
        else
          false
        end
      end
    end
  when :any
    new_nodesets = nodesets.collect do |nodeset|
      filter_nodeset(nodeset) do |node|
        raw_node = node.raw_node
        raw_node.node_type == any_type
      end
    end
  when :comment
    new_nodesets = nodesets.collect do |nodeset|
      filter_nodeset(nodeset) do |node|
        raw_node = node.raw_node
        raw_node.node_type == :comment
      end
    end
  when :text
    new_nodesets = nodesets.collect do |nodeset|
      filter_nodeset(nodeset) do |node|
        raw_node = node.raw_node
        raw_node.node_type == :text
      end
    end
  when :processing_instruction
    target = path_stack.shift
    new_nodesets = nodesets.collect do |nodeset|
      filter_nodeset(nodeset) do |node|
        raw_node = node.raw_node
        (raw_node.node_type == :processing_instruction) and
          (target.empty? or (raw_node.target == target))
      end
    end
  when :node
    new_nodesets = nodesets.collect do |nodeset|
      filter_nodeset(nodeset) do |node|
        true
      end
    end
  else
    message = "[BUG] Unexpected node test: " +
      "<#{operator.inspect}>: <#{path_stack.inspect}>"
    raise message
  end
  new_nodesets
ensure
  leave(:node_test, path_stack, new_nodesets) if @debug
end

#norm(b) (private)

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 802

def norm b
  case b
  when true, false
    return b
  when 'true', 'false'
    return Functions::boolean( b )
  when /^\d(\.\d)?$/, Numeric
    return Functions::number( b )
  else
    return Functions::string( b )
  end
end

#normalize_compare_values(a, operator, b) (private)

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 890

def normalize_compare_values(a, operator, b)
  a_type = value_type(a)
  b_type = value_type(b)
  case operator
  when :eq, :neq
    if a_type == :boolean or b_type == :boolean
      a = Functions.boolean(a) unless a_type == :boolean
      b = Functions.boolean(b) unless b_type == :boolean
    elsif a_type == :number or b_type == :number
      a = Functions.number(a) unless a_type == :number
      b = Functions.number(b) unless b_type == :number
    else
      a = Functions.string(a) unless a_type == :string
      b = Functions.string(b) unless b_type == :string
    end
  when :lt, :lteq, :gt, :gteq
    a = Functions.number(a) unless a_type == :number
    b = Functions.number(b) unless b_type == :number
  else
    message = "[BUG] Unexpected compare operator: " +
      "<#{operator.inspect}>: <#{a.inspect}>: <#{b.inspect}>"
    raise message
  end
  [a, b]
end

#parse(path, nodeset)

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 79

def parse path, nodeset
  path_stack = @parser.parse( path )
  match( path_stack, nodeset )
end

#preceding(node) (private)

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

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 708

def preceding(node)
  ancestors = []
  parent = node.parent
  while parent
    ancestors << parent
    parent = parent.parent
  end

  precedings = []
  preceding_node = preceding_node_of(node)
  while preceding_node
    if ancestors.include?(preceding_node)
      ancestors.delete(preceding_node)
    else
      precedings << XPathNode.new(preceding_node,
                                  position: precedings.size + 1)
    end
    preceding_node = preceding_node_of(preceding_node)
  end
  precedings
end

#preceding_node_of(node) (private)

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 730

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

#predicate(path, nodeset)

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 89

def predicate path, nodeset
  path_stack = @parser.parse( path )
  match( path_stack, nodeset )
end

#sort(array_of_nodes, order) (private)

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.

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 655

def sort(array_of_nodes, order)
  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 ]
  }
  ordered = new_arry.sort_by do |index, node|
    if order == :forward
      index
    else
      -index
    end
  end
  ordered.collect do |_index, node|
    node
  end
end

#step(path_stack, any_type: :element, order: :forward) (private)

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 441

def step(path_stack, any_type: :element, order: :forward)
  nodesets = yield
  begin
    enter(:step, path_stack, nodesets) if @debug
    nodesets = node_test(path_stack, nodesets, any_type: any_type)
    while path_stack[0] == :predicate
      path_stack.shift # :predicate
      predicate_expression = path_stack.shift.dclone
      nodesets = evaluate_predicate(predicate_expression, nodesets)
    end
    if nodesets.size == 1
      ordered_nodeset = nodesets[0]
    else
      raw_nodes = []
      nodesets.each do |nodeset|
        nodeset.each do |node|
          if node.respond_to?(:raw_node)
            raw_nodes << node.raw_node
          else
            raw_nodes << node
          end
        end
      end
      ordered_nodeset = sort(raw_nodes, order)
    end
    new_nodeset = []
    ordered_nodeset.each do |node|
      # TODO: Remove duplicated
      new_nodeset << XPathNode.new(node, position: new_nodeset.size + 1)
    end
    new_nodeset
  ensure
    leave(:step, path_stack, new_nodeset) if @debug
  end
end

#trace(*args) (private)

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 630

def trace(*args)
  indent = "  " * @nest
  PP.pp(args, "").each_line do |line|
    puts("#{indent}#{line}")
  end
end

#unnode(nodeset) (private)

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 950

def unnode(nodeset)
  each_unnode(nodeset).collect do |unnoded|
    unnoded = yield(unnoded) if block_given?
    unnoded
  end
end

#value_type(value) (private)

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 877

def value_type(value)
  case value
  when true, false
    :boolean
  when Numeric
    :number
  when String
    :string
  else
    raise "[BUG] Unexpected value type: <#{value.inspect}>"
  end
end

#variables=(vars = {})

[ GitHub ]

  
# File 'lib/rexml/xpath_parser.rb', line 74

def variables=( vars={} )
  Functions::variables = vars
  @variables = vars
end