Class REXML::XPathParser
In: lib/rexml/xpath_parser.rb
Parent: Object

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!

Methods

Included Modules

XMLTokens

Constants

LITERAL = /^'([^']*)'|^"([^"]*)"/u
ALL = [ :attribute, :element, :text, :processing_instruction, :comment ]   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 = [ :element ]

Public Class methods

[Source]

    # File lib/rexml/xpath_parser.rb, line 35
35:     def initialize( )
36:       @parser = REXML::Parsers::XPathParser.new
37:       @namespaces = {}
38:       @variables = {}
39:     end

Public Instance methods

[Source]

    # File lib/rexml/xpath_parser.rb, line 72
72:     def []=( variable_name, value )
73:       @variables[ variable_name ] = value
74:     end

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!

[Source]

     # File lib/rexml/xpath_parser.rb, line 81
 81:     def first( path_stack, node )
 82:       #puts "#{depth}) Entering match( #{path.inspect}, #{tree.inspect} )"
 83:       return nil if path.size == 0
 84: 
 85:       case path[0]
 86:       when :document
 87:         # do nothing 
 88:         return first( path[1..-1], node )
 89:       when :child
 90:         for c in node.children
 91:           #puts "#{depth}) CHILD checking #{name(c)}"
 92:           r = first( path[1..-1], c )
 93:           #puts "#{depth}) RETURNING #{r.inspect}" if r
 94:           return r if r
 95:         end
 96:       when :qname
 97:         name = path[2]
 98:         #puts "#{depth}) QNAME #{name(tree)} == #{name} (path => #{path.size})"
 99:         if node.name == name
100:           #puts "#{depth}) RETURNING #{tree.inspect}" if path.size == 3
101:           return node if path.size == 3
102:           return first( path[3..-1], node )
103:         else
104:           return nil
105:         end
106:       when :descendant_or_self
107:         r = first( path[1..-1], node )
108:         return r if r
109:         for c in node.children
110:           r = first( path, c )
111:           return r if r
112:         end
113:       when :node
114:         return first( path[1..-1], node )
115:       when :any
116:         return first( path[1..-1], node )
117:       end
118:       return nil
119:     end

[Source]

    # File lib/rexml/xpath_parser.rb, line 59
59:     def get_first path, nodeset
60:      #puts "#"*40
61:      path_stack = @parser.parse( path )
62:      #puts "PARSE: #{path} => #{path_stack.inspect}"
63:      #puts "PARSE: nodeset = #{nodeset.inspect}"
64:      first( path_stack, nodeset )
65:     end

[Source]

     # File lib/rexml/xpath_parser.rb, line 122
122:     def match( path_stack, nodeset ) 
123:       #puts "MATCH: path_stack = #{path_stack.inspect}"
124:       #puts "MATCH: nodeset = #{nodeset.inspect}"
125:       r = expr( path_stack, nodeset )
126:       #puts "MAIN EXPR => #{r.inspect}"
127:       r
128:     end

[Source]

    # File lib/rexml/xpath_parser.rb, line 41
41:     def namespaces=( namespaces={} )
42:       Functions::namespace_context = namespaces
43:       @namespaces = namespaces
44:     end

[Source]

    # File lib/rexml/xpath_parser.rb, line 51
51:     def parse path, nodeset
52:      #puts "#"*40
53:      path_stack = @parser.parse( path )
54:      #puts "PARSE: #{path} => #{path_stack.inspect}"
55:      #puts "PARSE: nodeset = #{nodeset.inspect}"
56:      match( path_stack, nodeset )
57:     end

[Source]

    # File lib/rexml/xpath_parser.rb, line 67
67:     def predicate path, nodeset
68:       path_stack = @parser.parse( path )
69:       expr( path_stack, nodeset )
70:     end

[Source]

    # File lib/rexml/xpath_parser.rb, line 46
46:     def variables=( vars={} )
47:       Functions::variables = vars
48:       @variables = vars
49:     end

Private Instance methods

[Source]

     # File lib/rexml/xpath_parser.rb, line 717
