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!
# File lib/rexml/xpath_parser.rb, line 39 39: def initialize( ) 40: @parser = REXML::Parsers::XPathParser.new 41: @namespaces = nil 42: @variables = {} 43: end
# File lib/rexml/xpath_parser.rb, line 76 76: def []=( variable_name, value ) 77: @variables[ variable_name ] = value 78: 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!
# File lib/rexml/xpath_parser.rb, line 85 85: def first( path_stack, node ) 86: #puts "#{depth}) Entering match( #{path.inspect}, #{tree.inspect} )" 87: return nil if path.size == 0 88: 89: case path[0] 90: when :document 91: # do nothing 92: return first( path[1..-1], node ) 93: when :child 94: for c in node.children 95: #puts "#{depth}) CHILD checking #{name(c)}" 96: r = first( path[1..-1], c ) 97: #puts "#{depth}) RETURNING #{r.inspect}" if r 98: return r if r 99: end 100: when :qname 101: name = path[2] 102: #puts "#{depth}) QNAME #{name(tree)} == #{name} (path => #{path.size})" 103: if node.name == name 104: #puts "#{depth}) RETURNING #{tree.inspect}" if path.size == 3 105: return node if path.size == 3 106: return first( path[3..-1], node ) 107: else 108: return nil 109: end 110: when :descendant_or_self 111: r = first( path[1..-1], node ) 112: return r if r 113: for c in node.children 114: r = first( path, c ) 115: return r if r 116: end 117: when :node 118: return first( path[1..-1], node ) 119: when :any 120: return first( path[1..-1], node ) 121: end 122: return nil 123: end
# File lib/rexml/xpath_parser.rb, line 63 63: def get_first path, nodeset 64: #puts "#"*40 65: path_stack = @parser.parse( path ) 66: #puts "PARSE: #{path} => #{path_stack.inspect}" 67: #puts "PARSE: nodeset = #{nodeset.inspect}" 68: first( path_stack, nodeset ) 69: end
# File lib/rexml/xpath_parser.rb, line 126 126: def match( path_stack, nodeset ) 127: #puts "MATCH: path_stack = #{path_stack.inspect}" 128: #puts "MATCH: nodeset = #{nodeset.inspect}" 129: r = expr( path_stack, nodeset ) 130: #puts "MAIN EXPR => #{r.inspect}" 131: r 132: end
# File lib/rexml/xpath_parser.rb, line 45 45: def namespaces=( namespaces={} ) 46: Functions::namespace_context = namespaces 47: @namespaces = namespaces 48: end
# File lib/rexml/xpath_parser.rb, line 55 55: def parse path, nodeset 56: #puts "#"*40 57: path_stack = @parser.parse( path ) 58: #puts "PARSE: #{path} => #{path_stack.inspect}" 59: #puts "PARSE: nodeset = #{nodeset.inspect}" 60: match( path_stack, nodeset ) 61: end
# File lib/rexml/xpath_parser.rb, line 71 71: def predicate path, nodeset 72: path_stack = @parser.parse( path ) 73: expr( path_stack, nodeset ) 74: end
# File lib/rexml/xpath_parser.rb, line 50 50: def variables=( vars={} ) 51: Functions::variables = vars 52: @variables = vars 53: end
# File lib/rexml/xpath_parser.rb, line 768 768: def compare a, op, b 769: #puts "COMPARE #{a.inspect}(#{a.class.name}) #{op} #{b.inspect}(#{b.class.name})" 770: case op 771: when :eq 772: a == b 773: when :neq 774: a != b 775: when :lt 776: a < b 777: when :lteq 778: a <= b 779: when :gt 780: a > b 781: when :gteq 782: a >= b 783: when :and 784: a and b 785: when :or 786: a or b 787: else 788: false 789: end 790: end
# File lib/rexml/xpath_parser.rb, line 533 533: def d_o_s( p, ns, r ) 534: #puts "IN DOS with #{ns.inspect}; ALREADY HAVE #{r.inspect}" 535: nt = nil 536: ns.each_index do |i| 537: n = ns[i] 538: #puts "P => #{p.inspect}" 539: x = expr( p.dclone, [ n ] ) 540: nt = n.node_type 541: d_o_s( p, n.children, x ) if nt == :element or nt == :document and n.children.size > 0 542: r.concat(x) if x.size > 0 543: end 544: 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 is a wonder it works at all. ########################################################
# File lib/rexml/xpath_parser.rb, line 522 522: def descendant_or_self( path_stack, nodeset ) 523: rs = [] 524: #puts "#"*80 525: #puts "PATH_STACK = #{path_stack.inspect}" 526: #puts "NODESET = #{nodeset.collect{|n|n.inspect}.inspect}" 527: d_o_s( path_stack, nodeset, rs ) 528: #puts "RS = #{rs.collect{|n|n.inspect}.inspect}" 529: document_order(rs.flatten.compact) 530: #rs.flatten.compact 531: 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.
# File lib/rexml/xpath_parser.rb, line 555 555: def document_order( array_of_nodes ) 556: new_arry = [] 557: array_of_nodes.each { |node| 558: node_idx = [] 559: np = node.node_type == :attribute ? node.element : node 560: while np.parent and np.parent.node_type == :element 561: node_idx << np.parent.index( np ) 562: np = np.parent 563: end 564: new_arry << [ node_idx.reverse, node ] 565: } 566: #puts "new_arry = #{new_arry.inspect}" 567: new_arry.sort{ |s1, s2| s1[0] <=> s2[0] }.collect{ |s| s[1] } 568: end
# File lib/rexml/xpath_parser.rb, line 675 675: def equality_relational_compare( set1, op, set2 ) 676: #puts "EQ_REL_COMP(#{set1.inspect} #{op.inspect} #{set2.inspect})" 677: if set1.kind_of? Array and set2.kind_of? Array 678: #puts "#{set1.size} & #{set2.size}" 679: if set1.size == 1 and set2.size == 1 680: set1 = set1[0] 681: set2 = set2[0] 682: elsif set1.size == 0 or set2.size == 0 683: nd = set1.size==0 ? set2 : set1 684: rv = nd.collect { |il| compare( il, op, nil ) } 685: #puts "RV = #{rv.inspect}" 686: return rv 687: else 688: res = [] 689: enum = SyncEnumerator.new( set1, set2 ).each { |i1, i2| 690: #puts "i1 = #{i1.inspect} (#{i1.class.name})" 691: #puts "i2 = #{i2.inspect} (#{i2.class.name})" 692: i1 = norm( i1 ) 693: i2 = norm( i2 ) 694: res << compare( i1, op, i2 ) 695: } 696: return res 697: end 698: end 699: #puts "EQ_REL_COMP: #{set1.inspect} (#{set1.class.name}), #{op}, #{set2.inspect} (#{set2.class.name})" 700: #puts "COMPARING VALUES" 701: # If one is nodeset and other is number, compare number to each item 702: # in nodeset s.t. number op number(string(item)) 703: # If one is nodeset and other is string, compare string to each item 704: # in nodeset s.t. string op string(item) 705: # If one is nodeset and other is boolean, compare boolean to each item 706: # in nodeset s.t. boolean op boolean(item) 707: if set1.kind_of? Array or set2.kind_of? Array 708: #puts "ISA ARRAY" 709: if set1.kind_of? Array 710: a = set1 711: b = set2 712: else 713: a = set2 714: b = set1 715: end 716: 717: case b 718: when true, false 719: return a.collect {|v| compare( Functions::boolean(v), op, b ) } 720: when Numeric 721: return a.collect {|v| compare( Functions::number(v), op, b )} 722: when /^\d+(\.\d+)?$/ 723: b = Functions::number( b ) 724: #puts "B = #{b.inspect}" 725: return a.collect {|v| compare( Functions::number(v), op, b )} 726: else 727: #puts "Functions::string( #{b}(#{b.class.name}) ) = #{Functions::string(b)}" 728: b = Functions::string( b ) 729: return a.collect { |v| compare( Functions::string(v), op, b ) } 730: end 731: else 732: # If neither is nodeset, 733: # If op is = or != 734: # If either boolean, convert to boolean 735: # If either number, convert to number 736: # Else, convert to string 737: # Else 738: # Convert both to numbers and compare 739: s1 = set1.to_s 740: s2 = set2.to_s 741: #puts "EQ_REL_COMP: #{set1}=>#{s1}, #{set2}=>#{s2}" 742: if s1 == 'true' or s1 == 'false' or s2 == 'true' or s2 == 'false' 743: #puts "Functions::boolean(#{set1})=>#{Functions::boolean(set1)}" 744: #puts "Functions::boolean(#{set2})=>#{Functions::boolean(set2)}" 745: set1 = Functions::boolean( set1 ) 746: set2 = Functions::boolean( set2 ) 747: else 748: if op == :eq or op == :neq 749: if s1 =~ /^\d+(\.\d+)?$/ or s2 =~ /^\d+(\.\d+)?$/ 750: set1 = Functions::number( s1 ) 751: set2 = Functions::number( s2 ) 752: else 753: set1 = Functions::string( set1 ) 754: set2 = Functions::string( set2 ) 755: end 756: else 757: set1 = Functions::number( set1 ) 758: set2 = Functions::number( set2 ) 759: end 760: end 761: #puts "EQ_REL_COMP: #{set1} #{op} #{set2}" 762: #puts ">>> #{compare( set1, op, set2 )}" 763: return compare( set1, op, set2 ) 764: end 765: return false 766: end
# File lib/rexml/xpath_parser.rb, line 156 156: def expr( path_stack, nodeset, context=nil ) 157: #puts "#"*15 158: #puts "In expr with #{path_stack.inspect}" 159: #puts "Returning" if path_stack.length == 0 || nodeset.length == 0 160: node_types = ELEMENTS 161: return nodeset if path_stack.length == 0 || nodeset.length == 0 162: while path_stack.length > 0 163: #puts "#"*5 164: #puts "Path stack = #{path_stack.inspect}" 165: #puts "Nodeset is #{nodeset.inspect}" 166: if nodeset.length == 0 167: path_stack.clear 168: return [] 169: end 170: case (op = path_stack.shift) 171: when :document 172: nodeset = [ nodeset[0].root_node ] 173: #puts ":document, nodeset = #{nodeset.inspect}" 174: 175: when :qname 176: #puts "IN QNAME" 177: prefix = path_stack.shift 178: name = path_stack.shift 179: nodeset.delete_if do |node| 180: # FIXME: This DOUBLES the time XPath searches take 181: ns = get_namespace( node, prefix ) 182: #puts "NS = #{ns.inspect}" 183: #puts "node.node_type == :element => #{node.node_type == :element}" 184: if node.node_type == :element 185: #puts "node.name == #{name} => #{node.name == name}" 186: if node.name == name 187: #puts "node.namespace == #{ns.inspect} => #{node.namespace == ns}" 188: end 189: end 190: !(node.node_type == :element and 191: node.name == name and 192: node.namespace == ns ) 193: end 194: node_types = ELEMENTS 195: 196: when :any 197: #puts "ANY 1: nodeset = #{nodeset.inspect}" 198: #puts "ANY 1: node_types = #{node_types.inspect}" 199: nodeset.delete_if { |node| !node_types.include?(node.node_type) } 200: #puts "ANY 2: nodeset = #{nodeset.inspect}" 201: 202: when :self 203: # This space left intentionally blank 204: 205: when :processing_instruction 206: target = path_stack.shift 207: nodeset.delete_if do |node| 208: (node.node_type != :processing_instruction) or 209: ( target!='' and ( node.target != target ) ) 210: end 211: 212: when :text 213: nodeset.delete_if { |node| node.node_type != :text } 214: 215: when :comment 216: nodeset.delete_if { |node| node.node_type != :comment } 217: 218: when :node 219: # This space left intentionally blank 220: node_types = ALL 221: 222: when :child 223: new_nodeset = [] 224: nt = nil 225: for node in nodeset 226: nt = node.node_type 227: new_nodeset += node.children if nt == :element or nt == :document 228: end 229: nodeset = new_nodeset 230: node_types = ELEMENTS 231: 232: when :literal 233: return path_stack.shift 234: 235: when :attribute 236: new_nodeset = [] 237: case path_stack.shift 238: when :qname 239: prefix = path_stack.shift 240: name = path_stack.shift 241: for element in nodeset 242: if element.node_type == :element 243: #puts "Element name = #{element.name}" 244: #puts "get_namespace( #{element.inspect}, #{prefix} ) = #{get_namespace(element, prefix)}" 245: attrib = element.attribute( name, get_namespace(element, prefix) ) 246: #puts "attrib = #{attrib.inspect}" 247: new_nodeset << attrib if attrib 248: end 249: end 250: when :any 251: #puts "ANY" 252: for element in nodeset 253: if element.node_type == :element 254: new_nodeset += element.attributes.to_a 255: end 256: end 257: end 258: nodeset = new_nodeset 259: 260: when :parent 261: #puts "PARENT 1: nodeset = #{nodeset}" 262: nodeset = nodeset.collect{|n| n.parent}.compact 263: #nodeset = expr(path_stack.dclone, nodeset.collect{|n| n.parent}.compact) 264: #puts "PARENT 2: nodeset = #{nodeset.inspect}" 265: node_types = ELEMENTS 266: 267: when :ancestor 268: new_nodeset = [] 269: for node in nodeset 270: while node.parent 271: node = node.parent 272: new_nodeset << node unless new_nodeset.include? node 273: end 274: end 275: nodeset = new_nodeset 276: node_types = ELEMENTS 277: 278: when :ancestor_or_self 279: new_nodeset = [] 280: for node in nodeset 281: if node.node_type == :element 282: new_nodeset << node 283: while ( node.parent ) 284: node = node.parent 285: new_nodeset << node unless new_nodeset.include? node 286: end 287: end 288: end 289: nodeset = new_nodeset 290: node_types = ELEMENTS 291: 292: when :predicate 293: new_nodeset = [] 294: subcontext = { :size => nodeset.size } 295: pred = path_stack.shift 296: nodeset.each_with_index { |node, index| 297: subcontext[ :node ] = node 298: #puts "PREDICATE SETTING CONTEXT INDEX TO #{index+1}" 299: subcontext[ :index ] = index+1 300: pc = pred.dclone 301: #puts "#{node.hash}) Recursing with #{pred.inspect} and [#{node.inspect}]" 302: result = expr( pc, [node], subcontext ) 303: result = result[0] if result.kind_of? Array and result.length == 1 304: #puts "#{node.hash}) Result = #{result.inspect} (#{result.class.name})" 305: if result.kind_of? Numeric 306: #puts "Adding node #{node.inspect}" if result == (index+1) 307: new_nodeset << node if result == (index+1) 308: elsif result.instance_of? Array 309: if result.size > 0 and result.inject(false) {|k,s| s or k} 310: #puts "Adding node #{node.inspect}" if result.size > 0 311: new_nodeset << node if result.size > 0 312: end 313: else 314: #puts "Adding node #{node.inspect}" if result 315: new_nodeset << node if result 316: end 317: } 318: #puts "New nodeset = #{new_nodeset.inspect}" 319: #puts "Path_stack = #{path_stack.inspect}" 320: nodeset = new_nodeset 321: ?? 322: 323: when :descendant_or_self 324: rv = descendant_or_self( path_stack, nodeset ) 325: path_stack.clear 326: nodeset = rv 327: node_types = ELEMENTS 328: 329: when :descendant 330: results = [] 331: nt = nil 332: for node in nodeset 333: nt = node.node_type 334: results += expr( path_stack.dclone.unshift( :descendant_or_self ), 335: node.children ) if nt == :element or nt == :document 336: end 337: nodeset = results 338: node_types = ELEMENTS 339: 340: when :following_sibling 341: #puts "FOLLOWING_SIBLING 1: nodeset = #{nodeset}" 342: results = [] 343: nodeset.each do |node| 344: next if node.parent.nil? 345: all_siblings = node.parent.children 346: current_index = all_siblings.index( node ) 347: following_siblings = all_siblings[ current_index+1 .. -1 ] 348: results += expr( path_stack.dclone, following_siblings ) 349: end 350: #puts "FOLLOWING_SIBLING 2: nodeset = #{nodeset}" 351: nodeset = results 352: 353: when :preceding_sibling 354: results = [] 355: nodeset.each do |node| 356: next if node.parent.nil? 357: all_siblings = node.parent.children 358: current_index = all_siblings.index( node ) 359: preceding_siblings = all_siblings[ 0, current_index ].reverse 360: results += preceding_siblings 361: end 362: nodeset = results 363: node_types = ELEMENTS 364: 365: when :preceding 366: new_nodeset = [] 367: for node in nodeset 368: new_nodeset += preceding( node ) 369: end 370: #puts "NEW NODESET => #{new_nodeset.inspect}" 371: nodeset = new_nodeset 372: node_types = ELEMENTS 373: 374: when :following 375: new_nodeset = [] 376: for node in nodeset 377: new_nodeset += following( node ) 378: end 379: nodeset = new_nodeset 380: node_types = ELEMENTS 381: 382: when :namespace 383: #puts "In :namespace" 384: new_nodeset = [] 385: prefix = path_stack.shift 386: for node in nodeset 387: if (node.node_type == :element or node.node_type == :attribute) 388: if @namespaces 389: namespaces = @namespaces 390: elsif (node.node_type == :element) 391: namespaces = node.namespaces 392: else 393: namespaces = node.element.namesapces 394: end 395: #puts "Namespaces = #{namespaces.inspect}" 396: #puts "Prefix = #{prefix.inspect}" 397: #puts "Node.namespace = #{node.namespace}" 398: if (node.namespace == namespaces[prefix]) 399: new_nodeset << node 400: end 401: end 402: end 403: nodeset = new_nodeset 404: 405: when :variable 406: var_name = path_stack.shift 407: return @variables[ var_name ] 408: 409: # :and, :or, :eq, :neq, :lt, :lteq, :gt, :gteq 410: # TODO: Special case for :or and :and -- not evaluate the right 411: # operand if the left alone determines result (i.e. is true for 412: # :or and false for :and). 413: when :eq, :neq, :lt, :lteq, :gt, :gteq, :and, :or 414: left = expr( path_stack.shift, nodeset.dup, context ) 415: #puts "LEFT => #{left.inspect} (#{left.class.name})" 416: right = expr( path_stack.shift, nodeset.dup, context ) 417: #puts "RIGHT => #{right.inspect} (#{right.class.name})" 418: res = equality_relational_compare( left, op, right ) 419: #puts "RES => #{res.inspect}" 420: return res 421: 422: when :and 423: left = expr( path_stack.shift, nodeset.dup, context ) 424: #puts "LEFT => #{left.inspect} (#{left.class.name})" 425: if left == false || left.nil? || !left.inject(false) {|a,b| a | b} 426: return [] 427: end 428: right = expr( path_stack.shift, nodeset.dup, context ) 429: #puts "RIGHT => #{right.inspect} (#{right.class.name})" 430: res = equality_relational_compare( left, op, right ) 431: #puts "RES => #{res.inspect}" 432: return res 433: 434: when :div 435: left = Functions::number(expr(path_stack.shift, nodeset, context)).to_f 436: right = Functions::number(expr(path_stack.shift, nodeset, context)).to_f 437: return (left / right) 438: 439: when :mod 440: left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f 441: right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f 442: return (left % right) 443: 444: when :mult 445: left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f 446: right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f 447: return (left * right) 448: 449: when :plus 450: left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f 451: right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f 452: return (left + right) 453: 454: when :minus 455: left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f 456: right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f 457: return (left - right) 458: 459: when :union 460: left = expr( path_stack.shift, nodeset, context ) 461: right = expr( path_stack.shift, nodeset, context ) 462: return (left | right) 463: 464: when :neg 465: res = expr( path_stack, nodeset, context ) 466: return -(res.to_f) 467: 468: when :not 469: when :function 470: func_name = path_stack.shift.tr('-','_') 471: arguments = path_stack.shift 472: #puts "FUNCTION 0: #{func_name}(#{arguments.collect{|a|a.inspect}.join(', ')})" 473: subcontext = context ? nil : { :size => nodeset.size } 474: 475: res = [] 476: cont = context 477: nodeset.each_with_index { |n, i| 478: if subcontext 479: subcontext[:node] = n 480: subcontext[:index] = i 481: cont = subcontext 482: end 483: arg_clone = arguments.dclone 484: args = arg_clone.collect { |arg| 485: #puts "FUNCTION 1: Calling expr( #{arg.inspect}, [#{n.inspect}] )" 486: expr( arg, [n], cont ) 487: } 488: #puts "FUNCTION 2: #{func_name}(#{args.collect{|a|a.inspect}.join(', ')})" 489: Functions.context = cont 490: res << Functions.send( func_name, *args ) 491: #puts "FUNCTION 3: #{res[-1].inspect}" 492: } 493: return res 494: 495: end 496: end # while 497: #puts "EXPR returning #{nodeset.inspect}" 498: return nodeset 499: end
# File lib/rexml/xpath_parser.rb, line 626 626: def following( node ) 627: #puts "IN PRECEDING" 628: acc = [] 629: p = next_sibling_node( node ) 630: #puts "P = #{p.inspect}" 631: while p 632: acc << p 633: p = following_node_of( p ) 634: #puts "P = #{p.inspect}" 635: end 636: acc 637: end
# File lib/rexml/xpath_parser.rb, line 639 639: def following_node_of( node ) 640: #puts "NODE: #{node.inspect}" 641: #puts "PREVIOUS NODE: #{node.previous_sibling_node.inspect}" 642: #puts "PARENT NODE: #{node.parent}" 643: if node.kind_of? Element and node.children.size > 0 644: return node.children[0] 645: end 646: return next_sibling_node(node) 647: end
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 142 142: def get_namespace( node, prefix ) 143: if @namespaces 144: return @namespaces[prefix] || '' 145: else 146: return node.namespace( prefix ) if node.node_type == :element 147: return '' 148: end 149: end
# File lib/rexml/xpath_parser.rb, line 649 649: def next_sibling_node(node) 650: psn = node.next_sibling_node 651: while psn.nil? 652: if node.parent.nil? or node.parent.class == Document 653: return nil 654: end 655: node = node.parent 656: psn = node.next_sibling_node 657: #puts "psn = #{psn.inspect}" 658: end 659: return psn 660: end
# File lib/rexml/xpath_parser.rb, line 662 662: def norm b 663: case b 664: when true, false 665: return b 666: when 'true', 'false' 667: return Functions::boolean( b ) 668: when /^\d+(\.\d+)?$/ 669: return Functions::number( b ) 670: else 671: return Functions::string( b ) 672: end 673: 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
# File lib/rexml/xpath_parser.rb, line 584 584: def preceding( node ) 585: #puts "IN PRECEDING" 586: ancestors = [] 587: p = node.parent 588: while p 589: ancestors << p 590: p = p.parent 591: end 592: 593: acc = [] 594: p = preceding_node_of( node ) 595: #puts "P = #{p.inspect}" 596: while p 597: if ancestors.include? p 598: ancestors.delete(p) 599: else 600: acc << p 601: end 602: p = preceding_node_of( p ) 603: #puts "P = #{p.inspect}" 604: end 605: acc 606: end
# File lib/rexml/xpath_parser.rb, line 608 608: def preceding_node_of( node ) 609: #puts "NODE: #{node.inspect}" 610: #puts "PREVIOUS NODE: #{node.previous_sibling_node.inspect}" 611: #puts "PARENT NODE: #{node.parent}" 612: psn = node.previous_sibling_node 613: if psn.nil? 614: if node.parent.nil? or node.parent.class == Document 615: return nil 616: end 617: return node.parent 618: #psn = preceding_node_of( node.parent ) 619: end 620: while psn and psn.kind_of? Element and psn.children.size > 0 621: psn = psn.children[-1] 622: end 623: psn 624: end