File: rexml/quickpath.rb

Overview
Module Structure
Class Hierarchy
Code

Overview

Module Structure

  module: <Toplevel Module>
  module: REXML#4
  class: QuickPath#5
includes
  Functions ( REXML )
  XMLTokens ( REXML )
inherits from
  Object ( Builtin-Module )
has properties
constant: EMPTY_HASH #9
class method: first #11
class method: each #15
class method: match #20
class method: filter #48
class method: axe / 3 #106
class method: predicate / 2 #160
class method: attribute / 1 #206
class method: name #210
class method: method_missing / 2 #214
class method: function / 3 #222
class method: parse_args / 2 #240

Class Hierarchy

Object ( Builtin-Module )
  QuickPath ( REXML ) #5

Code

   1  require 'rexml/functions'
   2  require 'rexml/xmltokens'
   3 
   4  module REXML
   5    class QuickPath
   6      include Functions
   7      include XMLTokens
   8 
   9      EMPTY_HASH = {}
  10 
  11      def QuickPath::first element, path, namespaces=EMPTY_HASH
  12        match(element, path, namespaces)[0]
  13      end
  14 
  15      def QuickPath::each element, path, namespaces=EMPTY_HASH, &block
  16        path = "*" unless path
  17        match(element, path, namespaces).each( &block )
  18      end
  19 
  20      def QuickPath::match element, path, namespaces=EMPTY_HASH
  21        raise "nil is not a valid xpath" unless path
  22        results = nil
  23        Functions::namespace_context = namespaces
  24        case path
  25        when /^\/([^\/]|$)/u
  26          # match on root
  27          path = path[1..-1]
  28          return [element.root.parent] if path == ''
  29          results = filter([element.root], path)
  30        when /^[-\w]*::/u
  31          results = filter([element], path)
  32        when /^\*/u
  33          results = filter(element.to_a, path)
  34        when /^[\[!\w:]/u
  35          # match on child
  36          matches = []
  37          children = element.to_a
  38          results = filter(children, path)
  39        else
  40          results = filter([element], path)
  41        end
  42        return results
  43      end
  44 
  45      # Given an array of nodes it filters the array based on the path. The
  46      # result is that when this method returns, the array will contain elements
  47      # which match the path
  48      def QuickPath::filter elements, path
  49        return elements if path.nil? or path == '' or elements.size == 0
  50        case path
  51        when /^\/\//u                     # Descendant
  52          return axe( elements, "descendant-or-self", $' )
  53        when /^\/?\b(\w[-\w]*)\b::/u              # Axe
  54          axe_name = $1
  55          rest = $'
  56          return axe( elements, $1, $' )
  57        when /^\/(?=\b([:!\w][-\.\w]*:)?[-!\*\.\w]*\b([^:(]|$)|\*)/u  # Child
  58          rest = $'
  59          results = []
  60          elements.each do |element|
  61            results |= filter( element.to_a, rest )
  62          end
  63          return results
  64        when /^\/?(\w[-\w]*)\(/u              # / Function
  65          return function( elements, $1, $' )
  66        when Namespace::NAMESPLIT   # Element name
  67          name = $2
  68          ns = $1
  69          rest = $'
  70          elements.delete_if do |element|
  71            !(element.kind_of? Element and 
  72              (element.expanded_name == name or
  73               (element.name == name and
  74                element.namespace == Functions.namespace_context[ns])))
  75          end
  76          return filter( elements, rest )
  77        when /^\/\[/u
  78          matches = []
  79          elements.each do |element|
  80            matches |= predicate( element.to_a, path[1..-1] ) if element.kind_of? Element
  81          end
  82          return matches
  83        when /^\[/u                       # Predicate
  84          return predicate( elements, path )
  85        when /^\/?\.\.\./u                    # Ancestor
  86          return axe( elements, "ancestor", $' )
  87        when /^\/?\.\./u                      # Parent
  88          return filter( elements.collect{|e|e.parent}, $' )
  89        when /^\/?\./u                        # Self
  90          return filter( elements, $' )
  91        when /^\*/u                         # Any
  92          results = []
  93          elements.each do |element|
  94            results |= filter( [element], $' ) if element.kind_of? Element
  95            #if element.kind_of? Element
  96            # children = element.to_a
  97            # children.delete_if { |child| !child.kind_of?(Element) }
  98            # results |= filter( children, $' )
  99            #end
 100          end
 101          return results
 102        end
 103        return []
 104      end
 105 
 106      def QuickPath::axe( elements, axe_name, rest )
 107        matches = []
 108        matches = filter( elements.dup, rest ) if axe_name =~ /-or-self$/u
 109        case axe_name
 110        when /^descendant/u
 111          elements.each do |element|
 112            matches |= filter( element.to_a, "descendant-or-self::#{rest}" ) if element.kind_of? Element
 113          end
 114        when /^ancestor/u
 115          elements.each do |element|
 116            while element.parent
 117              matches << element.parent
 118              element = element.parent
 119            end
 120          end
 121          matches = filter( matches, rest )
 122        when "self"
 123          matches = filter( elements, rest )
 124        when "child"
 125          elements.each do |element|
 126            matches |= filter( element.to_a, rest ) if element.kind_of? Element
 127          end
 128        when "attribute"
 129          elements.each do |element|
 130            matches << element.attributes[ rest ] if element.kind_of? Element
 131          end
 132        when "parent"
 133          matches = filter(elements.collect{|element| element.parent}.uniq, rest)
 134        when "following-sibling"
 135          matches = filter(elements.collect{|element| element.next_sibling}.uniq,
 136            rest)
 137        when "previous-sibling"
 138          matches = filter(elements.collect{|element| 
 139            element.previous_sibling}.uniq, rest )
 140        end
 141        return matches.uniq
 142      end
 143 
 144      # A predicate filters a node-set with respect to an axis to produce a
 145      # new node-set. For each node in the node-set to be filtered, the 
 146      # PredicateExpr is evaluated with that node as the context node, with 
 147      # the number of nodes in the node-set as the context size, and with the 
 148      # proximity position of the node in the node-set with respect to the
 149      # axis as the context position; if PredicateExpr evaluates to true for
 150      # that node, the node is included in the new node-set; otherwise, it is
 151      # not included.
 152      #
 153      # A PredicateExpr is evaluated by evaluating the Expr and converting
 154      # the result to a boolean. If the result is a number, the result will
 155      # be converted to true if the number is equal to the context position
 156      # and will be converted to false otherwise; if the result is not a
 157      # number, then the result will be converted as if by a call to the
 158      # boolean function. Thus a location path para[3] is equivalent to
 159      # para[position()=3].
 160      def QuickPath::predicate( elements, path ) 
 161        ind = 1
 162        bcount = 1
 163        while bcount > 0
 164          bcount += 1 if path[ind] == ?[
 165          bcount -= 1 if path[ind] == ?]
 166          ind += 1
 167        end
 168        ind -= 1
 169        predicate = path[1..ind-1]
 170        rest = path[ind+1..-1]
 171 
 172        # have to change 'a [=<>] b [=<>] c' into 'a [=<>] b and b [=<>] c'
 173        predicate.gsub!( /([^\s(and)(or)<>=]+)\s*([<>=])\s*([^\s(and)(or)<>=]+)\s*([<>=])\s*([^\s(and)(or)<>=]+)/u ) { 
 174          "#$1 #$2 #$3 and #$3 #$4 #$5"
 175        }
 176        # Let's do some Ruby trickery to avoid some work:
 177        predicate.gsub!( /&/u, "&&" )
 178        predicate.gsub!( /=/u, "==" )
 179        predicate.gsub!( /@(\w[-\w.]*)/u ) {
 180          "attribute(\"#$1\")" 
 181        }
 182        predicate.gsub!( /\bmod\b/u, "%" )
 183        predicate.gsub!( /\b(\w[-\w.]*\()/u ) {
 184          fname = $1
 185          fname.gsub( /-/u, "_" )
 186        }
 187 
 188        Functions.pair = [ 0, elements.size ]
 189        results = []
 190        elements.each do |element|
 191          Functions.pair[0] += 1
 192          Functions.node = element
 193          res = eval( predicate )
 194          case res
 195          when true
 196            results << element
 197          when Fixnum
 198            results << element if Functions.pair[0] == res
 199          when String
 200            results << element
 201          end
 202        end
 203        return filter( results, rest )
 204      end
 205 
 206      def QuickPath::attribute( name )
 207        return Functions.node.attributes[name] if Functions.node.kind_of? Element
 208      end
 209 
 210      def QuickPath::name()
 211        return Functions.node.name if Functions.node.kind_of? Element
 212      end
 213 
 214      def QuickPath::method_missing( id, *args )
 215        begin
 216          Functions.send( id.id2name, *args )
 217        rescue Exception
 218          raise "METHOD: #{id.id2name}(#{args.join ', '})\n#{$!.message}"
 219        end
 220      end
 221 
 222      def QuickPath::function( elements, fname, rest )
 223        args = parse_args( elements, rest )
 224        Functions.pair = [0, elements.size]
 225        results = []
 226        elements.each do |element|
 227          Functions.pair[0] += 1
 228          Functions.node = element
 229          res = Functions.send( fname, *args )
 230          case res
 231          when true
 232            results << element
 233          when Fixnum
 234            results << element if Functions.pair[0] == res
 235          end
 236        end
 237        return results
 238      end
 239 
 240      def QuickPath::parse_args( element, string )
 241        # /.*?(?:\)|,)/
 242        arguments = []
 243        buffer = ""
 244        while string and string != ""
 245          c = string[0]
 246          string.sub!(/^./u, "")
 247          case c
 248          when ?,
 249            # if depth = 1, then we start a new argument
 250            arguments << evaluate( buffer )
 251            #arguments << evaluate( string[0..count] )
 252          when ?(
 253            # start a new method call
 254            function( element, buffer, string )
 255            buffer = ""
 256          when ?)
 257            # close the method call and return arguments
 258            return arguments
 259          else
 260            buffer << c
 261          end
 262        end
 263        ""
 264      end
 265    end
 266  end