717:     def compare a, op, b
718:       #puts "COMPARE #{a.inspect}(#{a.class.name}) #{op} #{b.inspect}(#{b.class.name})"
719:       case op
720:       when :eq
721:         a == b
722:       when :neq
723:         a != b
724:       when :lt
725:         a < b
726:       when :lteq
727:         a <= b
728:       when :gt
729:         a > b
730:       when :gteq
731:         a >= b
732:       when :and
733:         a and b
734:       when :or
735:         a or b
736:       else
737:         false
738:       end
739:     end

[Source]

     # File lib/rexml/xpath_parser.rb, line 482
482:     def d_o_s( p, ns, r )
483:       #puts "IN DOS with #{ns.inspect}; ALREADY HAVE #{r.inspect}"
484:       nt = nil
485:       ns.each_index do |i|
486:         n = ns[i]
487:         #puts "P => #{p.inspect}"
488:         x = expr( p.dclone, [ n ] )
489:         nt = n.node_type
490:         d_o_s( p, n.children, x ) if nt == :element or nt == :document and n.children.size > 0
491:         r.concat(x) if x.size > 0
492:       end
493:     end

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 took me three days to get it to work at all. ########################################################

[Source]

     # File lib/rexml/xpath_parser.rb, line 471
471:     def descendant_or_self( path_stack, nodeset )
472:       rs = []
473:       #puts "#"*80
474:       #puts "PATH_STACK = #{path_stack.inspect}"
475:       #puts "NODESET = #{nodeset.collect{|n|n.inspect}.inspect}"
476:       d_o_s( path_stack, nodeset, rs )
477:       #puts "RS = #{rs.collect{|n|n.inspect}.inspect}"
478:       document_order(rs.flatten.compact)
479:       #rs.flatten.compact
480:     end

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.

[Source]

     # File lib/rexml/xpath_parser.rb, line 504
504:     def document_order( array_of_nodes )
505:       new_arry = []
506:       array_of_nodes.each { |node|
507:         node_idx = [] 
508:         np = node.node_type == :attribute ? node.element : node
509:         while np.parent and np.parent.node_type == :element
510:           node_idx << np.parent.index( np )
511:           np = np.parent
512:         end
513:         new_arry << [ node_idx.reverse, node ]
514:       }
515:       #puts "new_arry = #{new_arry.inspect}"
516:       new_arry.sort{ |s1, s2| s1[0] <=> s2[0] }.collect{ |s| s[1] }
517:     end

[Source]

     # File lib/rexml/xpath_parser.rb, line 624
