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

LITERAL

Public Class Methods

new(strict: false) click to toggle source
# File lib/rexml/xpath_parser.rb, line 50
def initialize(strict: false)
  @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 83
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 92
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 73
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 128
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 58
def namespaces=( namespaces={} )
  Functions::namespace_context = namespaces
  @namespaces = namespaces
end
parse(path, nodeset) click to toggle source
# File lib/rexml/xpath_parser.rb, line 68
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 78
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 63
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 763
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, op, b) click to toggle source
# File lib/rexml/xpath_parser.rb, line 882
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
descendant(nodeset, include_self) click to toggle source
# File lib/rexml/xpath_parser.rb, line 666
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 677
  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

  # 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
  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

  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

  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

  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

  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

  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

  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

  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 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 unnode(a) {|v| compare( Functions::boolean(v), op, b ) }
      when Numeric
        return unnode(a) {|v| compare( Functions::number(v), op, b )}
      when /^\d+(\.\d+)?$/
        b = Functions::number( b )
        return unnode(a) {|v| compare( Functions::number(v), op, b )}
      else
        b = Functions::string( b )
        return unnode(a) { |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
      set1 = unnode(set1) if set1.is_a?(Array)
      set2 = unnode(set2) if set2.is_a?(Array)
      s1 = Functions.string(set1)
      s2 = Functions.string(set2)
      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

  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

  def unnode(nodeset)
    nodeset.collect do |node|
      if node.is_a?(XPathNode)
        unnoded = node.raw_node
      else
        unnoded = node
      end
      unnoded = yield(unnoded) if block_given?
      unnoded
    end
  end
end
enter(tag, *args) click to toggle source
# File lib/rexml/xpath_parser.rb, line 625
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 803
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 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 unnode(a) {|v| compare( Functions::boolean(v), op, b ) }
    when Numeric
      return unnode(a) {|v| compare( Functions::number(v), op, b )}
    when /^\d+(\.\d+)?$/
      b = Functions::number( b )
      return unnode(a) {|v| compare( Functions::number(v), op, b )}
    else
      b = Functions::string( b )
      return unnode(a) { |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
    set1 = unnode(set1) if set1.is_a?(Array)
    set2 = unnode(set2) if set2.is_a?(Array)
    s1 = Functions.string(set1)
    s2 = Functions.string(set2)
    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
evaluate_predicate(expression, nodesets) click to toggle source
# File lib/rexml/xpath_parser.rb, line 581
def evaluate_predicate(expression, nodesets)
  # enter(:predicate, expression, nodesets)
  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)
      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)
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 164
def expr( path_stack, nodeset, context=nil )
  # enter(:expr, path_stack, nodeset)
  return nodeset if path_stack.length == 0 || nodeset.length == 0
  while path_stack.length > 0
    # trace(:while, path_stack, nodeset)
    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)
      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]]

    # :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 )
      # trace(op, left, right, res)
      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, :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
      subcontext = context ? nil : { :size => nodeset.size }

      res = []
      cont = context
      nodeset.each_with_index do |node, i|
        if subcontext
          if node.is_a?(XPathNode)
            subcontext[:node]  = node.raw_node
            subcontext[:index] = node.position
          else
            subcontext[:node]  = node
            subcontext[:index] = i
          end
          cont = subcontext
        end
        arg_clone = arguments.dclone
        args = arg_clone.collect do |arg|
          result = expr( arg, [node], cont )
          result = unnode(result) if result.is_a?(Array)
          result
        end
        Functions.context = cont
        res << Functions.send( func_name, *args )
      end
      return res

    else
      raise "[BUG] Unexpected path: <#{op.inspect}>: <#{path_stack.inspect}>"
    end
  end # while
  return nodeset
# ensure
#   leave(:expr, path_stack, nodeset)
end
filter_nodeset(nodeset) { |node| ... } click to toggle source
# File lib/rexml/xpath_parser.rb, line 572
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 733
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 744
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 152
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 630
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 751
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 465
def node_test(path_stack, nodesets, any_type: :element)
  # enter(:node_test, path_stack, nodesets)
  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?
            # FIXME: This DOUBLES the time XPath searches take
            raw_node.name == name and
              raw_node.namespace == raw_node.element.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)
end
norm(b) click to toggle source
# File lib/rexml/xpath_parser.rb, line 790
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
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 696
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 718
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 643
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 429
def step(path_stack, any_type: :element, order: :forward)
  nodesets = yield
  begin
    # enter(:step, path_stack, nodesets)
    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)
  end
end
strict?() click to toggle source
# File lib/rexml/xpath_parser.rb, line 143
def strict?
  @strict
end
trace(*args) click to toggle source
# File lib/rexml/xpath_parser.rb, line 620
def trace(*args)
  indent = "  " * @nest
  puts("#{indent}#{args.inspect}")
end
unnode(nodeset) { |unnoded| ... } click to toggle source
# File lib/rexml/xpath_parser.rb, line 905
def unnode(nodeset)
  nodeset.collect do |node|
    if node.is_a?(XPathNode)
      unnoded = node.raw_node
    else
      unnoded = node
    end
    unnoded = yield(unnoded) if block_given?
    unnoded
  end
end