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