624:     def equality_relational_compare( set1, op, set2 )
625:       #puts "EQ_REL_COMP(#{set1.inspect} #{op.inspect} #{set2.inspect})"
626:       if set1.kind_of? Array and set2.kind_of? Array
627:                           #puts "#{set1.size} & #{set2.size}"
628:         if set1.size == 1 and set2.size == 1
629:           set1 = set1[0]
630:           set2 = set2[0]
631:         elsif set1.size == 0 or set2.size == 0
632:           nd = set1.size==0 ? set2 : set1
633:           rv = nd.collect { |il| compare( il, op, nil ) }
634:           #puts "RV = #{rv.inspect}"
635:           return rv
636:         else
637:           res = []
638:           enum = SyncEnumerator.new( set1, set2 ).each { |i1, i2|
639:             #puts "i1 = #{i1.inspect} (#{i1.class.name})"
640:             #puts "i2 = #{i2.inspect} (#{i2.class.name})"
641:             i1 = norm( i1 )
642:             i2 = norm( i2 )
643:             res << compare( i1, op, i2 )
644:           }
645:           return res
646:         end
647:       end
648:                   #puts "EQ_REL_COMP: #{set1.inspect} (#{set1.class.name}), #{op}, #{set2.inspect} (#{set2.class.name})"
649:       #puts "COMPARING VALUES"
650:       # If one is nodeset and other is number, compare number to each item
651:       # in nodeset s.t. number op number(string(item))
652:       # If one is nodeset and other is string, compare string to each item
653:       # in nodeset s.t. string op string(item)
654:       # If one is nodeset and other is boolean, compare boolean to each item
655:       # in nodeset s.t. boolean op boolean(item)
656:       if set1.kind_of? Array or set2.kind_of? Array
657:                           #puts "ISA ARRAY"
658:         if set1.kind_of? Array
659:           a = set1
660:           b = set2
661:         else
662:           a = set2
663:           b = set1
664:         end
665: 
666:         case b
667:         when true, false
668:           return a.collect {|v| compare( Functions::boolean(v), op, b ) }
669:         when Numeric
670:           return a.collect {|v| compare( Functions::number(v), op, b )}
671:         when /^\d+(\.\d+)?$/
672:           b = Functions::number( b )
673:           #puts "B = #{b.inspect}"
674:           return a.collect {|v| compare( Functions::number(v), op, b )}
675:         else
676:                                   #puts "Functions::string( #{b}(#{b.class.name}) ) = #{Functions::string(b)}"
677:           b = Functions::string( b )
678:           return a.collect { |v| compare( Functions::string(v), op, b ) }
679:         end
680:       else
681:         # If neither is nodeset,
682:         #   If op is = or !=
683:         #     If either boolean, convert to boolean
684:         #     If either number, convert to number
685:         #     Else, convert to string
686:         #   Else
687:         #     Convert both to numbers and compare
688:         s1 = set1.to_s
689:         s2 = set2.to_s
690:         #puts "EQ_REL_COMP: #{set1}=>#{s1}, #{set2}=>#{s2}"
691:         if s1 == 'true' or s1 == 'false' or s2 == 'true' or s2 == 'false'
692:           #puts "Functions::boolean(#{set1})=>#{Functions::boolean(set1)}"
693:           #puts "Functions::boolean(#{set2})=>#{Functions::boolean(set2)}"
694:           set1 = Functions::boolean( set1 )
695:           set2 = Functions::boolean( set2 )
696:         else
697:           if op == :eq or op == :neq
698:             if s1 =~ /^\d+(\.\d+)?$/ or s2 =~ /^\d+(\.\d+)?$/
699:               set1 = Functions::number( s1 )
700:               set2 = Functions::number( s2 )
701:             else
702:               set1 = Functions::string( set1 )
703:               set2 = Functions::string( set2 )
704:             end
705:           else
706:             set1 = Functions::number( set1 )
707:             set2 = Functions::number( set2 )
708:           end
709:         end
710:         #puts "EQ_REL_COMP: #{set1} #{op} #{set2}"
711:         #puts ">>> #{compare( set1, op, set2 )}"
712:         return compare( set1, op, set2 )
713:       end
714:       return false
715:     end

[Source]

     # File lib/rexml/xpath_parser.rb, line 137
