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

DEBUG
LITERAL

Public Class Methods

new(strict: false) click to toggle source
# File lib/rexml/xpath_parser.rb, line 54
def initialize(strict: false)
  @debug = DEBUG
  @parser = REXML::Parsers::XPathParser.new
  @namespaces = nil
  @variables = {}
  @nest = 0
  @strict = strict
end

Public Instance Methods

[]=( variable_name, value ) click to toggle source
# File lib/rexml/xpath_parser.rb, line 88
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 97
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 78
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 133
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={} ) click to toggle source
# File lib/rexml/xpath_parser.rb, line 63
def namespaces=( namespaces={} )
  Functions::namespace_context = namespaces
  @namespaces = namespaces
end
parse(path, nodeset) click to toggle source
# File lib/rexml/xpath_parser.rb, line 73
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 83
def predicate path, nodeset
  path_stack = @parser.parse( path )
  match( path_stack, nodeset )
end
variables=( vars={} ) click to toggle source
# File lib/rexml/xpath_parser.rb, line 68
def variables=( vars={} )
  Functions::variables = vars
  @variables = vars
end

Private Instance Methods

child(nodeset) click to toggle source
# File lib/rexml/xpath_parser.rb, line 769
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) click to toggle source
# File lib/rexml/xpath_parser.rb, line 910
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) click to toggle source
# File lib/rexml/xpath_parser.rb, line 672
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) click to toggle source
# File lib/rexml/xpath_parser.rb, line 683
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) { |unnoded| ... } click to toggle source
# File lib/rexml/xpath_parser.rb, line 932
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) click to toggle source
# File lib/rexml/xpath_parser.rb, line 631
def enter(tag, *args)
  trace(:enter, tag, *args)
  @nest += 1
end
equality_relational_compare(set1, op, set2) click to toggle source
# File lib/rexml/xpath_parser.rb, line 809
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) click to toggle source
# File lib/rexml/xpath_parser.rb, line 585
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 ) click to toggle source

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

# File lib/rexml/xpath_parser.rb, line 169
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) { |node| ... } click to toggle source
# File lib/rexml/xpath_parser.rb, line 576
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
following(node) click to toggle source
# File lib/rexml/xpath_parser.rb, line 739
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 ) click to toggle source
# File lib/rexml/xpath_parser.rb, line 750
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 157
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) click to toggle source
# File lib/rexml/xpath_parser.rb, line 636
def leave(tag, *args)
  @nest -= 1
  trace(:leave, tag, *args)
end
next_sibling_node(node) click to toggle source
# File lib/rexml/xpath_parser.rb, line 757
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) click to toggle source
# File lib/rexml/xpath_parser.rb, line 471
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) click to toggle source
# File lib/rexml/xpath_parser.rb, line 796
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) click to toggle source
# File lib/rexml/xpath_parser.rb, line 884
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
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 702
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 ) click to toggle source
# File lib/rexml/xpath_parser.rb, line 724
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
sort(array_of_nodes, order) 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 649
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) { || ... } click to toggle source
# File lib/rexml/xpath_parser.rb, line 435
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
strict?() click to toggle source
# File lib/rexml/xpath_parser.rb, line 148
def strict?
  @strict
end
trace(*args) click to toggle source
# File lib/rexml/xpath_parser.rb, line 624
def trace(*args)
  indent = "  " * @nest
  PP.pp(args, "").each_line do |line|
    puts("#{indent}#{line}")
  end
end
unnode(nodeset) { |unnoded| ... } click to toggle source
# File lib/rexml/xpath_parser.rb, line 944
def unnode(nodeset)
  each_unnode(nodeset).collect do |unnoded|
    unnoded = yield(unnoded) if block_given?
    unnoded
  end
end
value_type(value) click to toggle source
# File lib/rexml/xpath_parser.rb, line 871
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