class REXML::XPathParser

你不应该使用这个类。真的。请使用 XPath,它是这个类的封装器。相信我。你不会想在这里瞎折腾的。这段代码里有奇怪、黑暗的魔法在运作。当心。回去!趁现在还能回去,快回去!

常量

DEBUG
LITERAL

公共类方法

new(strict: false) 点击切换源代码
# File rexml-3.4.0/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

公共实例方法

[]=( variable_name, value ) 点击切换源代码
# File rexml-3.4.0/lib/rexml/xpath_parser.rb, line 94
def []=( variable_name, value )
  @variables[ variable_name ] = value
end
first( path_stack, node ) 点击切换源代码

执行深度优先(文档顺序)XPath搜索,并返回第一个匹配项。这是返回单个结果的最快、最轻量的方法。

FIXME:此方法不完整!

# File rexml-3.4.0/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
get_first(path, nodeset) 点击切换源代码
# File rexml-3.4.0/lib/rexml/xpath_parser.rb, line 84
def get_first path, nodeset
  path_stack = @parser.parse( path )
  first( path_stack, nodeset )
end
match(path_stack, nodeset) 点击切换源代码
# File rexml-3.4.0/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={} ) 点击切换源代码
# File rexml-3.4.0/lib/rexml/xpath_parser.rb, line 69
def namespaces=( namespaces={} )
  Functions::namespace_context = namespaces
  @namespaces = namespaces
end
parse(path, nodeset) 点击切换源代码
# File rexml-3.4.0/lib/rexml/xpath_parser.rb, line 79
def parse path, nodeset
  path_stack = @parser.parse( path )
  match( path_stack, nodeset )
end
predicate(path, nodeset) 点击切换源代码
# File rexml-3.4.0/lib/rexml/xpath_parser.rb, line 89
def predicate path, nodeset
  path_stack = @parser.parse( path )
  match( path_stack, nodeset )
end
variables=( vars={} ) 点击切换源代码
# File rexml-3.4.0/lib/rexml/xpath_parser.rb, line 74
def variables=( vars={} )
  Functions::variables = vars
  @variables = vars
end

私有实例方法

child(nodeset) 点击切换源代码
# File rexml-3.4.0/lib/rexml/xpath_parser.rb, line 779
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) 点击切换源代码
# File rexml-3.4.0/lib/rexml/xpath_parser.rb, line 920
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) 点击切换源代码
# File rexml-3.4.0/lib/rexml/xpath_parser.rb, line 682
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) 点击切换源代码
# File rexml-3.4.0/lib/rexml/xpath_parser.rb, line 693
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| ... } 点击切换源代码
# File rexml-3.4.0/lib/rexml/xpath_parser.rb, line 942
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) 点击切换源代码
# File rexml-3.4.0/lib/rexml/xpath_parser.rb, line 641
def enter(tag, *args)
  trace(:enter, tag, *args)
  @nest += 1
end
equality_relational_compare(set1, op, set2) 点击切换源代码
# File rexml-3.4.0/lib/rexml/xpath_parser.rb, line 819
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) 点击切换源代码
# File rexml-3.4.0/lib/rexml/xpath_parser.rb, line 591
def evaluate_predicate(expression, nodesets)
  enter(:predicate, expression, nodesets) if @debug
  new_nodeset_count = 0
  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_count += 1
          new_nodeset << XPathNode.new(node, position: new_nodeset_count)
        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_count += 1
            new_nodeset << XPathNode.new(node, position: new_nodeset_count)
          end
        end
      else
        if result
          new_nodeset_count += 1
          new_nodeset << XPathNode.new(node, position: new_nodeset_count)
        end
      end
    end
    new_nodeset
  end
  new_nodesets
ensure
  leave(:predicate, new_nodesets) if @debug
end
expr( path_stack, nodeset, context=nil ) 点击切换源代码

Expr 接收路径元素堆栈和一组节点(Parent 或数组),并返回匹配节点的数组

# File rexml-3.4.0/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) { |node| ... } 点击切换源代码
# File rexml-3.4.0/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
following(node) 点击切换源代码
# File rexml-3.4.0/lib/rexml/xpath_parser.rb, line 749
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 ) 点击切换源代码
# File rexml-3.4.0/lib/rexml/xpath_parser.rb, line 760
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 ) 点击切换源代码

返回节点的字符串命名空间,给定前缀。规则是

1. Use the supplied namespace mapping first.
2. If no mapping was supplied, use the context node to look up the namespace
# File rexml-3.4.0/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) 点击切换源代码
# File rexml-3.4.0/lib/rexml/xpath_parser.rb, line 646
def leave(tag, *args)
  @nest -= 1
  trace(:leave, tag, *args)
end
next_sibling_node(node) 点击切换源代码
# File rexml-3.4.0/lib/rexml/xpath_parser.rb, line 767
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) 点击切换源代码
# File rexml-3.4.0/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) 点击切换源代码
# File rexml-3.4.0/lib/rexml/xpath_parser.rb, line 806
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) 点击切换源代码
# File rexml-3.4.0/lib/rexml/xpath_parser.rb, line 894
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) 点击切换源代码

构建一个包含所提供节点所有前面的节点的节点集,按文档逆序排列

preceding

包括文档中在这个节点之前的每个元素,

除了祖先

# File rexml-3.4.0/lib/rexml/xpath_parser.rb, line 712
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 ) 点击切换源代码
# File rexml-3.4.0/lib/rexml/xpath_parser.rb, line 734
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) 点击切换源代码

重新排序节点数组,使其按照文档顺序排列。它尝试高效地执行此操作。

FIXME:我需要摆脱这个,但问题是大多数 XPath 解释器都充当过滤器,这意味着我们在函数调用中进出时会丢失上下文。如果我知道节点的索引是什么,我就不必这样做了。也许为每个节点添加一个文档 IDX?可变文档的问题。或者,重写一切。

# File rexml-3.4.0/lib/rexml/xpath_parser.rb, line 659
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) { || ... } 点击切换源代码
# File rexml-3.4.0/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
strict?() 点击切换源代码
# File rexml-3.4.0/lib/rexml/xpath_parser.rb, line 154
def strict?
  @strict
end
trace(*args) 点击切换源代码
# File rexml-3.4.0/lib/rexml/xpath_parser.rb, line 634
def trace(*args)
  indent = "  " * @nest
  PP.pp(args, "").each_line do |line|
    puts("#{indent}#{line}")
  end
end
unnode(nodeset) { |unnoded| ... } 点击切换源代码
# File rexml-3.4.0/lib/rexml/xpath_parser.rb, line 954
def unnode(nodeset)
  each_unnode(nodeset).collect do |unnoded|
    unnoded = yield(unnoded) if block_given?
    unnoded
  end
end
value_type(value) 点击切换源代码
# File rexml-3.4.0/lib/rexml/xpath_parser.rb, line 881
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