137:     def expr( path_stack, nodeset, context=nil )
138:       #puts "#"*15
139:       #puts "In expr with #{path_stack.inspect}"
140:       #puts "Returning" if path_stack.length == 0 || nodeset.length == 0
141:       node_types = ELEMENTS
142:       return nodeset if path_stack.length == 0 || nodeset.length == 0
143:       while path_stack.length > 0
144:         #puts "Path stack = #{path_stack.inspect}"
145:         #puts "Nodeset is #{nodeset.inspect}"
146:         case (op = path_stack.shift)
147:         when :document
148:           nodeset = [ nodeset[0].root_node ]
149:           #puts ":document, nodeset = #{nodeset.inspect}"
150: 
151:         when :qname
152:           #puts "IN QNAME"
153:           prefix = path_stack.shift
154:           name = path_stack.shift
155:           default_ns = @namespaces[prefix]
156:           default_ns = default_ns ? default_ns : ''
157:           nodeset.delete_if do |node|
158:             ns = default_ns
159:             # FIXME: This DOUBLES the time XPath searches take
160:             ns = node.namespace( prefix ) if node.node_type == :element and ns == ''
161:             #puts "NS = #{ns.inspect}"
162:             #puts "node.node_type == :element => #{node.node_type == :element}"
163:             if node.node_type == :element
164:               #puts "node.name == #{name} => #{node.name == name}"
165:               if node.name == name
166:                 #puts "node.namespace == #{ns.inspect} => #{node.namespace == ns}"
167:               end
168:             end
169:             !(node.node_type == :element and 
170:               node.name == name and 
171:               node.namespace == ns )
172:           end
173:           node_types = ELEMENTS
174: 
175:         when :any
176:           #puts "ANY 1: nodeset = #{nodeset.inspect}"
177:           #puts "ANY 1: node_types = #{node_types.inspect}"
178:           nodeset.delete_if { |node| !node_types.include?(node.node_type) }
179:           #puts "ANY 2: nodeset = #{nodeset.inspect}"
180: 
181:         when :self
182:           # This space left intentionally blank
183: 
184:         when :processing_instruction
185:           target = path_stack.shift
186:           nodeset.delete_if do |node|
187:             (node.node_type != :processing_instruction) or 
188:             ( target!='' and ( node.target != target ) )
189:           end
190: 
191:         when :text
192:           nodeset.delete_if { |node| node.node_type != :text }
193: 
194:         when :comment
195:           nodeset.delete_if { |node| node.node_type != :comment }
196: 
197:         when :node
198:           # This space left intentionally blank
199:           node_types = ALL
200: 
201:         when :child
202:           new_nodeset = []
203:           nt = nil
204:           for node in nodeset
205:             nt = node.node_type
206:             new_nodeset += node.children if nt == :element or nt == :document
207:           end
208:           nodeset = new_nodeset
209:           node_types = ELEMENTS
210: 
211:         when :literal
212:           literal = path_stack.shift
213:           if literal =~ /^\d+(\.\d+)?$/
214:             return ($1 ? literal.to_f : literal.to_i) 
215:           end
216:           return literal
217:         
218:         when :attribute
219:           new_nodeset = []
220:           case path_stack.shift
221:           when :qname
222:             prefix = path_stack.shift
223:             name = path_stack.shift
224:             for element in nodeset
225:               if element.node_type == :element
226:                 #puts element.name
227:                 attr = element.attribute( name, @namespaces[prefix] )
228:                 new_nodeset << attr if attr
229:               end
230:             end
231:           when :any
232:             #puts "ANY"
233:             for element in nodeset
234:               if element.node_type == :element
235:                 new_nodeset += element.attributes.to_a
236:               end
237:             end
238:           end
239:           nodeset = new_nodeset
240: 
241:         when :parent
242:           #puts "PARENT 1: nodeset = #{nodeset}"
243:           nodeset = nodeset.collect{|n| n.parent}.compact
244:           #nodeset = expr(path_stack.dclone, nodeset.collect{|n| n.parent}.compact)
245:           #puts "PARENT 2: nodeset = #{nodeset.inspect}"
246:           node_types = ELEMENTS
247: 
248:         when :ancestor
249:           new_nodeset = []
250:           for node in nodeset
251:             while node.parent
252:               node = node.parent
253:               new_nodeset << node unless new_nodeset.include? node
254:             end
255:           end
256:           nodeset = new_nodeset
257:           node_types = ELEMENTS
258: 
259:         when :ancestor_or_self
260:           new_nodeset = []
261:           for node in nodeset
262:             if node.node_type == :element
263:               new_nodeset << node
264:               while ( node.parent )
265:                 node = node.parent
266:                 new_nodeset << node unless new_nodeset.include? node
267:               end
268:             end
269:           end
270:           nodeset = new_nodeset
271:           node_types = ELEMENTS
272: 
273:         when :predicate
274:           new_nodeset = []
275:           subcontext = { :size => nodeset.size }
276:           pred = path_stack.shift
277:           nodeset.each_with_index { |node, index|
278:             subcontext[ :node ] = node
279:             #puts "PREDICATE SETTING CONTEXT INDEX TO #{index+1}"
280:             subcontext[ :index ] = index+1
281:             pc = pred.dclone
282:             #puts "#{node.hash}) Recursing with #{pred.inspect} and [#{node.inspect}]"
283:             result = expr( pc, [node], subcontext )
284:             result = result[0] if result.kind_of? Array and result.length == 1
285:             #puts "#{node.hash}) Result = #{result.inspect} (#{result.class.name})"
286:             if result.kind_of? Numeric
287:               #puts "Adding node #{node.inspect}" if result == (index+1)
288:               new_nodeset << node if result == (index+1)
289:             elsif result.instance_of? Array
290:               #puts "Adding node #{node.inspect}" if result.size > 0
291:               new_nodeset << node if result.size > 0
292:             else
293:               #puts "Adding node #{node.inspect}" if result
294:               new_nodeset << node if result
295:             end
296:           }
297:           #puts "New nodeset = #{new_nodeset.inspect}"
298:           #puts "Path_stack  = #{path_stack.inspect}"
299:           nodeset = new_nodeset
300: ??
301: 
302:         when :descendant_or_self
303:           rv = descendant_or_self( path_stack, nodeset )
304:           path_stack.clear
305:           nodeset = rv
306:           node_types = ELEMENTS
307: 
308:         when :descendant
309:           results = []
310:           nt = nil
311:           for node in nodeset
312:             nt = node.node_type
313:             results += expr( path_stack.dclone.unshift( :descendant_or_self ),
314:               node.children ) if nt == :element or nt == :document
315:           end
316:           nodeset = results
317:           node_types = ELEMENTS
318: 
319:         when :following_sibling
320:           #puts "FOLLOWING_SIBLING 1: nodeset = #{nodeset}"
321:           results = []
322:           for node in nodeset
323:             all_siblings = node.parent.children
324:             current_index = all_siblings.index( node )
325:             following_siblings = all_siblings[ current_index+1 .. -1 ]
326:             results += expr( path_stack.dclone, following_siblings )
327:           end
328:           #puts "FOLLOWING_SIBLING 2: nodeset = #{nodeset}"
329:           nodeset = results
330: 
331:         when :preceding_sibling
332:           results = []
333:           for node in nodeset
334:             all_siblings = node.parent.children
335:             current_index = all_siblings.index( node )
336:             preceding_siblings = all_siblings[ 0 .. current_index-1 ].reverse
337:             #results += expr( path_stack.dclone, preceding_siblings )
338:           end
339:           nodeset = preceding_siblings || []
340:           node_types = ELEMENTS
341: 
342:         when :preceding
343:           new_nodeset = []
344:           for node in nodeset
345:             new_nodeset += preceding( node )
346:           end
347:           #puts "NEW NODESET => #{new_nodeset.inspect}"
348:           nodeset = new_nodeset
349:           node_types = ELEMENTS
350: 
351:         when :following
352:           new_nodeset = []
353:           for node in nodeset
354:             new_nodeset += following( node )
355:           end
356:           nodeset = new_nodeset
357:           node_types = ELEMENTS
358: 
359:         when :namespace
360:           new_set = []
361:           for node in nodeset
362:             new_nodeset << node.namespace if node.node_type == :element or node.node_type == :attribute
363:           end
364:           nodeset = new_nodeset
365: 
366:         when :variable
367:           var_name = path_stack.shift
368:           return @variables[ var_name ]
369: 
370:         # :and, :or, :eq, :neq, :lt, :lteq, :gt, :gteq
371:                                 # TODO: Special case for :or and :and -- not evaluate the right
372:                                 # operand if the left alone determines result (i.e. is true for
373:                                 # :or and false for :and).
374:         when :eq, :neq, :lt, :lteq, :gt, :gteq, :and, :or
375:           left = expr( path_stack.shift, nodeset.dup, context )
376:           #puts "LEFT => #{left.inspect} (#{left.class.name})"
377:           right = expr( path_stack.shift, nodeset.dup, context )
378:           #puts "RIGHT => #{right.inspect} (#{right.class.name})"
379:           res = equality_relational_compare( left, op, right )
380:           #puts "RES => #{res.inspect}"
381:           return res
382: 
383:         when :div
384:           left = Functions::number(expr(path_stack.shift, nodeset, context)).to_f
385:           right = Functions::number(expr(path_stack.shift, nodeset, context)).to_f
386:           return (left / right)
387: 
388:         when :mod
389:           left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
390:           right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
391:           return (left % right)
392: 
393:         when :mult
394:           left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
395:           right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
396:           return (left * right)
397: 
398:         when :plus
399:           left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
400:           right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
401:           return (left + right)
402: 
403:         when :minus
404:           left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
405:           right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
406:           return (left - right)
407: 
408:         when :union
409:           left = expr( path_stack.shift, nodeset, context )
410:           right = expr( path_stack.shift, nodeset, context )
411:           return (left | right)
412: 
413:         when :neg
414:           res = expr( path_stack, nodeset, context )
415:           return -(res.to_f)
416: 
417:         when :not
418:         when :function
419:           func_name = path_stack.shift.tr('-','_')
420:           arguments = path_stack.shift
421:           #puts "FUNCTION 0: #{func_name}(#{arguments.collect{|a|a.inspect}.join(', ')})" 
422:           subcontext = context ? nil : { :size => nodeset.size }
423: 
424:           res = []
425:           cont = context
426:           nodeset.each_with_index { |n, i| 
427:             if subcontext
428:               subcontext[:node]  = n
429:               subcontext[:index] = i
430:               cont = subcontext
431:             end
432:             arg_clone = arguments.dclone
433:             args = arg_clone.collect { |arg| 
434:               #puts "FUNCTION 1: Calling expr( #{arg.inspect}, [#{n.inspect}] )"
435:               expr( arg, [n], cont ) 
436:             }
437:             #puts "FUNCTION 2: #{func_name}(#{args.collect{|a|a.inspect}.join(', ')})" 
438:             Functions.context = cont
439:             res << Functions.send( func_name, *args )
440:             #puts "FUNCTION 3: #{res[-1].inspect}"
441:           }
442:           return res
443: 
444:         end
445:       end # while
446:       #puts "EXPR returning #{nodeset.inspect}"
447:       return nodeset
448:     end

