1require 'rexml/namespace' 2require 'rexml/xmltokens' 3require 'rexml/attribute' 4require 'rexml/syncenumerator' 5require 'rexml/parsers/xpathparser' 6 7class Object 8 # provides a unified +clone+ operation, for REXML::XPathParser 9 # to use across multiple Object types 10 def dclone 11 clone 12 end 13end 14class Symbol 15 # provides a unified +clone+ operation, for REXML::XPathParser 16 # to use across multiple Object types 17 def dclone ; self ; end 18end 19class Fixnum 20 # provides a unified +clone+ operation, for REXML::XPathParser 21 # to use across multiple Object types 22 def dclone ; self ; end 23end 24class Float 25 # provides a unified +clone+ operation, for REXML::XPathParser 26 # to use across multiple Object types 27 def dclone ; self ; end 28end 29class Array 30 # provides a unified +clone+ operation, for REXML::XPathParser 31 # to use across multiple Object+ types 32 def dclone 33 klone = self.clone 34 klone.clear 35 self.each{|v| klone << v.dclone} 36 klone 37 end 38end 39 40module REXML 41 # You don't want to use this class. Really. Use XPath, which is a wrapper 42 # for this class. Believe me. You don't want to poke around in here. 43 # There is strange, dark magic at work in this code. Beware. Go back! Go 44 # back while you still can! 45 class XPathParser 46 include XMLTokens 47 LITERAL = /^'([^']*)'|^"([^"]*)"/u 48 49 def initialize( ) 50 @parser = REXML::Parsers::XPathParser.new 51 @namespaces = nil 52 @variables = {} 53 end 54 55 def namespaces=( namespaces={} ) 56 Functions::namespace_context = namespaces 57 @namespaces = namespaces 58 end 59 60 def variables=( vars={} ) 61 Functions::variables = vars 62 @variables = vars 63 end 64 65 def parse path, nodeset 66 #puts "#"*40 67 path_stack = @parser.parse( path ) 68 #puts "PARSE: #{path} => #{path_stack.inspect}" 69 #puts "PARSE: nodeset = #{nodeset.inspect}" 70 match( path_stack, nodeset ) 71 end 72 73 def get_first path, nodeset 74 #puts "#"*40 75 path_stack = @parser.parse( path ) 76 #puts "PARSE: #{path} => #{path_stack.inspect}" 77 #puts "PARSE: nodeset = #{nodeset.inspect}" 78 first( path_stack, nodeset ) 79 end 80 81 def predicate path, nodeset 82 path_stack = @parser.parse( path ) 83 expr( path_stack, nodeset ) 84 end 85 86 def []=( variable_name, value ) 87 @variables[ variable_name ] = value 88 end 89 90 91 # Performs a depth-first (document order) XPath search, and returns the 92 # first match. This is the fastest, lightest way to return a single result. 93 # 94 # FIXME: This method is incomplete! 95 def first( path_stack, node ) 96 #puts "#{depth}) Entering match( #{path.inspect}, #{tree.inspect} )" 97 return nil if path.size == 0 98 99 case path[0] 100 when :document 101 # do nothing 102 return first( path[1..-1], node ) 103 when :child 104 for c in node.children 105 #puts "#{depth}) CHILD checking #{name(c)}" 106 r = first( path[1..-1], c ) 107 #puts "#{depth}) RETURNING #{r.inspect}" if r 108 return r if r 109 end 110 when :qname 111 name = path[2] 112 #puts "#{depth}) QNAME #{name(tree)} == #{name} (path => #{path.size})" 113 if node.name == name 114 #puts "#{depth}) RETURNING #{tree.inspect}" if path.size == 3 115 return node if path.size == 3 116 return first( path[3..-1], node ) 117 else 118 return nil 119 end 120 when :descendant_or_self 121 r = first( path[1..-1], node ) 122 return r if r 123 for c in node.children 124 r = first( path, c ) 125 return r if r 126 end 127 when :node 128 return first( path[1..-1], node ) 129 when :any 130 return first( path[1..-1], node ) 131 end 132 return nil 133 end 134 135 136 def match( path_stack, nodeset ) 137 #puts "MATCH: path_stack = #{path_stack.inspect}" 138 #puts "MATCH: nodeset = #{nodeset.inspect}" 139 r = expr( path_stack, nodeset ) 140 #puts "MAIN EXPR => #{r.inspect}" 141 r 142 end 143 144 private 145 146 147 # Returns a String namespace for a node, given a prefix 148 # The rules are: 149 # 150 # 1. Use the supplied namespace mapping first. 151 # 2. If no mapping was supplied, use the context node to look up the namespace 152 def get_namespace( node, prefix ) 153 if @namespaces 154 return @namespaces[prefix] || '' 155 else 156 return node.namespace( prefix ) if node.node_type == :element 157 return '' 158 end 159 end 160 161 162 # Expr takes a stack of path elements and a set of nodes (either a Parent 163 # or an Array and returns an Array of matching nodes 164 ALL = [ :attribute, :element, :text, :processing_instruction, :comment ] 165 ELEMENTS = [ :element ] 166 def expr( path_stack, nodeset, context=nil ) 167 #puts "#"*15 168 #puts "In expr with #{path_stack.inspect}" 169 #puts "Returning" if path_stack.length == 0 || nodeset.length == 0 170 node_types = ELEMENTS 171 return nodeset if path_stack.length == 0 || nodeset.length == 0 172 while path_stack.length > 0 173 #puts "#"*5 174 #puts "Path stack = #{path_stack.inspect}" 175 #puts "Nodeset is #{nodeset.inspect}" 176 if nodeset.length == 0 177 path_stack.clear 178 return [] 179 end 180 case (op = path_stack.shift) 181 when :document 182 nodeset = [ nodeset[0].root_node ] 183 #puts ":document, nodeset = #{nodeset.inspect}" 184 185 when :qname 186 #puts "IN QNAME" 187 prefix = path_stack.shift 188 name = path_stack.shift 189 nodeset.delete_if do |node| 190 # FIXME: This DOUBLES the time XPath searches take 191 ns = get_namespace( node, prefix ) 192 #puts "NS = #{ns.inspect}" 193 #puts "node.node_type == :element => #{node.node_type == :element}" 194 if node.node_type == :element 195 #puts "node.name == #{name} => #{node.name == name}" 196 if node.name == name 197 #puts "node.namespace == #{ns.inspect} => #{node.namespace == ns}" 198 end 199 end 200 !(node.node_type == :element and 201 node.name == name and 202 node.namespace == ns ) 203 end 204 node_types = ELEMENTS 205 206 when :any 207 #puts "ANY 1: nodeset = #{nodeset.inspect}" 208 #puts "ANY 1: node_types = #{node_types.inspect}" 209 nodeset.delete_if { |node| !node_types.include?(node.node_type) } 210 #puts "ANY 2: nodeset = #{nodeset.inspect}" 211 212 when :self 213 # This space left intentionally blank 214 215 when :processing_instruction 216 target = path_stack.shift 217 nodeset.delete_if do |node| 218 (node.node_type != :processing_instruction) or 219 ( target!='' and ( node.target != target ) ) 220 end 221 222 when :text 223 nodeset.delete_if { |node| node.node_type != :text } 224 225 when :comment 226 nodeset.delete_if { |node| node.node_type != :comment } 227 228 when :node 229 # This space left intentionally blank 230 node_types = ALL 231 232 when :child 233 new_nodeset = [] 234 nt = nil 235 nodeset.each do |node| 236 nt = node.node_type 237 new_nodeset += node.children if nt == :element or nt == :document 238 end 239 nodeset = new_nodeset 240 node_types = ELEMENTS 241 242 when :literal 243 return path_stack.shift 244 245 when :attribute 246 new_nodeset = [] 247 case path_stack.shift 248 when :qname 249 prefix = path_stack.shift 250 name = path_stack.shift 251 for element in nodeset 252 if element.node_type == :element 253 #puts "Element name = #{element.name}" 254 #puts "get_namespace( #{element.inspect}, #{prefix} ) = #{get_namespace(element, prefix)}" 255 attrib = element.attribute( name, get_namespace(element, prefix) ) 256 #puts "attrib = #{attrib.inspect}" 257 new_nodeset << attrib if attrib 258 end 259 end 260 when :any 261 #puts "ANY" 262 for element in nodeset 263 if element.node_type == :element 264 new_nodeset += element.attributes.to_a 265 end 266 end 267 end 268 nodeset = new_nodeset 269 270 when :parent 271 #puts "PARENT 1: nodeset = #{nodeset}" 272 nodeset = nodeset.collect{|n| n.parent}.compact 273 #nodeset = expr(path_stack.dclone, nodeset.collect{|n| n.parent}.compact) 274 #puts "PARENT 2: nodeset = #{nodeset.inspect}" 275 node_types = ELEMENTS 276 277 when :ancestor 278 new_nodeset = [] 279 nodeset.each do |node| 280 while node.parent 281 node = node.parent 282 new_nodeset << node unless new_nodeset.include? node 283 end 284 end 285 nodeset = new_nodeset 286 node_types = ELEMENTS 287 288 when :ancestor_or_self 289 new_nodeset = [] 290 nodeset.each do |node| 291 if node.node_type == :element 292 new_nodeset << node 293 while ( node.parent ) 294 node = node.parent 295 new_nodeset << node unless new_nodeset.include? node 296 end 297 end 298 end 299 nodeset = new_nodeset 300 node_types = ELEMENTS 301 302 when :predicate 303 new_nodeset = [] 304 subcontext = { :size => nodeset.size } 305 pred = path_stack.shift 306 nodeset.each_with_index { |node, index| 307 subcontext[ :node ] = node 308 #puts "PREDICATE SETTING CONTEXT INDEX TO #{index+1}" 309 subcontext[ :index ] = index+1 310 pc = pred.dclone 311 #puts "#{node.hash}) Recursing with #{pred.inspect} and [#{node.inspect}]" 312 result = expr( pc, [node], subcontext ) 313 result = result[0] if result.kind_of? Array and result.length == 1 314 #puts "#{node.hash}) Result = #{result.inspect} (#{result.class.name})" 315 if result.kind_of? Numeric 316 #puts "Adding node #{node.inspect}" if result == (index+1) 317 new_nodeset << node if result == (index+1) 318 elsif result.instance_of? Array 319 if result.size > 0 and result.inject(false) {|k,s| s or k} 320 #puts "Adding node #{node.inspect}" if result.size > 0 321 new_nodeset << node if result.size > 0 322 end 323 else 324 #puts "Adding node #{node.inspect}" if result 325 new_nodeset << node if result 326 end 327 } 328 #puts "New nodeset = #{new_nodeset.inspect}" 329 #puts "Path_stack = #{path_stack.inspect}" 330 nodeset = new_nodeset 331=begin 332 predicate = path_stack.shift 333 ns = nodeset.clone 334 result = expr( predicate, ns ) 335 #puts "Result = #{result.inspect} (#{result.class.name})" 336 #puts "nodeset = #{nodeset.inspect}" 337 if result.kind_of? Array 338 nodeset = result.zip(ns).collect{|m,n| n if m}.compact 339 else 340 nodeset = result ? nodeset : [] 341 end 342 #puts "Outgoing NS = #{nodeset.inspect}" 343=end 344 345 when :descendant_or_self 346 rv = descendant_or_self( path_stack, nodeset ) 347 path_stack.clear 348 nodeset = rv 349 node_types = ELEMENTS 350 351 when :descendant 352 results = [] 353 nt = nil 354 nodeset.each do |node| 355 nt = node.node_type 356 results += expr( path_stack.dclone.unshift( :descendant_or_self ), 357 node.children ) if nt == :element or nt == :document 358 end 359 nodeset = results 360 node_types = ELEMENTS 361 362 when :following_sibling 363 #puts "FOLLOWING_SIBLING 1: nodeset = #{nodeset}" 364 results = [] 365 nodeset.each do |node| 366 next if node.parent.nil? 367 all_siblings = node.parent.children 368 current_index = all_siblings.index( node ) 369 following_siblings = all_siblings[ current_index+1 .. -1 ] 370 results += expr( path_stack.dclone, following_siblings ) 371 end 372 #puts "FOLLOWING_SIBLING 2: nodeset = #{nodeset}" 373 nodeset = results 374 375 when :preceding_sibling 376 results = [] 377 nodeset.each do |node| 378 next if node.parent.nil? 379 all_siblings = node.parent.children 380 current_index = all_siblings.index( node ) 381 preceding_siblings = all_siblings[ 0, current_index ].reverse 382 results += preceding_siblings 383 end 384 nodeset = results 385 node_types = ELEMENTS 386 387 when :preceding 388 new_nodeset = [] 389 nodeset.each do |node| 390 new_nodeset += preceding( node ) 391 end 392 #puts "NEW NODESET => #{new_nodeset.inspect}" 393 nodeset = new_nodeset 394 node_types = ELEMENTS 395 396 when :following 397 new_nodeset = [] 398 nodeset.each do |node| 399 new_nodeset += following( node ) 400 end 401 nodeset = new_nodeset 402 node_types = ELEMENTS 403 404 when :namespace 405 #puts "In :namespace" 406 new_nodeset = [] 407 prefix = path_stack.shift 408 nodeset.each do |node| 409 if (node.node_type == :element or node.node_type == :attribute) 410 if @namespaces 411 namespaces = @namespaces 412 elsif (node.node_type == :element) 413 namespaces = node.namespaces 414 else 415 namespaces = node.element.namesapces 416 end 417 #puts "Namespaces = #{namespaces.inspect}" 418 #puts "Prefix = #{prefix.inspect}" 419 #puts "Node.namespace = #{node.namespace}" 420 if (node.namespace == namespaces[prefix]) 421 new_nodeset << node 422 end 423 end 424 end 425 nodeset = new_nodeset 426 427 when :variable 428 var_name = path_stack.shift 429 return @variables[ var_name ] 430 431 # :and, :or, :eq, :neq, :lt, :lteq, :gt, :gteq 432 # TODO: Special case for :or and :and -- not evaluate the right 433 # operand if the left alone determines result (i.e. is true for 434 # :or and false for :and). 435 when :eq, :neq, :lt, :lteq, :gt, :gteq, :or 436 left = expr( path_stack.shift, nodeset.dup, context ) 437 #puts "LEFT => #{left.inspect} (#{left.class.name})" 438 right = expr( path_stack.shift, nodeset.dup, context ) 439 #puts "RIGHT => #{right.inspect} (#{right.class.name})" 440 res = equality_relational_compare( left, op, right ) 441 #puts "RES => #{res.inspect}" 442 return res 443 444 when :and 445 left = expr( path_stack.shift, nodeset.dup, context ) 446 #puts "LEFT => #{left.inspect} (#{left.class.name})" 447 return [] unless left 448 if left.respond_to?(:inject) and !left.inject(false) {|a,b| a | b} 449 return [] 450 end 451 right = expr( path_stack.shift, nodeset.dup, context ) 452 #puts "RIGHT => #{right.inspect} (#{right.class.name})" 453 res = equality_relational_compare( left, op, right ) 454 #puts "RES => #{res.inspect}" 455 return res 456 457 when :div 458 left = Functions::number(expr(path_stack.shift, nodeset, context)).to_f 459 right = Functions::number(expr(path_stack.shift, nodeset, context)).to_f 460 return (left / right) 461 462 when :mod 463 left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f 464 right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f 465 return (left % right) 466 467 when :mult 468 left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f 469 right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f 470 return (left * right) 471 472 when :plus 473 left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f 474 right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f 475 return (left + right) 476 477 when :minus 478 left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f 479 right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f 480 return (left - right) 481 482 when :union 483 left = expr( path_stack.shift, nodeset, context ) 484 right = expr( path_stack.shift, nodeset, context ) 485 return (left | right) 486 487 when :neg 488 res = expr( path_stack, nodeset, context ) 489 return -(res.to_f) 490 491 when :not 492 when :function 493 func_name = path_stack.shift.tr('-','_') 494 arguments = path_stack.shift 495 #puts "FUNCTION 0: #{func_name}(#{arguments.collect{|a|a.inspect}.join(', ')})" 496 subcontext = context ? nil : { :size => nodeset.size } 497 498 res = [] 499 cont = context 500 nodeset.each_with_index { |n, i| 501 if subcontext 502 subcontext[:node] = n 503 subcontext[:index] = i 504 cont = subcontext 505 end 506 arg_clone = arguments.dclone 507 args = arg_clone.collect { |arg| 508 #puts "FUNCTION 1: Calling expr( #{arg.inspect}, [#{n.inspect}] )" 509 expr( arg, [n], cont ) 510 } 511 #puts "FUNCTION 2: #{func_name}(#{args.collect{|a|a.inspect}.join(', ')})" 512 Functions.context = cont 513 res << Functions.send( func_name, *args ) 514 #puts "FUNCTION 3: #{res[-1].inspect}" 515 } 516 return res 517 518 end 519 end # while 520 #puts "EXPR returning #{nodeset.inspect}" 521 return nodeset 522 end 523 524 525 ########################################################## 526 # FIXME 527 # The next two methods are BAD MOJO! 528 # This is my achilles heel. If anybody thinks of a better 529 # way of doing this, be my guest. This really sucks, but 530 # it is a wonder it works at all. 531 # ######################################################## 532 533 def descendant_or_self( path_stack, nodeset ) 534 rs = [] 535 #puts "#"*80 536 #puts "PATH_STACK = #{path_stack.inspect}" 537 #puts "NODESET = #{nodeset.collect{|n|n.inspect}.inspect}" 538 d_o_s( path_stack, nodeset, rs ) 539 #puts "RS = #{rs.collect{|n|n.inspect}.inspect}" 540 document_order(rs.flatten.compact) 541 #rs.flatten.compact 542 end 543 544 def d_o_s( p, ns, r ) 545 #puts "IN DOS with #{ns.inspect}; ALREADY HAVE #{r.inspect}" 546 nt = nil 547 ns.each_index do |i| 548 n = ns[i] 549 #puts "P => #{p.inspect}" 550 x = expr( p.dclone, [ n ] ) 551 nt = n.node_type 552 d_o_s( p, n.children, x ) if nt == :element or nt == :document and n.children.size > 0 553 r.concat(x) if x.size > 0 554 end 555 end 556 557 558 # Reorders an array of nodes so that they are in document order 559 # It tries to do this efficiently. 560 # 561 # FIXME: I need to get rid of this, but the issue is that most of the XPath 562 # interpreter functions as a filter, which means that we lose context going 563 # in and out of function calls. If I knew what the index of the nodes was, 564 # I wouldn't have to do this. Maybe add a document IDX for each node? 565 # Problems with mutable documents. Or, rewrite everything. 566 def document_order( array_of_nodes ) 567 new_arry = [] 568 array_of_nodes.each { |node| 569 node_idx = [] 570 np = node.node_type == :attribute ? node.element : node 571 while np.parent and np.parent.node_type == :element 572 node_idx << np.parent.index( np ) 573 np = np.parent 574 end 575 new_arry << [ node_idx.reverse, node ] 576 } 577 #puts "new_arry = #{new_arry.inspect}" 578 new_arry.sort{ |s1, s2| s1[0] <=> s2[0] }.collect{ |s| s[1] } 579 end 580 581 582 def recurse( nodeset, &block ) 583 for node in nodeset 584 yield node 585 recurse( node, &block ) if node.node_type == :element 586 end 587 end 588 589 590 591 # Builds a nodeset of all of the preceding nodes of the supplied node, 592 # in reverse document order 593 # preceding:: includes every element in the document that precedes this node, 594 # except for ancestors 595 def preceding( node ) 596 #puts "IN PRECEDING" 597 ancestors = [] 598 p = node.parent 599 while p 600 ancestors << p 601 p = p.parent 602 end 603 604 acc = [] 605 p = preceding_node_of( node ) 606 #puts "P = #{p.inspect}" 607 while p 608 if ancestors.include? p 609 ancestors.delete(p) 610 else 611 acc << p 612 end 613 p = preceding_node_of( p ) 614 #puts "P = #{p.inspect}" 615 end 616 acc 617 end 618 619 def preceding_node_of( node ) 620 #puts "NODE: #{node.inspect}" 621 #puts "PREVIOUS NODE: #{node.previous_sibling_node.inspect}" 622 #puts "PARENT NODE: #{node.parent}" 623 psn = node.previous_sibling_node 624 if psn.nil? 625 if node.parent.nil? or node.parent.class == Document 626 return nil 627 end 628 return node.parent 629 #psn = preceding_node_of( node.parent ) 630 end 631 while psn and psn.kind_of? Element and psn.children.size > 0 632 psn = psn.children[-1] 633 end 634 psn 635 end 636 637 def following( node ) 638 #puts "IN PRECEDING" 639 acc = [] 640 p = next_sibling_node( node ) 641 #puts "P = #{p.inspect}" 642 while p 643 acc << p 644 p = following_node_of( p ) 645 #puts "P = #{p.inspect}" 646 end 647 acc 648 end 649 650 def following_node_of( node ) 651 #puts "NODE: #{node.inspect}" 652 #puts "PREVIOUS NODE: #{node.previous_sibling_node.inspect}" 653 #puts "PARENT NODE: #{node.parent}" 654 if node.kind_of? Element and node.children.size > 0 655 return node.children[0] 656 end 657 return next_sibling_node(node) 658 end 659 660 def next_sibling_node(node) 661 psn = node.next_sibling_node 662 while psn.nil? 663 if node.parent.nil? or node.parent.class == Document 664 return nil 665 end 666 node = node.parent 667 psn = node.next_sibling_node 668 #puts "psn = #{psn.inspect}" 669 end 670 return psn 671 end 672 673 def norm b 674 case b 675 when true, false 676 return b 677 when 'true', 'false' 678 return Functions::boolean( b ) 679 when /^\d+(\.\d+)?$/ 680 return Functions::number( b ) 681 else 682 return Functions::string( b ) 683 end 684 end 685 686 def equality_relational_compare( set1, op, set2 ) 687 #puts "EQ_REL_COMP(#{set1.inspect} #{op.inspect} #{set2.inspect})" 688 if set1.kind_of? Array and set2.kind_of? Array 689 #puts "#{set1.size} & #{set2.size}" 690 if set1.size == 1 and set2.size == 1 691 set1 = set1[0] 692 set2 = set2[0] 693 elsif set1.size == 0 or set2.size == 0 694 nd = set1.size==0 ? set2 : set1 695 rv = nd.collect { |il| compare( il, op, nil ) } 696 #puts "RV = #{rv.inspect}" 697 return rv 698 else 699 res = [] 700 SyncEnumerator.new( set1, set2 ).each { |i1, i2| 701 #puts "i1 = #{i1.inspect} (#{i1.class.name})" 702 #puts "i2 = #{i2.inspect} (#{i2.class.name})" 703 i1 = norm( i1 ) 704 i2 = norm( i2 ) 705 res << compare( i1, op, i2 ) 706 } 707 return res 708 end 709 end 710 #puts "EQ_REL_COMP: #{set1.inspect} (#{set1.class.name}), #{op}, #{set2.inspect} (#{set2.class.name})" 711 #puts "COMPARING VALUES" 712 # If one is nodeset and other is number, compare number to each item 713 # in nodeset s.t. number op number(string(item)) 714 # If one is nodeset and other is string, compare string to each item 715 # in nodeset s.t. string op string(item) 716 # If one is nodeset and other is boolean, compare boolean to each item 717 # in nodeset s.t. boolean op boolean(item) 718 if set1.kind_of? Array or set2.kind_of? Array 719 #puts "ISA ARRAY" 720 if set1.kind_of? Array 721 a = set1 722 b = set2 723 else 724 a = set2 725 b = set1 726 end 727 728 case b 729 when true, false 730 return a.collect {|v| compare( Functions::boolean(v), op, b ) } 731 when Numeric 732 return a.collect {|v| compare( Functions::number(v), op, b )} 733 when /^\d+(\.\d+)?$/ 734 b = Functions::number( b ) 735 #puts "B = #{b.inspect}" 736 return a.collect {|v| compare( Functions::number(v), op, b )} 737 else 738 #puts "Functions::string( #{b}(#{b.class.name}) ) = #{Functions::string(b)}" 739 b = Functions::string( b ) 740 return a.collect { |v| compare( Functions::string(v), op, b ) } 741 end 742 else 743 # If neither is nodeset, 744 # If op is = or != 745 # If either boolean, convert to boolean 746 # If either number, convert to number 747 # Else, convert to string 748 # Else 749 # Convert both to numbers and compare 750 s1 = set1.to_s 751 s2 = set2.to_s 752 #puts "EQ_REL_COMP: #{set1}=>#{s1}, #{set2}=>#{s2}" 753 if s1 == 'true' or s1 == 'false' or s2 == 'true' or s2 == 'false' 754 #puts "Functions::boolean(#{set1})=>#{Functions::boolean(set1)}" 755 #puts "Functions::boolean(#{set2})=>#{Functions::boolean(set2)}" 756 set1 = Functions::boolean( set1 ) 757 set2 = Functions::boolean( set2 ) 758 else 759 if op == :eq or op == :neq 760 if s1 =~ /^\d+(\.\d+)?$/ or s2 =~ /^\d+(\.\d+)?$/ 761 set1 = Functions::number( s1 ) 762 set2 = Functions::number( s2 ) 763 else 764 set1 = Functions::string( set1 ) 765 set2 = Functions::string( set2 ) 766 end 767 else 768 set1 = Functions::number( set1 ) 769 set2 = Functions::number( set2 ) 770 end 771 end 772 #puts "EQ_REL_COMP: #{set1} #{op} #{set2}" 773 #puts ">>> #{compare( set1, op, set2 )}" 774 return compare( set1, op, set2 ) 775 end 776 return false 777 end 778 779 def compare a, op, b 780 #puts "COMPARE #{a.inspect}(#{a.class.name}) #{op} #{b.inspect}(#{b.class.name})" 781 case op 782 when :eq 783 a == b 784 when :neq 785 a != b 786 when :lt 787 a < b 788 when :lteq 789 a <= b 790 when :gt 791 a > b 792 when :gteq 793 a >= b 794 when :and 795 a and b 796 when :or 797 a or b 798 else 799 false 800 end 801 end 802 end 803end 804