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