[Source]

     # File lib/rexml/xpath_parser.rb, line 575
575:     def following( node )
576:       #puts "IN PRECEDING"
577:       acc = []
578:       p = next_sibling_node( node )
579:       #puts "P = #{p.inspect}"
580:       while p
581:         acc << p
582:         p = following_node_of( p )
583:         #puts "P = #{p.inspect}"
584:       end
585:       acc
586:     end

[Source]

     # File lib/rexml/xpath_parser.rb, line 588
588:     def following_node_of( node )
589:       #puts "NODE: #{node.inspect}"
590:       #puts "PREVIOUS NODE: #{node.previous_sibling_node.inspect}"
591:       #puts "PARENT NODE: #{node.parent}"
592:       if node.kind_of? Element and node.children.size > 0
593:         return node.children[0]
594:       end
595:       return next_sibling_node(node)
596:     end

[Source]

     # File lib/rexml/xpath_parser.rb, line 598
598:     def next_sibling_node(node)
599:       psn = node.next_sibling_node 
600:       while psn.nil?
601:         if node.parent.nil? or node.parent.class == Document 
602:           return nil
603:         end
604:         node = node.parent
605:         psn = node.next_sibling_node
606:         #puts "psn = #{psn.inspect}"
607:       end
608:       return psn
609:     end

