Class | REXML::XPathParser |
In: |
lib/xmpp4r/rexmladdons.rb
|
Parent: | Object |
The XPath parser has bugs. Here is a patch.
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!
ALL | = | [ :attribute, :element, :text, :processing_instruction, :comment ] unless defined?(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 | = | [ :element ] unless defined?(ELEMENTS) |
LITERAL = /^’([^’]*)’|^"([^"]*)"/u
# File lib/xmpp4r/rexmladdons.rb, line 113 113: def initialize( ) 114: @parser = REXML::Parsers::XPathParser.new 115: @namespaces = {} 116: @variables = {} 117: end
# File lib/xmpp4r/rexmladdons.rb, line 150 150: def []=( variable_name, value ) 151: @variables[ variable_name ] = value 152: 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.
# File lib/xmpp4r/rexmladdons.rb, line 157 157: def first( path_stack, node ) 158: #puts "#{depth}) Entering match( #{path.inspect}, #{tree.inspect} )" 159: return nil if path.size == 0 160: 161: case path[0] 162: when :document 163: # do nothing 164: return first( path[1..-1], node ) 165: when :child 166: for c in node.children 167: #puts "#{depth}) CHILD checking #{name(c)}" 168: r = first( path[1..-1], c ) 169: #puts "#{depth}) RETURNING #{r.inspect}" if r 170: return r if r 171: end 172: when :qname 173: name = path[2] 174: #puts "#{depth}) QNAME #{name(tree)} == #{name} (path => #{path.size})" 175: if node.name == name 176: #puts "#{depth}) RETURNING #{tree.inspect}" if path.size == 3 177: return node if path.size == 3 178: return first( path[3..-1], node ) 179: else 180: return nil 181: end 182: when :descendant_or_self 183: r = first( path[1..-1], node ) 184: return r if r 185: for c in node.children 186: r = first( path, c ) 187: return r if r 188: end 189: when :node 190: return first( path[1..-1], node ) 191: when :any 192: return first( path[1..-1], node ) 193: end 194: return nil 195: end
# File lib/xmpp4r/rexmladdons.rb, line 137 137: def get_first path, nodeset 138: #puts "#"*40 139: path_stack = @parser.parse( path ) 140: #puts "PARSE: #{path} => #{path_stack.inspect}" 141: #puts "PARSE: nodeset = #{nodeset.inspect}" 142: first( path_stack, nodeset ) 143: end
# File lib/xmpp4r/rexmladdons.rb, line 198 198: def match( path_stack, nodeset ) 199: #puts "MATCH: path_stack = #{path_stack.inspect}" 200: #puts "MATCH: nodeset = #{nodeset.inspect}" 201: r = expr( path_stack, nodeset ) 202: #puts "MAIN EXPR => #{r.inspect}" 203: r 204: 205: #while ( path_stack.size > 0 and nodeset.size > 0 ) 206: # #puts "MATCH: #{path_stack.inspect} '#{nodeset.collect{|n|n.class}.inspect}'" 207: # nodeset = expr( path_stack, nodeset ) 208: # #puts "NODESET: #{nodeset.inspect}" 209: # #puts "PATH_STACK: #{path_stack.inspect}" 210: #end 211: #nodeset 212: end
# File lib/xmpp4r/rexmladdons.rb, line 119 119: def namespaces=( namespaces={} ) 120: Functions::namespace_context = namespaces 121: @namespaces = namespaces 122: end
# File lib/xmpp4r/rexmladdons.rb, line 129 129: def parse path, nodeset 130: #puts "#"*40 131: path_stack = @parser.parse( path ) 132: #puts "PARSE: #{path} => #{path_stack.inspect}" 133: #puts "PARSE: nodeset = #{nodeset.inspect}" 134: match( path_stack, nodeset ) 135: end
# File lib/xmpp4r/rexmladdons.rb, line 145 145: def predicate path, nodeset 146: path_stack = @parser.parse( path ) 147: expr( path_stack, nodeset ) 148: end
# File lib/xmpp4r/rexmladdons.rb, line 124 124: def variables=( vars={} ) 125: Functions::variables = vars 126: @variables = vars 127: end
# File lib/xmpp4r/rexmladdons.rb, line 795 795: def compare a, op, b 796: #puts "COMPARE #{a.inspect}(#{a.class.name}) #{op} #{b.inspect}(#{b.class.name})" 797: case op 798: when :eq 799: a == b 800: when :neq 801: a != b 802: when :lt 803: a < b 804: when :lteq 805: a <= b 806: when :gt 807: a > b 808: when :gteq 809: a >= b 810: when :and 811: a and b 812: when :or 813: a or b 814: else 815: false 816: end 817: end
# File lib/xmpp4r/rexmladdons.rb, line 560 560: def d_o_s( p, ns, r ) 561: #puts "IN DOS with #{ns.inspect}; ALREADY HAVE #{r.inspect}" 562: nt = nil 563: ns.each_index do |i| 564: n = ns[i] 565: #puts "P => #{p.inspect}" 566: x = expr( p.dclone, [ n ] ) 567: nt = n.node_type 568: d_o_s( p, n.children, x ) if nt == :element or nt == :document and n.children.size > 0 569: r.concat(x) if x.size > 0 570: end 571: 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. ########################################################
# File lib/xmpp4r/rexmladdons.rb, line 552 552: def descendant_or_self( path_stack, nodeset ) 553: rs = [] 554: d_o_s( path_stack, nodeset, rs ) 555: #puts "RS = #{rs.collect{|n|n.to_s}.inspect}" 556: document_order(rs.flatten.compact) 557: #rs.flatten.compact 558: 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/xmpp4r/rexmladdons.rb, line 582 582: def document_order( array_of_nodes ) 583: new_arry = [] 584: array_of_nodes.each { |node| 585: node_idx = [] 586: np = node.node_type == :attribute ? node.element : node 587: while np.parent and np.parent.node_type == :element 588: node_idx << np.parent.index( np ) 589: np = np.parent 590: end 591: new_arry << [ node_idx.reverse, node ] 592: } 593: #puts "new_arry = #{new_arry.inspect}" 594: new_arry.sort{ |s1, s2| s1[0] <=> s2[0] }.collect{ |s| s[1] } 595: end
# File lib/xmpp4r/rexmladdons.rb, line 702 702: def equality_relational_compare( set1, op, set2 ) 703: #puts "EQ_REL_COMP(#{set1.inspect} #{op.inspect} #{set2.inspect})" 704: if set1.kind_of? Array and set2.kind_of? Array 705: #puts "#{set1.size} & #{set2.size}" 706: if set1.size == 1 and set2.size == 1 707: set1 = set1[0] 708: set2 = set2[0] 709: elsif set1.size == 0 or set2.size == 0 710: nd = set1.size==0 ? set2 : set1 711: rv = nd.collect { |il| compare( il, op, nil ) } 712: #puts "RV = #{rv.inspect}" 713: return rv 714: else 715: res = [] 716: enum = SyncEnumerator.new( set1, set2 ).each { |i1, i2| 717: #puts "i1 = #{i1.inspect} (#{i1.class.name})" 718: #puts "i2 = #{i2.inspect} (#{i2.class.name})" 719: i1 = norm( i1 ) 720: i2 = norm( i2 ) 721: res << compare( i1, op, i2 ) 722: } 723: return res 724: end 725: end 726: #puts "EQ_REL_COMP: #{set1.inspect} (#{set1.class.name}), #{op}, #{set2.inspect} (#{set2.class.name})" 727: #puts "COMPARING VALUES" 728: # If one is nodeset and other is number, compare number to each item 729: # in nodeset s.t. number op number(string(item)) 730: # If one is nodeset and other is string, compare string to each item 731: # in nodeset s.t. string op string(item) 732: # If one is nodeset and other is boolean, compare boolean to each item 733: # in nodeset s.t. boolean op boolean(item) 734: if set1.kind_of? Array or set2.kind_of? Array 735: #puts "ISA ARRAY" 736: if set1.kind_of? Array 737: a = set1 738: b = set2 739: else 740: a = set2 741: b = set1 742: end 743: 744: case b 745: when true, false 746: return a.collect {|v| compare( Functions::boolean(v), op, b ) } 747: when Numeric 748: return a.collect {|v| compare( Functions::number(v), op, b )} 749: when /^\d+(\.\d+)?$/ 750: b = Functions::number( b ) 751: #puts "B = #{b.inspect}" 752: return a.collect {|v| compare( Functions::number(v), op, b )} 753: else 754: #puts "Functions::string( #{b}(#{b.class.name}) ) = #{Functions::string(b)}" 755: b = Functions::string( b ) 756: return a.collect { |v| compare( Functions::string(v), op, b ) } 757: end 758: else 759: # If neither is nodeset, 760: # If op is = or != 761: # If either boolean, convert to boolean 762: # If either number, convert to number 763: # Else, convert to string 764: # Else 765: # Convert both to numbers and compare 766: s1 = set1.to_s 767: s2 = set2.to_s 768: #puts "EQ_REL_COMP: #{set1}=>#{s1}, #{set2}=>#{s2}" 769: if s1 == 'true' or s1 == 'false' or s2 == 'true' or s2 == 'false' 770: #puts "Functions::boolean(#{set1})=>#{Functions::boolean(set1)}" 771: #puts "Functions::boolean(#{set2})=>#{Functions::boolean(set2)}" 772: set1 = Functions::boolean( set1 ) 773: set2 = Functions::boolean( set2 ) 774: else 775: if op == :eq or op == :neq 776: if s1 =~ /^\d+(\.\d+)?$/ or s2 =~ /^\d+(\.\d+)?$/ 777: set1 = Functions::number( s1 ) 778: set2 = Functions::number( s2 ) 779: else 780: set1 = Functions::string( set1 ) 781: set2 = Functions::string( set2 ) 782: end 783: else 784: set1 = Functions::number( set1 ) 785: set2 = Functions::number( set2 ) 786: end 787: end 788: #puts "EQ_REL_COMP: #{set1} #{op} #{set2}" 789: #puts ">>> #{compare( set1, op, set2 )}" 790: return compare( set1, op, set2 ) 791: end 792: return false 793: end
# File lib/xmpp4r/rexmladdons.rb, line 221 221: def expr( path_stack, nodeset, context=nil ) 222: #puts "#"*15 223: #puts "In expr with #{path_stack.inspect}" 224: #puts "Returning" if path_stack.length == 0 || nodeset.length == 0 225: node_types = ELEMENTS 226: return nodeset if path_stack.length == 0 || nodeset.length == 0 227: while path_stack.length > 0 228: #puts "Path stack = #{path_stack.inspect}" 229: #puts "Nodeset is #{nodeset.inspect}" 230: case (op = path_stack.shift) 231: when :document 232: nodeset = [ nodeset[0].root_node ] 233: #puts ":document, nodeset = #{nodeset.inspect}" 234: 235: when :qname 236: #puts "IN QNAME" 237: prefix = path_stack.shift 238: name = path_stack.shift 239: default_ns = @namespaces[prefix] 240: default_ns = default_ns ? default_ns : '' 241: nodeset.delete_if do |node| 242: ns = default_ns 243: # FIXME: This DOUBLES the time XPath searches take 244: ns = node.namespace( prefix ) if node.node_type == :element and ns == '' 245: #puts "NS = #{ns.inspect}" 246: #puts "node.node_type == :element => #{node.node_type == :element}" 247: if node.node_type == :element 248: #puts "node.name == #{name} => #{node.name == name}" 249: if node.name == name 250: #puts "node.namespace == #{ns.inspect} => #{node.namespace == ns}" 251: end 252: end 253: !(node.node_type == :element and 254: node.name == name and 255: node.namespace == ns ) 256: end 257: node_types = ELEMENTS 258: 259: when :any 260: #puts "ANY 1: nodeset = #{nodeset.inspect}" 261: #puts "ANY 1: node_types = #{node_types.inspect}" 262: nodeset.delete_if { |node| !node_types.include?(node.node_type) } 263: #puts "ANY 2: nodeset = #{nodeset.inspect}" 264: 265: when :self 266: # This space left intentionally blank 267: 268: when :processing_instruction 269: target = path_stack.shift 270: nodeset.delete_if do |node| 271: (node.node_type != :processing_instruction) or 272: ( target!='' and ( node.target != target ) ) 273: end 274: 275: when :text 276: nodeset.delete_if { |node| node.node_type != :text } 277: 278: when :comment 279: nodeset.delete_if { |node| node.node_type != :comment } 280: 281: when :node 282: # This space left intentionally blank 283: node_types = ALL 284: 285: when :child 286: new_nodeset = [] 287: nt = nil 288: for node in nodeset 289: nt = node.node_type 290: new_nodeset += node.children if nt == :element or nt == :document 291: end 292: nodeset = new_nodeset 293: node_types = ELEMENTS 294: 295: when :literal 296: literal = path_stack.shift 297: if literal =~ /^\d+(\.\d+)?$/ 298: return ($1 ? literal.to_f : literal.to_i) 299: end 300: return literal 301: 302: when :attribute 303: new_nodeset = [] 304: case path_stack.shift 305: when :qname 306: prefix = path_stack.shift 307: name = path_stack.shift 308: for element in nodeset 309: if element.node_type == :element 310: #puts element.name 311: attr = element.attribute( name, @namespaces[prefix] ) 312: new_nodeset << attr if attr 313: end 314: end 315: when :any 316: #puts "ANY" 317: for element in nodeset 318: if element.node_type == :element 319: new_nodeset += element.attributes.to_a 320: end 321: end 322: end 323: nodeset = new_nodeset 324: 325: when :parent 326: #puts "PARENT 1: nodeset = #{nodeset}" 327: nodeset = nodeset.collect{|n| n.parent}.compact 328: #nodeset = expr(path_stack.dclone, nodeset.collect{|n| n.parent}.compact) 329: #puts "PARENT 2: nodeset = #{nodeset.inspect}" 330: node_types = ELEMENTS 331: 332: when :ancestor 333: new_nodeset = [] 334: for node in nodeset 335: while node.parent 336: node = node.parent 337: new_nodeset << node unless new_nodeset.include? node 338: end 339: end 340: nodeset = new_nodeset 341: node_types = ELEMENTS 342: 343: when :ancestor_or_self 344: new_nodeset = [] 345: for node in nodeset 346: if node.node_type == :element 347: new_nodeset << node 348: while ( node.parent ) 349: node = node.parent 350: new_nodeset << node unless new_nodeset.include? node 351: end 352: end 353: end 354: nodeset = new_nodeset 355: node_types = ELEMENTS 356: 357: when :predicate 358: new_nodeset = [] 359: subcontext = { :size => nodeset.size } 360: pred = path_stack.shift 361: nodeset.each_with_index { |node, index| 362: subcontext[ :node ] = node 363: #puts "PREDICATE SETTING CONTEXT INDEX TO #{index+1}" 364: subcontext[ :index ] = index+1 365: pc = pred.dclone 366: #puts "#{node.hash}) Recursing with #{pred.inspect} and [#{node.inspect}]" 367: result = expr( pc, [node], subcontext ) 368: result = result[0] if result.kind_of? Array and result.length == 1 369: #puts "#{node.hash}) Result = #{result.inspect} (#{result.class.name})" 370: if result.kind_of? Numeric 371: #puts "Adding node #{node.inspect}" if result == (index+1) 372: new_nodeset << node if result == (index+1) 373: elsif result.instance_of? Array 374: #puts "Adding node #{node.inspect}" if result.size > 0 375: new_nodeset << node if result.size > 0 376: else 377: #puts "Adding node #{node.inspect}" if result 378: new_nodeset << node if result 379: end 380: } 381: #puts "New nodeset = #{new_nodeset.inspect}" 382: #puts "Path_stack = #{path_stack.inspect}" 383: nodeset = new_nodeset 384: ?? 385: 386: when :descendant_or_self 387: rv = descendant_or_self( path_stack, nodeset ) 388: path_stack.clear 389: nodeset = rv 390: node_types = ELEMENTS 391: 392: when :descendant 393: results = [] 394: nt = nil 395: for node in nodeset 396: nt = node.node_type 397: results += expr( path_stack.dclone.unshift( :descendant_or_self ), 398: node.children ) if nt == :element or nt == :document 399: end 400: nodeset = results 401: node_types = ELEMENTS 402: 403: when :following_sibling 404: #puts "FOLLOWING_SIBLING 1: nodeset = #{nodeset}" 405: results = [] 406: for node in nodeset 407: all_siblings = node.parent.children 408: current_index = all_siblings.index( node ) 409: following_siblings = all_siblings[ current_index+1 .. -1 ] 410: results += expr( path_stack.dclone, following_siblings ) 411: end 412: #puts "FOLLOWING_SIBLING 2: nodeset = #{nodeset}" 413: nodeset = results 414: 415: when :preceding_sibling 416: results = [] 417: for node in nodeset 418: all_siblings = node.parent.children 419: current_index = all_siblings.index( node ) 420: preceding_siblings = all_siblings[ 0 .. current_index-1 ].reverse 421: #results += expr( path_stack.dclone, preceding_siblings ) 422: end 423: nodeset = preceding_siblings 424: node_types = ELEMENTS 425: 426: when :preceding 427: new_nodeset = [] 428: for node in nodeset 429: new_nodeset += preceding( node ) 430: end 431: #puts "NEW NODESET => #{new_nodeset.inspect}" 432: nodeset = new_nodeset 433: node_types = ELEMENTS 434: 435: when :following 436: new_nodeset = [] 437: for node in nodeset 438: new_nodeset += following( node ) 439: end 440: nodeset = new_nodeset 441: node_types = ELEMENTS 442: 443: when :namespace 444: new_set = [] 445: for node in nodeset 446: new_nodeset << node.namespace if node.node_type == :element or node.node_type == :attribute 447: end 448: nodeset = new_nodeset 449: 450: when :variable 451: var_name = path_stack.shift 452: return @variables[ var_name ] 453: 454: # :and, :or, :eq, :neq, :lt, :lteq, :gt, :gteq 455: when :eq, :neq, :lt, :lteq, :gt, :gteq, :and, :or 456: left = expr( path_stack.shift, nodeset, context ) 457: #puts "LEFT => #{left.inspect} (#{left.class.name})" 458: right = expr( path_stack.shift, nodeset, context ) 459: #puts "RIGHT => #{right.inspect} (#{right.class.name})" 460: res = equality_relational_compare( left, op, right ) 461: #puts "RES => #{res.inspect}" 462: return res 463: 464: when :div 465: left = Functions::number(expr(path_stack.shift, nodeset, context)).to_f 466: right = Functions::number(expr(path_stack.shift, nodeset, context)).to_f 467: return (left / right) 468: 469: when :mod 470: left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f 471: right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f 472: return (left % right) 473: 474: when :mult 475: left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f 476: right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f 477: return (left * right) 478: 479: when :plus 480: left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f 481: right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f 482: return (left + right) 483: 484: when :minus 485: left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f 486: right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f 487: return (left - right) 488: 489: when :union 490: left = expr( path_stack.shift, nodeset, context ) 491: right = expr( path_stack.shift, nodeset, context ) 492: return (left | right) 493: 494: when :neg 495: res = expr( path_stack, nodeset, context ) 496: return -(res.to_f) 497: 498: when :not 499: when :function 500: func_name = path_stack.shift.tr('-','_') 501: arguments = path_stack.shift 502: #puts "FUNCTION 0: #{func_name}(#{arguments.collect{|a|a.inspect}.join(', ')})" 503: subcontext = context ? nil : { :size => nodeset.size } 504: 505: res = [] 506: cont = context 507: nodeset.each_with_index { |n, i| 508: if subcontext 509: subcontext[:node] = n 510: subcontext[:index] = i 511: cont = subcontext 512: end 513: arg_clone = arguments.dclone 514: args = arg_clone.collect { |arg| 515: #puts "FUNCTION 1: Calling expr( #{arg.inspect}, [#{n.inspect}] )" 516: expr( arg, [n], cont ) 517: } 518: #puts "FUNCTION 2: #{func_name}(#{args.collect{|a|a.inspect}.join(', ')})" 519: Functions.context = cont 520: res << Functions.send( func_name, *args ) 521: #puts "FUNCTION 3: #{res[-1].inspect}" 522: } 523: return res 524: 525: end 526: end # while 527: #puts "EXPR returning #{nodeset.inspect}" 528: return nodeset 529: end
# File lib/xmpp4r/rexmladdons.rb, line 653 653: def following( node ) 654: #puts "IN PRECEDING" 655: acc = [] 656: p = next_sibling_node( node ) 657: #puts "P = #{p.inspect}" 658: while p 659: acc << p 660: p = following_node_of( p ) 661: #puts "P = #{p.inspect}" 662: end 663: acc 664: end
# File lib/xmpp4r/rexmladdons.rb, line 666 666: def following_node_of( node ) 667: #puts "NODE: #{node.inspect}" 668: #puts "PREVIOUS NODE: #{node.previous_sibling_node.inspect}" 669: #puts "PARENT NODE: #{node.parent}" 670: if node.kind_of? Element and node.children.size > 0 671: return node.children[0] 672: end 673: return next_sibling_node(node) 674: end
# File lib/xmpp4r/rexmladdons.rb, line 676 676: def next_sibling_node(node) 677: psn = node.next_sibling_node 678: while psn.nil? 679: if node.parent.nil? or node.parent.class == Document 680: return nil 681: end 682: node = node.parent 683: psn = node.next_sibling_node 684: #puts "psn = #{psn.inspect}" 685: end 686: return psn 687: end
# File lib/xmpp4r/rexmladdons.rb, line 689 689: def norm b 690: case b 691: when true, false 692: return b 693: when 'true', 'false' 694: return Functions::boolean( b ) 695: when /^\d+(\.\d+)?$/ 696: return Functions::number( b ) 697: else 698: return Functions::string( b ) 699: end 700: 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/xmpp4r/rexmladdons.rb, line 611 611: def preceding( node ) 612: #puts "IN PRECEDING" 613: ancestors = [] 614: p = node.parent 615: while p 616: ancestors << p 617: p = p.parent 618: end 619: 620: acc = [] 621: p = preceding_node_of( node ) 622: #puts "P = #{p.inspect}" 623: while p 624: if ancestors.include? p 625: ancestors.delete(p) 626: else 627: acc << p 628: end 629: p = preceding_node_of( p ) 630: #puts "P = #{p.inspect}" 631: end 632: acc 633: end
# File lib/xmpp4r/rexmladdons.rb, line 635 635: def preceding_node_of( node ) 636: #puts "NODE: #{node.inspect}" 637: #puts "PREVIOUS NODE: #{node.previous_sibling_node.inspect}" 638: #puts "PARENT NODE: #{node.parent}" 639: psn = node.previous_sibling_node 640: if psn.nil? 641: if node.parent.nil? or node.parent.class == Document 642: return nil 643: end 644: return node.parent 645: #psn = preceding_node_of( node.parent ) 646: end 647: while psn and psn.kind_of? Element and psn.children.size > 0 648: psn = psn.children[-1] 649: end 650: psn 651: end