class REXML::XPathParser - Documentation for Ruby 2.3.0 (original) (raw)

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

Constants

ALL

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

ELEMENTS

LITERAL

Public Class Methods

new( ) click to toggle source

def initialize( ) @parser = REXML::Parsers::XPathParser.new @namespaces = nil @variables = {} end

Public Instance Methods

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

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!

def first( path_stack, node ) return nil if path.size == 0

case path[0] when :document

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

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

match( path_stack, nodeset ) click to toggle source

def match( path_stack, nodeset ) r = expr( path_stack, nodeset ) r end

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

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

parse(path, nodeset) click to toggle source

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

predicate(path, nodeset) click to toggle source

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

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

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

Private Instance Methods

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

def compare a, op, b case op when :eq a == b when :neq a != b when :lt a < b when :lteq a <= b when :gt a > b when :gteq a >= b when :and a and b when :or a or b else false end end

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

def d_o_s( p, ns, r ) nt = nil ns.each_index do |i| n = ns[i] x = expr( p.dclone, [ n ] ) nt = n.node_type d_o_s( p, n.children, x ) if nt == :element or nt == :document and n.children.size > 0 r.concat(x) if x.size > 0 end end

descendant_or_self( path_stack, nodeset ) click to toggle source

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

def descendant_or_self( path_stack, nodeset ) rs = [] d_o_s( path_stack, nodeset, rs ) document_order(rs.flatten.compact)

end

document_order( array_of_nodes ) click to toggle source

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

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

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

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

def equality_relational_compare( set1, op, set2 ) if set1.kind_of? Array and set2.kind_of? Array if set1.size == 1 and set2.size == 1 set1 = set1[0] set2 = set2[0] elsif set1.size == 0 or set2.size == 0 nd = set1.size==0 ? set2 : set1 rv = nd.collect { |il| compare( il, op, nil ) } return rv else res = [] SyncEnumerator.new( set1, set2 ).each { |i1, i2| i1 = norm( i1 ) i2 = norm( i2 ) res << compare( i1, op, i2 ) } return res end end

if set1.kind_of? Array or set2.kind_of? Array if set1.kind_of? Array a = set1 b = set2 else a = set2 b = set1 end

case b
when true, false
  return a.collect {|v| compare( Functions::boolean(v), op, b ) }
when Numeric
  return a.collect {|v| compare( Functions::number(v), op, b )}
when /^\d+(\.\d+)?$/
  b = Functions::number( b )
  return a.collect {|v| compare( Functions::number(v), op, b )}
else
  b = Functions::string( b )
  return a.collect { |v| compare( Functions::string(v), op, b ) }
end

else

s1 = set1.to_s
s2 = set2.to_s
if s1 == 'true' or s1 == 'false' or s2 == 'true' or s2 == 'false'
  set1 = Functions::boolean( set1 )
  set2 = Functions::boolean( set2 )
else
  if op == :eq or op == :neq
    if s1 =~ /^\d+(\.\d+)?$/ or s2 =~ /^\d+(\.\d+)?$/
      set1 = Functions::number( s1 )
      set2 = Functions::number( s2 )
    else
      set1 = Functions::string( set1 )
      set2 = Functions::string( set2 )
    end
  else
    set1 = Functions::number( set1 )
    set2 = Functions::number( set2 )
  end
end
return compare( set1, op, set2 )

end return false end

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

def expr( path_stack, nodeset, context=nil )
  node_types = ELEMENTS
  return nodeset if path_stack.length == 0 || nodeset.length == 0
  while path_stack.length > 0
    if nodeset.length == 0
      path_stack.clear
      return []
    end
    case (op = path_stack.shift)
    when :document
      nodeset = [ nodeset[0].root_node ]

    when :qname
      prefix = path_stack.shift
      name = path_stack.shift
      nodeset.delete_if do |node|
        
        ns = get_namespace( node, prefix )
        if node.node_type == :element
          if node.name == name
          end
        end
        !(node.node_type == :element and
          node.name == name and
          node.namespace == ns )
      end
      node_types = ELEMENTS

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

    when :self
      

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

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

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

    when :node
      
      node_types = ALL

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

    when :literal
      return path_stack.shift

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

    when :parent
      nodeset = nodeset.collect{|n| n.parent}.compact
      
      node_types = ELEMENTS

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

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

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


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

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

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

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

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

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

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

    when :variable
      var_name = path_stack.shift
      return @variables[ var_name ]
    
    
    when :eq, :neq, :lt, :lteq, :gt, :gteq, :or
      left = expr( path_stack.shift, nodeset.dup, context )
      right = expr( path_stack.shift, nodeset.dup, context )
      res = equality_relational_compare( left, op, right )
      return res

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

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

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

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

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

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

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

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

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

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

    end
  end 
  return nodeset
end

following( node ) click to toggle source

def following( node ) acc = [] p = next_sibling_node( node ) while p acc << p p = following_node_of( p ) end acc end

following_node_of( node ) click to toggle source

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

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

next_sibling_node(node) click to toggle source

def next_sibling_node(node) psn = node.next_sibling_node while psn.nil? if node.parent.nil? or node.parent.class == Document return nil end node = node.parent psn = node.next_sibling_node end return psn end

norm(b) click to toggle source

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

preceding( node ) click to toggle source

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

preceding

includes every element in the document that precedes this node,

except for ancestors

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

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

preceding_node_of( node ) click to toggle source

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

end while psn and psn.kind_of? Element and psn.children.size > 0 psn = psn.children[-1] end psn end

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

def recurse( nodeset, &block ) for node in nodeset yield node recurse( node, &block ) if node.node_type == :element end end