[Source]

     # File lib/rexml/xpath_parser.rb, line 611
611:     def norm b
612:       case b
613:       when true, false
614:         return b
615:       when 'true', 'false'
616:         return Functions::boolean( b )
617:       when /^\d+(\.\d+)?$/
618:         return Functions::number( b )
619:       else
620:         return Functions::string( b )
621:       end
622:     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

[Source]

     # File lib/rexml/xpath_parser.rb, line 533
533:     def preceding( node )
534:       #puts "IN PRECEDING"
535:       ancestors = []
536:       p = node.parent
537:       while p
538:         ancestors << p
539:         p = p.parent
540:       end
541: 
542:       acc = []
543:       p = preceding_node_of( node )
544:       #puts "P = #{p.inspect}"
545:       while p
546:         if ancestors.include? p
547:           ancestors.delete(p)
548:         else
549:           acc << p
550:         end
551:         p = preceding_node_of( p )
552:         #puts "P = #{p.inspect}"
553:       end
554:       acc
555:     end

[Source]

     # File lib/rexml/xpath_parser.rb, line 557
557:     def preceding_node_of( node )
558:      #puts "NODE: #{node.inspect}"
559:      #puts "PREVIOUS NODE: #{node.previous_sibling_node.inspect}"
560:      #puts "PARENT NODE: #{node.parent}"
561:       psn = node.previous_sibling_node 
562:       if psn.nil?
563:         if node.parent.nil? or node.parent.class == Document 
564:           return nil
565:         end
566:         return node.parent
567:         #psn = preceding_node_of( node.parent )
568:       end
569:       while psn and psn.kind_of? Element and psn.children.size > 0
570:         psn = psn.children[-1]
571:       end
572:       psn
573:     end

[Source]

     # File lib/rexml/xpath_parser.rb, line 520
520:     def recurse( nodeset, &block )
521:       for node in nodeset
522:         yield node
523:         recurse( node, &block ) if node.node_type == :element
524:       end
525:     end

[Validate]