rhodes 2.0.0.beta1 → 2.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. data/CHANGELOG +5 -0
  2. data/LICENSE +19 -674
  3. data/Manifest.txt +21 -4
  4. data/README.textile +5 -36
  5. data/Rakefile +5 -0
  6. data/lib/build/jake.rb +3 -1
  7. data/lib/extensions/rexml/rexml/document.rb +2 -0
  8. data/lib/extensions/rexml/rexml/parsers/baseparser.rb +0 -6
  9. data/lib/extensions/rhoxml/rexml/child.rb +96 -0
  10. data/lib/extensions/rhoxml/rexml/document.rb +527 -0
  11. data/lib/extensions/rhoxml/rexml/element.rb +987 -0
  12. data/lib/extensions/rhoxml/rexml/encoding.rb +71 -0
  13. data/lib/extensions/rhoxml/rexml/encodings/US-ASCII.rb +30 -0
  14. data/lib/extensions/rhoxml/rexml/encodings/UTF-16.rb +35 -0
  15. data/lib/extensions/rhoxml/rexml/encodings/UTF-8.rb +18 -0
  16. data/lib/extensions/rhoxml/rexml/namespace.rb +47 -0
  17. data/lib/extensions/rhoxml/rexml/node.rb +76 -0
  18. data/lib/extensions/rhoxml/rexml/parent.rb +166 -0
  19. data/lib/extensions/rhoxml/rexml/parseexception.rb +51 -0
  20. data/lib/extensions/rhoxml/rexml/parsers/baseparser.rb +531 -0
  21. data/lib/extensions/rhoxml/rexml/parsers/treeparser.rb +100 -0
  22. data/lib/extensions/rhoxml/rexml/parsers/xpathparser.rb +698 -0
  23. data/lib/extensions/rhoxml/rexml/set.rb +1274 -0
  24. data/lib/extensions/rhoxml/rexml/source.rb +258 -0
  25. data/lib/extensions/rhoxml/rexml/xmltokens.rb +18 -0
  26. data/lib/extensions/rhoxml/rexml/xpath.rb +77 -0
  27. data/lib/extensions/rhoxml/rexml/xpath_parser.rb +806 -0
  28. data/lib/framework/builtinME.rb +2 -0
  29. data/lib/framework/dateME.rb +5 -1
  30. data/lib/framework/rho/render.rb +10 -2
  31. data/lib/framework/rhom/rhom_object_factory.rb +2 -1
  32. data/lib/framework/singleton.rb +1 -1
  33. data/platform/android/Rhodes/jni/src/rhodes.cpp +2 -4
  34. data/platform/android/Rhodes/src/com/rhomobile/rhodes/NativeBar.java +23 -18
  35. data/platform/android/Rhodes/src/com/rhomobile/rhodes/Rhodes.java +42 -69
  36. data/platform/android/Rhodes/src/com/rhomobile/rhodes/SplashScreen.java +59 -7
  37. data/platform/android/Rhodes/src/com/rhomobile/rhodes/camera/Camera.java +1 -1
  38. data/platform/android/Rhodes/src/com/rhomobile/rhodes/mapview/Annotation.java +1 -0
  39. data/platform/android/Rhodes/src/com/rhomobile/rhodes/mapview/MapView.java +97 -85
  40. data/platform/android/Rhodes/src/com/rhomobile/rhodes/uri/SmsUriHandler.java +52 -0
  41. data/platform/android/build/RhodesSRC_build.files +1 -0
  42. data/platform/android/build/android.rake +38 -14
  43. data/platform/bb/RubyVM/RubyVM.jdp +1 -0
  44. data/platform/bb/build/RubyVM_build.files +1 -0
  45. data/platform/bb/build/bb.rake +44 -2
  46. data/platform/bb/rhodes/platform/5.0/com/rho/BrowserAdapter5.java +1 -1
  47. data/platform/bb/rhodes/rhodes.jdp +4 -4
  48. data/platform/bb/rhodes/src/com/rho/BrowserAdapter.java +8 -4
  49. data/platform/bb/rhodes/src/com/rho/rubyext/Alert.java +149 -17
  50. data/platform/bb/rhodes/src/rhomobile/PushListeningThread.java +20 -17
  51. data/platform/bb/rhodes/src/rhomobile/RhodesApplication.java +54 -28
  52. data/platform/bb/rhodes/src/rhomobile/mapview/Annotation.java +1 -0
  53. data/platform/bb/rhodes/src/rhomobile/mapview/GoogleMapField.java +37 -11
  54. data/platform/bb/rhodes/src/rhomobile/mapview/MapView.java +49 -19
  55. data/platform/bb/rhodes/src/rhomobile/mapview/MapViewScreen.java +6 -0
  56. data/platform/iphone/Classes/MapView/GoogleGeocoder.h +6 -7
  57. data/platform/iphone/Classes/MapView/GoogleGeocoder.m +70 -70
  58. data/platform/iphone/Classes/MapView/MapAnnotation.h +5 -3
  59. data/platform/iphone/Classes/MapView/MapAnnotation.m +10 -8
  60. data/platform/iphone/Classes/MapView/MapViewController.h +13 -10
  61. data/platform/iphone/Classes/MapView/MapViewController.m +131 -72
  62. data/platform/iphone/Classes/Rhodes.h +2 -0
  63. data/platform/iphone/Classes/Rhodes.m +13 -1
  64. data/platform/iphone/Classes/SimpleMainView.m +0 -1
  65. data/platform/iphone/Classes/TabbedMainView.m +5 -6
  66. data/platform/shared/common/RhoTime.h +2 -2
  67. data/platform/shared/common/RhodesApp.cpp +1 -1
  68. data/platform/shared/db/DBAdapter.cpp +6 -0
  69. data/platform/shared/net/CURLNetRequest.cpp +23 -1
  70. data/platform/shared/ruby/thread_win32.c +9 -1
  71. data/platform/shared/rubyJVM/src/com/rho/Capabilities.java +6 -0
  72. data/platform/shared/rubyJVM/src/com/rho/RhodesApp.java +1 -1
  73. data/platform/shared/rubyJVM/src/com/rho/sync/ClientRegister.java +1 -1
  74. data/platform/shared/rubyJVM/src/com/xruby/GeneratedMethods/RubySymbol_Methods.java +6 -1
  75. data/platform/shared/rubyJVM/src/com/xruby/GeneratedMethods/RubyTime_Methods.java +3 -3
  76. data/platform/shared/rubyJVM/src/com/xruby/runtime/builtin/InputStreamExecutor.java +1 -1
  77. data/platform/shared/rubyJVM/src/com/xruby/runtime/builtin/RubyArray.java +15 -3
  78. data/platform/shared/rubyJVM/src/com/xruby/runtime/builtin/RubyString.java +10 -2
  79. data/platform/shared/rubyJVM/src/com/xruby/runtime/builtin/RubyTime.java +12 -1
  80. data/platform/shared/rubyJVM/src/com/xruby/runtime/lang/RubyKernelModule.java +18 -9
  81. data/platform/shared/rubyJVM/src/com/xruby/runtime/lang/RubySymbol.java +5 -0
  82. data/platform/shared/rubyJVM/src/org/json/me/JSONArray.java +2 -1
  83. data/platform/shared/unzip/unzip.cpp +1 -1
  84. data/platform/wm/build/wm.rake +27 -6
  85. data/platform/wm/rhodes/Alert.cpp +335 -5
  86. data/platform/wm/rhodes/Alert.h +84 -1
  87. data/platform/wm/rhodes/MainWindow.cpp +28 -6
  88. data/platform/wm/rhodes/MainWindow.h +7 -2
  89. data/platform/wm/rhodes/Rhodes.cpp +23 -0
  90. data/platform/wm/rhodes/rho/rubyext/SystemImpl.cpp +2 -1
  91. data/platform/wm/rhodes/stdafx.h +1 -0
  92. data/platform/wm/tools/detool/detool.cpp +405 -14
  93. data/rakefile.rb +5 -0
  94. data/res/build-tools/detool.exe +0 -0
  95. data/res/generators/rhogen.rb +2 -0
  96. data/rhodes.gemspec +1 -1
  97. data/spec/framework_spec/app/spec/fixtures/object_values.txt +1 -1
  98. data/spec/framework_spec/app/spec/pagination/fixtures/object_values.txt +1 -1
  99. data/spec/framework_spec/app/spec/rhom_object_spec.rb +12 -12
  100. metadata +23 -6
  101. data/LICENSING_OPTIONS +0 -1
  102. data/platform/bb/build/rhodesApp.rapc +0 -9
  103. data/res/generators/templates/source/source_adapter.rb +0 -48
  104. data/rhobuild.yml +0 -37
@@ -0,0 +1,258 @@
1
+ require 'rexml/encoding'
2
+
3
+ module REXML
4
+ # Generates Source-s. USE THIS CLASS.
5
+ class SourceFactory
6
+ # Generates a Source object
7
+ # @param arg Either a String, or an IO
8
+ # @return a Source, or nil if a bad argument was given
9
+ def SourceFactory::create_from(arg)
10
+ if arg.respond_to?( :read ) and
11
+ arg.respond_to?( :readline ) and
12
+ arg.respond_to?( :nil? ) and
13
+ arg.respond_to?( :eof? )
14
+ IOSource.new(arg)
15
+ elsif arg.respond_to?( :to_str )
16
+ require 'stringio'
17
+ IOSource.new(StringIO.new(arg))
18
+ elsif arg.kind_of?( Source )
19
+ arg
20
+ else
21
+ raise "#{arg.class} is not a valid input stream. It must walk \n"+
22
+ "like either a String, an IO, or a Source."
23
+ end
24
+ end
25
+ end
26
+
27
+ # A Source can be searched for patterns, and wraps buffers and other
28
+ # objects and provides consumption of text
29
+ class Source
30
+ include Encoding
31
+ # The current buffer (what we're going to read next)
32
+ attr_reader :buffer
33
+ # The line number of the last consumed text
34
+ attr_reader :line
35
+ attr_reader :encoding
36
+
37
+ # Constructor
38
+ # @param arg must be a String, and should be a valid XML document
39
+ # @param encoding if non-null, sets the encoding of the source to this
40
+ # value, overriding all encoding detection
41
+ def initialize(arg, encoding=nil)
42
+ @orig = @buffer = arg
43
+ if encoding
44
+ self.encoding = encoding
45
+ else
46
+ self.encoding = check_encoding( @buffer )
47
+ end
48
+ @line = 0
49
+ end
50
+
51
+
52
+ # Inherited from Encoding
53
+ # Overridden to support optimized en/decoding
54
+ def encoding=(enc)
55
+ return unless super
56
+ @line_break = encode( '>' )
57
+ if enc != UTF_8
58
+ @buffer = decode(@buffer)
59
+ @to_utf = true
60
+ else
61
+ @to_utf = false
62
+ if @buffer.respond_to? :force_encoding
63
+ @buffer.force_encoding Encoding::UTF_8
64
+ end
65
+ end
66
+ end
67
+
68
+ # Scans the source for a given pattern. Note, that this is not your
69
+ # usual scan() method. For one thing, the pattern argument has some
70
+ # requirements; for another, the source can be consumed. You can easily
71
+ # confuse this method. Originally, the patterns were easier
72
+ # to construct and this method more robust, because this method
73
+ # generated search regexes on the fly; however, this was
74
+ # computationally expensive and slowed down the entire REXML package
75
+ # considerably, since this is by far the most commonly called method.
76
+ # @param pattern must be a Regexp, and must be in the form of
77
+ # /^\s*(#{your pattern, with no groups})(.*)/. The first group
78
+ # will be returned; the second group is used if the consume flag is
79
+ # set.
80
+ # @param consume if true, the pattern returned will be consumed, leaving
81
+ # everything after it in the Source.
82
+ # @return the pattern, if found, or nil if the Source is empty or the
83
+ # pattern is not found.
84
+ def scan(pattern, cons=false)
85
+ return nil if @buffer.nil?
86
+ rv = @buffer.scan(pattern)
87
+ @buffer = $' if cons and rv.size>0
88
+ rv
89
+ end
90
+
91
+ def read
92
+ end
93
+
94
+ def consume( pattern )
95
+ @buffer = $' if pattern.match( @buffer )
96
+ end
97
+
98
+ def match_to( char, pattern )
99
+ return pattern.match(@buffer)
100
+ end
101
+
102
+ def match_to_consume( char, pattern )
103
+ md = pattern.match(@buffer)
104
+ @buffer = $'
105
+ return md
106
+ end
107
+
108
+ def match(pattern, cons=false)
109
+ md = pattern.match(@buffer)
110
+ @buffer = $' if cons and md
111
+ return md
112
+ end
113
+
114
+ # @return true if the Source is exhausted
115
+ def empty?
116
+ @buffer == ""
117
+ end
118
+
119
+ def position
120
+ @orig.index( @buffer )
121
+ end
122
+
123
+ # @return the current line in the source
124
+ def current_line
125
+ lines = @orig.split
126
+ res = lines.grep @buffer[0..30]
127
+ res = res[-1] if res.kind_of? Array
128
+ lines.index( res ) if res
129
+ end
130
+ end
131
+
132
+ # A Source that wraps an IO. See the Source class for method
133
+ # documentation
134
+ class IOSource < Source
135
+ #attr_reader :block_size
136
+
137
+ # block_size has been deprecated
138
+ def initialize(arg, block_size=500, encoding=nil)
139
+ @er_source = @source = arg
140
+ @to_utf = false
141
+
142
+ # Determining the encoding is a deceptively difficult issue to resolve.
143
+ # First, we check the first two bytes for UTF-16. Then we
144
+ # assume that the encoding is at least ASCII enough for the '>', and
145
+ # we read until we get one of those. This gives us the XML declaration,
146
+ # if there is one. If there isn't one, the file MUST be UTF-8, as per
147
+ # the XML spec. If there is one, we can determine the encoding from
148
+ # it.
149
+ @buffer = ""
150
+ str = @source.read( 2 ) || ''
151
+ if encoding
152
+ self.encoding = encoding
153
+ elsif str[0,2] == "\xfe\xff"
154
+ @line_break = "\000>"
155
+ elsif str[0,2] == "\xff\xfe"
156
+ @line_break = ">\000"
157
+ elsif str[0,2] == "\xef\xbb"
158
+ str += @source.read(1)
159
+ str = '' if (str[2,1] == "\xBF")
160
+ @line_break = ">"
161
+ else
162
+ @line_break = ">"
163
+ end
164
+ super( @source.eof? ? str : str+@source.readline( @line_break ) )
165
+ end
166
+
167
+ def scan(pattern, cons=false)
168
+ rv = super
169
+ # You'll notice that this next section is very similar to the same
170
+ # section in match(), but just a liiittle different. This is
171
+ # because it is a touch faster to do it this way with scan()
172
+ # than the way match() does it; enough faster to warrent duplicating
173
+ # some code
174
+ if rv.size == 0
175
+ until @buffer =~ pattern or @source.nil?
176
+ begin
177
+ # READLINE OPT
178
+ #str = @source.read(@block_size)
179
+ str = @source.readline(@line_break)
180
+ str = decode(str) if @to_utf and str
181
+ @buffer << str
182
+ rescue Iconv::IllegalSequence
183
+ raise
184
+ rescue
185
+ @source = nil
186
+ end
187
+ end
188
+ rv = super
189
+ end
190
+ rv.taint
191
+ rv
192
+ end
193
+
194
+ def read
195
+ begin
196
+ str = @source.readline(@line_break)
197
+ str = decode(str) if @to_utf and str
198
+ @buffer << str
199
+ if not @to_utf and @buffer.respond_to? :force_encoding
200
+ @buffer.force_encoding Encoding::UTF_8
201
+ end
202
+ rescue Exception, NameError
203
+ @source = nil
204
+ end
205
+ end
206
+
207
+ def consume( pattern )
208
+ match( pattern, true )
209
+ end
210
+
211
+ def match( pattern, cons=false )
212
+ rv = pattern.match(@buffer)
213
+ @buffer = $' if cons and rv
214
+ while !rv and @source
215
+ begin
216
+ str = @source.readline(@line_break)
217
+ str = decode(str) if @to_utf and str
218
+ @buffer << str
219
+ rv = pattern.match(@buffer)
220
+ @buffer = $' if cons and rv
221
+ rescue
222
+ @source = nil
223
+ end
224
+ end
225
+ rv.taint
226
+ rv
227
+ end
228
+
229
+ def empty?
230
+ super and ( @source.nil? || @source.eof? )
231
+ end
232
+
233
+ def position
234
+ @er_source.pos rescue 0
235
+ end
236
+
237
+ # @return the current line in the source
238
+ def current_line
239
+ begin
240
+ pos = @er_source.pos # The byte position in the source
241
+ lineno = @er_source.lineno # The XML < position in the source
242
+ @er_source.rewind
243
+ line = 0 # The \r\n position in the source
244
+ begin
245
+ while @er_source.pos < pos
246
+ @er_source.readline
247
+ line += 1
248
+ end
249
+ rescue
250
+ end
251
+ rescue IOError
252
+ pos = -1
253
+ line = -1
254
+ end
255
+ [pos, lineno, line]
256
+ end
257
+ end
258
+ end
@@ -0,0 +1,18 @@
1
+ module REXML
2
+ # Defines a number of tokens used for parsing XML. Not for general
3
+ # consumption.
4
+ module XMLTokens
5
+ NCNAME_STR= '[\w:][\-\w\d.]*'
6
+ NAME_STR= "(?:#{NCNAME_STR}:)?#{NCNAME_STR}"
7
+
8
+ NAMECHAR = '[\-\w\d\.:]'
9
+ NAME = "([\\w:]#{NAMECHAR}*)"
10
+ NMTOKEN = "(?:#{NAMECHAR})+"
11
+ NMTOKENS = "#{NMTOKEN}(\\s+#{NMTOKEN})*"
12
+ REFERENCE = "(?:&#{NAME};|&#\\d+;|&#x[0-9a-fA-F]+;)"
13
+
14
+ #REFERENCE = "(?:#{ENTITYREF}|#{CHARREF})"
15
+ #ENTITYREF = "&#{NAME};"
16
+ #CHARREF = "&#\\d+;|&#x[0-9a-fA-F]+;"
17
+ end
18
+ end
@@ -0,0 +1,77 @@
1
+ #require 'rexml/functions'
2
+ require 'rexml/xpath_parser'
3
+
4
+ module REXML
5
+ # Wrapper class. Use this class to access the XPath functions.
6
+ class XPath
7
+ #include Functions
8
+ EMPTY_HASH = {}
9
+
10
+ # Finds and returns the first node that matches the supplied xpath.
11
+ # element::
12
+ # The context element
13
+ # path::
14
+ # The xpath to search for. If not supplied or nil, returns the first
15
+ # node matching '*'.
16
+ # namespaces::
17
+ # If supplied, a Hash which defines a namespace mapping.
18
+ # variables::
19
+ # If supplied, a Hash which maps $variables in the query
20
+ # to values. This can be used to avoid XPath injection attacks
21
+ # or to automatically handle escaping string values.
22
+ #
23
+ # XPath.first( node )
24
+ # XPath.first( doc, "//b"} )
25
+ # XPath.first( node, "a/x:b", { "x"=>"http://doofus" } )
26
+ # XPath.first( node, '/book/publisher/text()=$publisher', {}, {"publisher"=>"O'Reilly"})
27
+ def XPath::first element, path=nil, namespaces=nil, variables={}
28
+ raise "The namespaces argument, if supplied, must be a hash object." unless namespaces.nil? or namespaces.kind_of?(Hash)
29
+ raise "The variables argument, if supplied, must be a hash object." unless variables.kind_of?(Hash)
30
+ parser = XPathParser.new
31
+ parser.namespaces = namespaces
32
+ parser.variables = variables
33
+ path = "*" unless path
34
+ element = [element] unless element.kind_of? Array
35
+ parser.parse(path, element).flatten[0]
36
+ end
37
+
38
+ # Iterates over nodes that match the given path, calling the supplied
39
+ # block with the match.
40
+ # element::
41
+ # The context element
42
+ # path::
43
+ # The xpath to search for. If not supplied or nil, defaults to '*'
44
+ # namespaces::
45
+ # If supplied, a Hash which defines a namespace mapping
46
+ # variables::
47
+ # If supplied, a Hash which maps $variables in the query
48
+ # to values. This can be used to avoid XPath injection attacks
49
+ # or to automatically handle escaping string values.
50
+ #
51
+ # XPath.each( node ) { |el| ... }
52
+ # XPath.each( node, '/*[@attr='v']' ) { |el| ... }
53
+ # XPath.each( node, 'ancestor::x' ) { |el| ... }
54
+ # XPath.each( node, '/book/publisher/text()=$publisher', {}, {"publisher"=>"O'Reilly"}) \
55
+ # {|el| ... }
56
+ def XPath::each element, path=nil, namespaces=nil, variables={}, &block
57
+ raise "The namespaces argument, if supplied, must be a hash object." unless namespaces.nil? or namespaces.kind_of?(Hash)
58
+ raise "The variables argument, if supplied, must be a hash object." unless variables.kind_of?(Hash)
59
+ parser = XPathParser.new
60
+ parser.namespaces = namespaces
61
+ parser.variables = variables
62
+ path = "*" unless path
63
+ element = [element] unless element.kind_of? Array
64
+ parser.parse(path, element).each( &block )
65
+ end
66
+
67
+ # Returns an array of nodes matching a given XPath.
68
+ def XPath::match element, path=nil, namespaces=nil, variables={}
69
+ parser = XPathParser.new
70
+ parser.namespaces = namespaces
71
+ parser.variables = variables
72
+ path = "*" unless path
73
+ element = [element] unless element.kind_of? Array
74
+ parser.parse(path,element)
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,806 @@
1
+ require 'rexml/namespace'
2
+ require 'rexml/xmltokens'
3
+ #require 'rexml/attribute'
4
+ #require 'rexml/syncenumerator'
5
+ require 'rexml/parsers/xpathparser'
6
+
7
+ class Object
8
+ def dclone
9
+ clone
10
+ end
11
+ end
12
+ class Symbol
13
+ def dclone ; self ; end
14
+ end
15
+ class Fixnum
16
+ def dclone ; self ; end
17
+ end
18
+ class Float
19
+ def dclone ; self ; end
20
+ end
21
+ class Array
22
+ def dclone
23
+ klone = self.clone
24
+ klone.clear
25
+ self.each{|v| klone << v.dclone}
26
+ klone
27
+ end
28
+ end
29
+
30
+ module REXML
31
+
32
+ module Functions
33
+ @@context = nil
34
+ @@namespace_context = {}
35
+ @@variables = {}
36
+ def Functions::namespace_context=(x) ; @@namespace_context=x ; end
37
+ def Functions::variables=(x) ; @@variables=x ; end
38
+ def Functions::namespace_context ; @@namespace_context ; end
39
+ def Functions::variables ; @@variables ; end
40
+
41
+ def Functions::context=(value); @@context = value; end
42
+
43
+ end
44
+
45
+ # You don't want to use this class. Really. Use XPath, which is a wrapper
46
+ # for this class. Believe me. You don't want to poke around in here.
47
+ # There is strange, dark magic at work in this code. Beware. Go back! Go
48
+ # back while you still can!
49
+ class XPathParser
50
+ include XMLTokens
51
+ LITERAL = /^'([^']*)'|^"([^"]*)"/u
52
+
53
+ def initialize( )
54
+ @parser = REXML::Parsers::XPathParser.new
55
+ @namespaces = nil
56
+ @variables = {}
57
+ end
58
+
59
+ def namespaces=( namespaces={} )
60
+ Functions::namespace_context = namespaces
61
+ @namespaces = namespaces
62
+ end
63
+
64
+ def variables=( vars={} )
65
+ Functions::variables = vars
66
+ @variables = vars
67
+ end
68
+
69
+ def parse path, nodeset
70
+ #puts "#"*40
71
+ path_stack = @parser.parse( path )
72
+ #puts "PARSE: #{path} => #{path_stack.inspect}"
73
+ #puts "PARSE: nodeset = #{nodeset.inspect}"
74
+ match( path_stack, nodeset )
75
+ end
76
+
77
+ def get_first path, nodeset
78
+ #puts "#"*40
79
+ path_stack = @parser.parse( path )
80
+ #puts "PARSE: #{path} => #{path_stack.inspect}"
81
+ #puts "PARSE: nodeset = #{nodeset.inspect}"
82
+ first( path_stack, nodeset )
83
+ end
84
+
85
+ def predicate path, nodeset
86
+ path_stack = @parser.parse( path )
87
+ expr( path_stack, nodeset )
88
+ end
89
+
90
+ def []=( variable_name, value )
91
+ @variables[ variable_name ] = value
92
+ end
93
+
94
+
95
+ # Performs a depth-first (document order) XPath search, and returns the
96
+ # first match. This is the fastest, lightest way to return a single result.
97
+ #
98
+ # FIXME: This method is incomplete!
99
+ def first( path_stack, node )
100
+ #puts "#{depth}) Entering match( #{path.inspect}, #{tree.inspect} )"
101
+ return nil if path.size == 0
102
+
103
+ case path[0]
104
+ when :document
105
+ # do nothing
106
+ return first( path[1..-1], node )
107
+ when :child
108
+ for c in node.children
109
+ #puts "#{depth}) CHILD checking #{name(c)}"
110
+ r = first( path[1..-1], c )
111
+ #puts "#{depth}) RETURNING #{r.inspect}" if r
112
+ return r if r
113
+ end
114
+ when :qname
115
+ name = path[2]
116
+ #puts "#{depth}) QNAME #{name(tree)} == #{name} (path => #{path.size})"
117
+ if node.name == name
118
+ #puts "#{depth}) RETURNING #{tree.inspect}" if path.size == 3
119
+ return node if path.size == 3
120
+ return first( path[3..-1], node )
121
+ else
122
+ return nil
123
+ end
124
+ when :descendant_or_self
125
+ r = first( path[1..-1], node )
126
+ return r if r
127
+ for c in node.children
128
+ r = first( path, c )
129
+ return r if r
130
+ end
131
+ when :node
132
+ return first( path[1..-1], node )
133
+ when :any
134
+ return first( path[1..-1], node )
135
+ end
136
+ return nil
137
+ end
138
+
139
+
140
+ def match( path_stack, nodeset )
141
+ #puts "MATCH: path_stack = #{path_stack.inspect}"
142
+ #puts "MATCH: nodeset = #{nodeset.inspect}"
143
+ r = expr( path_stack, nodeset )
144
+ #puts "MAIN EXPR => #{r.inspect}"
145
+ r
146
+ end
147
+
148
+ private
149
+
150
+
151
+ # Returns a String namespace for a node, given a prefix
152
+ # The rules are:
153
+ #
154
+ # 1. Use the supplied namespace mapping first.
155
+ # 2. If no mapping was supplied, use the context node to look up the namespace
156
+ def get_namespace( node, prefix )
157
+ if @namespaces
158
+ return @namespaces[prefix] || ''
159
+ else
160
+ return node.namespace( prefix ) if node.node_type == :element
161
+ return ''
162
+ end
163
+ end
164
+
165
+
166
+ # Expr takes a stack of path elements and a set of nodes (either a Parent
167
+ # or an Array and returns an Array of matching nodes
168
+ ALL = [ :attribute, :element, :text, :processing_instruction, :comment ]
169
+ ELEMENTS = [ :element ]
170
+ def expr( path_stack, nodeset, context=nil )
171
+ #puts "#"*15
172
+ #puts "In expr with #{path_stack.inspect}"
173
+ #puts "Returning" if path_stack.length == 0 || nodeset.length == 0
174
+ node_types = ELEMENTS
175
+ return nodeset if path_stack.length == 0 || nodeset.length == 0
176
+ while path_stack.length > 0
177
+ #puts "#"*5
178
+ #puts "Path stack = #{path_stack.inspect}"
179
+ #puts "Nodeset is #{nodeset.inspect}"
180
+ if nodeset.length == 0
181
+ path_stack.clear
182
+ return []
183
+ end
184
+ case (op = path_stack.shift)
185
+ when :document
186
+ nodeset = [ nodeset[0].root_node ]
187
+ #puts ":document, nodeset = #{nodeset.inspect}"
188
+
189
+ when :qname
190
+ #puts "IN QNAME"
191
+ prefix = path_stack.shift
192
+ name = path_stack.shift
193
+ nodeset.delete_if do |node|
194
+ # FIXME: This DOUBLES the time XPath searches take
195
+ ns = get_namespace( node, prefix )
196
+ #puts "NS = #{ns.inspect}"
197
+ #puts "node.node_type == :element => #{node.node_type == :element}"
198
+ if node.node_type == :element
199
+ #puts "node.name == #{name} => #{node.name == name}"
200
+ if node.name == name
201
+ #puts "node.namespace == #{ns.inspect} => #{node.namespace == ns}"
202
+ end
203
+ end
204
+ !(node.node_type == :element and
205
+ node.name == name and
206
+ node.namespace == ns )
207
+ end
208
+ node_types = ELEMENTS
209
+
210
+ when :any
211
+ #puts "ANY 1: nodeset = #{nodeset.inspect}"
212
+ #puts "ANY 1: node_types = #{node_types.inspect}"
213
+ nodeset.delete_if { |node| !node_types.include?(node.node_type) }
214
+ #puts "ANY 2: nodeset = #{nodeset.inspect}"
215
+
216
+ when :self
217
+ # This space left intentionally blank
218
+
219
+ when :processing_instruction
220
+ target = path_stack.shift
221
+ nodeset.delete_if do |node|
222
+ (node.node_type != :processing_instruction) or
223
+ ( target!='' and ( node.target != target ) )
224
+ end
225
+
226
+ when :text
227
+ nodeset.delete_if { |node| node.node_type != :text }
228
+
229
+ when :comment
230
+ nodeset.delete_if { |node| node.node_type != :comment }
231
+
232
+ when :node
233
+ # This space left intentionally blank
234
+ node_types = ALL
235
+
236
+ when :child
237
+ new_nodeset = []
238
+ nt = nil
239
+ nodeset.each do |node|
240
+ nt = node.node_type
241
+ new_nodeset += node.children if nt == :element or nt == :document
242
+ end
243
+ nodeset = new_nodeset
244
+ node_types = ELEMENTS
245
+
246
+ when :literal
247
+ return path_stack.shift
248
+
249
+ when :attribute
250
+ new_nodeset = []
251
+ case path_stack.shift
252
+ when :qname
253
+ prefix = path_stack.shift
254
+ name = path_stack.shift
255
+ for element in nodeset
256
+ if element.node_type == :element
257
+ #puts "Element name = #{element.name}"
258
+ #puts "get_namespace( #{element.inspect}, #{prefix} ) = #{get_namespace(element, prefix)}"
259
+ attrib = element.attribute( name, get_namespace(element, prefix) )
260
+ #puts "attrib = #{attrib.inspect}"
261
+ new_nodeset << attrib if attrib
262
+ end
263
+ end
264
+ when :any
265
+ #puts "ANY"
266
+ for element in nodeset
267
+ if element.node_type == :element
268
+ new_nodeset += element.attributes.to_a
269
+ end
270
+ end
271
+ end
272
+ nodeset = new_nodeset
273
+
274
+ when :parent
275
+ #puts "PARENT 1: nodeset = #{nodeset}"
276
+ nodeset = nodeset.collect{|n| n.parent}.compact
277
+ #nodeset = expr(path_stack.dclone, nodeset.collect{|n| n.parent}.compact)
278
+ #puts "PARENT 2: nodeset = #{nodeset.inspect}"
279
+ node_types = ELEMENTS
280
+
281
+ when :ancestor
282
+ new_nodeset = []
283
+ nodeset.each do |node|
284
+ while node.parent
285
+ node = node.parent
286
+ new_nodeset << node unless new_nodeset.include? node
287
+ end
288
+ end
289
+ nodeset = new_nodeset
290
+ node_types = ELEMENTS
291
+
292
+ when :ancestor_or_self
293
+ new_nodeset = []
294
+ nodeset.each do |node|
295
+ if node.node_type == :element
296
+ new_nodeset << node
297
+ while ( node.parent )
298
+ node = node.parent
299
+ new_nodeset << node unless new_nodeset.include? node
300
+ end
301
+ end
302
+ end
303
+ nodeset = new_nodeset
304
+ node_types = ELEMENTS
305
+
306
+ when :predicate
307
+ new_nodeset = []
308
+ subcontext = { :size => nodeset.size }
309
+ pred = path_stack.shift
310
+ nodeset.each_with_index { |node, index|
311
+ subcontext[ :node ] = node
312
+ #puts "PREDICATE SETTING CONTEXT INDEX TO #{index+1}"
313
+ subcontext[ :index ] = index+1
314
+ pc = pred.dclone
315
+ #puts "#{node.hash}) Recursing with #{pred.inspect} and [#{node.inspect}]"
316
+ result = expr( pc, [node], subcontext )
317
+ result = result[0] if result.kind_of?( Array ) and result.length == 1
318
+ #puts "#{node.hash}) Result = #{result.inspect} (#{result.class.name})"
319
+ if result.kind_of? Numeric
320
+ #puts "Adding node #{node.inspect}" if result == (index+1)
321
+ new_nodeset << node if result == (index+1)
322
+ elsif result.instance_of? Array
323
+ if result.size > 0 and result.inject(false) {|k,s| s or k}
324
+ #puts "Adding node #{node.inspect}" if result.size > 0
325
+ new_nodeset << node if result.size > 0
326
+ end
327
+ else
328
+ #puts "Adding node #{node.inspect}" if result
329
+ new_nodeset << node if result
330
+ end
331
+ }
332
+ #puts "New nodeset = #{new_nodeset.inspect}"
333
+ #puts "Path_stack = #{path_stack.inspect}"
334
+ nodeset = new_nodeset
335
+ =begin
336
+ predicate = path_stack.shift
337
+ ns = nodeset.clone
338
+ result = expr( predicate, ns )
339
+ #puts "Result = #{result.inspect} (#{result.class.name})"
340
+ #puts "nodeset = #{nodeset.inspect}"
341
+ if result.kind_of? Array
342
+ nodeset = result.zip(ns).collect{|m,n| n if m}.compact
343
+ else
344
+ nodeset = result ? nodeset : []
345
+ end
346
+ #puts "Outgoing NS = #{nodeset.inspect}"
347
+ =end
348
+
349
+ when :descendant_or_self
350
+ rv = descendant_or_self( path_stack, nodeset )
351
+ path_stack.clear
352
+ nodeset = rv
353
+ node_types = ELEMENTS
354
+
355
+ when :descendant
356
+ results = []
357
+ nt = nil
358
+ nodeset.each do |node|
359
+ nt = node.node_type
360
+ results += expr( path_stack.dclone.unshift( :descendant_or_self ),
361
+ node.children ) if nt == :element or nt == :document
362
+ end
363
+ nodeset = results
364
+ node_types = ELEMENTS
365
+
366
+ when :following_sibling
367
+ #puts "FOLLOWING_SIBLING 1: nodeset = #{nodeset}"
368
+ results = []
369
+ nodeset.each do |node|
370
+ next if node.parent.nil?
371
+ all_siblings = node.parent.children
372
+ current_index = all_siblings.index( node )
373
+ following_siblings = all_siblings[ current_index+1 .. -1 ]
374
+ results += expr( path_stack.dclone, following_siblings )
375
+ end
376
+ #puts "FOLLOWING_SIBLING 2: nodeset = #{nodeset}"
377
+ nodeset = results
378
+
379
+ when :preceding_sibling
380
+ results = []
381
+ nodeset.each do |node|
382
+ next if node.parent.nil?
383
+ all_siblings = node.parent.children
384
+ current_index = all_siblings.index( node )
385
+ preceding_siblings = all_siblings[ 0, current_index ].reverse
386
+ results += preceding_siblings
387
+ end
388
+ nodeset = results
389
+ node_types = ELEMENTS
390
+
391
+ when :preceding
392
+ new_nodeset = []
393
+ nodeset.each do |node|
394
+ new_nodeset += preceding( node )
395
+ end
396
+ #puts "NEW NODESET => #{new_nodeset.inspect}"
397
+ nodeset = new_nodeset
398
+ node_types = ELEMENTS
399
+
400
+ when :following
401
+ new_nodeset = []
402
+ nodeset.each do |node|
403
+ new_nodeset += following( node )
404
+ end
405
+ nodeset = new_nodeset
406
+ node_types = ELEMENTS
407
+
408
+ when :namespace
409
+ #puts "In :namespace"
410
+ new_nodeset = []
411
+ prefix = path_stack.shift
412
+ nodeset.each do |node|
413
+ if (node.node_type == :element or node.node_type == :attribute)
414
+ if @namespaces
415
+ namespaces = @namespaces
416
+ elsif (node.node_type == :element)
417
+ namespaces = node.namespaces
418
+ else
419
+ namespaces = node.element.namesapces
420
+ end
421
+ #puts "Namespaces = #{namespaces.inspect}"
422
+ #puts "Prefix = #{prefix.inspect}"
423
+ #puts "Node.namespace = #{node.namespace}"
424
+ if (node.namespace == namespaces[prefix])
425
+ new_nodeset << node
426
+ end
427
+ end
428
+ end
429
+ nodeset = new_nodeset
430
+
431
+ when :variable
432
+ var_name = path_stack.shift
433
+ return @variables[ var_name ]
434
+
435
+ # :and, :or, :eq, :neq, :lt, :lteq, :gt, :gteq
436
+ # TODO: Special case for :or and :and -- not evaluate the right
437
+ # operand if the left alone determines result (i.e. is true for
438
+ # :or and false for :and).
439
+ when :eq, :neq, :lt, :lteq, :gt, :gteq, :or
440
+ left = expr( path_stack.shift, nodeset.dup, context )
441
+ #puts "LEFT => #{left.inspect} (#{left.class.name})"
442
+ right = expr( path_stack.shift, nodeset.dup, context )
443
+ #puts "RIGHT => #{right.inspect} (#{right.class.name})"
444
+ res = equality_relational_compare( left, op, right )
445
+ #puts "RES => #{res.inspect}"
446
+ return res
447
+
448
+ when :and
449
+ left = expr( path_stack.shift, nodeset.dup, context )
450
+ #puts "LEFT => #{left.inspect} (#{left.class.name})"
451
+ if left == false || left.nil? || !left.inject(false) {|a,b| a | b}
452
+ return []
453
+ end
454
+ right = expr( path_stack.shift, nodeset.dup, context )
455
+ #puts "RIGHT => #{right.inspect} (#{right.class.name})"
456
+ res = equality_relational_compare( left, op, right )
457
+ #puts "RES => #{res.inspect}"
458
+ return res
459
+
460
+ when :div
461
+ left = Functions::number(expr(path_stack.shift, nodeset, context)).to_f
462
+ right = Functions::number(expr(path_stack.shift, nodeset, context)).to_f
463
+ return (left / right)
464
+
465
+ when :mod
466
+ left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
467
+ right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
468
+ return (left % right)
469
+
470
+ when :mult
471
+ left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
472
+ right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
473
+ return (left * right)
474
+
475
+ when :plus
476
+ left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
477
+ right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
478
+ return (left + right)
479
+
480
+ when :minus
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 :union
486
+ left = expr( path_stack.shift, nodeset, context )
487
+ right = expr( path_stack.shift, nodeset, context )
488
+ return (left | right)
489
+
490
+ when :neg
491
+ res = expr( path_stack, nodeset, context )
492
+ return -(res.to_f)
493
+
494
+ when :not
495
+ when :function
496
+ func_name = path_stack.shift.tr('-','_')
497
+ arguments = path_stack.shift
498
+ #puts "FUNCTION 0: #{func_name}(#{arguments.collect{|a|a.inspect}.join(', ')})"
499
+ subcontext = context ? nil : { :size => nodeset.size }
500
+
501
+ res = []
502
+ cont = context
503
+ nodeset.each_with_index { |n, i|
504
+ if subcontext
505
+ subcontext[:node] = n
506
+ subcontext[:index] = i
507
+ cont = subcontext
508
+ end
509
+ arg_clone = arguments.dclone
510
+ args = arg_clone.collect { |arg|
511
+ #puts "FUNCTION 1: Calling expr( #{arg.inspect}, [#{n.inspect}] )"
512
+ expr( arg, [n], cont )
513
+ }
514
+ #puts "FUNCTION 2: #{func_name}(#{args.collect{|a|a.inspect}.join(', ')})"
515
+ Functions.context = cont
516
+ res << Functions.send( func_name, *args )
517
+ #puts "FUNCTION 3: #{res[-1].inspect}"
518
+ }
519
+ return res
520
+
521
+ end
522
+ end # while
523
+ #puts "EXPR returning #{nodeset.inspect}"
524
+ return nodeset
525
+ end
526
+
527
+
528
+ ##########################################################
529
+ # FIXME
530
+ # The next two methods are BAD MOJO!
531
+ # This is my achilles heel. If anybody thinks of a better
532
+ # way of doing this, be my guest. This really sucks, but
533
+ # it is a wonder it works at all.
534
+ # ########################################################
535
+
536
+ def descendant_or_self( path_stack, nodeset )
537
+ rs = []
538
+ #puts "#"*80
539
+ #puts "PATH_STACK = #{path_stack.inspect}"
540
+ #puts "NODESET = #{nodeset.collect{|n|n.inspect}.inspect}"
541
+ d_o_s( path_stack, nodeset, rs )
542
+ #puts "RS = #{rs.collect{|n|n.inspect}.inspect}"
543
+ document_order(rs.flatten.compact)
544
+ #rs.flatten.compact
545
+ end
546
+
547
+ def d_o_s( p, ns, r )
548
+ #puts "IN DOS with #{ns.inspect}; ALREADY HAVE #{r.inspect}"
549
+ nt = nil
550
+ ns.each_index do |i|
551
+ n = ns[i]
552
+ #puts "P => #{p.inspect}"
553
+ x = expr( p.dclone, [ n ] )
554
+ nt = n.node_type
555
+ d_o_s( p, n.children, x ) if nt == :element or nt == :document and n.children.size > 0
556
+ r.concat(x) if x.size > 0
557
+ end
558
+ end
559
+
560
+
561
+ # Reorders an array of nodes so that they are in document order
562
+ # It tries to do this efficiently.
563
+ #
564
+ # FIXME: I need to get rid of this, but the issue is that most of the XPath
565
+ # interpreter functions as a filter, which means that we lose context going
566
+ # in and out of function calls. If I knew what the index of the nodes was,
567
+ # I wouldn't have to do this. Maybe add a document IDX for each node?
568
+ # Problems with mutable documents. Or, rewrite everything.
569
+ def document_order( array_of_nodes )
570
+ new_arry = []
571
+ array_of_nodes.each { |node|
572
+ node_idx = []
573
+ np = node.node_type == :attribute ? node.element : node
574
+ while np.parent and np.parent.node_type == :element
575
+ node_idx << np.parent.index( np )
576
+ np = np.parent
577
+ end
578
+ new_arry << [ node_idx.reverse, node ]
579
+ }
580
+ #puts "new_arry = #{new_arry.inspect}"
581
+ new_arry.sort{ |s1, s2| s1[0] <=> s2[0] }.collect{ |s| s[1] }
582
+ end
583
+
584
+
585
+ def recurse( nodeset, &block )
586
+ for node in nodeset
587
+ yield node
588
+ recurse( node, &block ) if node.node_type == :element
589
+ end
590
+ end
591
+
592
+
593
+
594
+ # Builds a nodeset of all of the preceding nodes of the supplied node,
595
+ # in reverse document order
596
+ # preceding:: includes every element in the document that precedes this node,
597
+ # except for ancestors
598
+ def preceding( node )
599
+ #puts "IN PRECEDING"
600
+ ancestors = []
601
+ p = node.parent
602
+ while p
603
+ ancestors << p
604
+ p = p.parent
605
+ end
606
+
607
+ acc = []
608
+ p = preceding_node_of( node )
609
+ #puts "P = #{p.inspect}"
610
+ while p
611
+ if ancestors.include? p
612
+ ancestors.delete(p)
613
+ else
614
+ acc << p
615
+ end
616
+ p = preceding_node_of( p )
617
+ #puts "P = #{p.inspect}"
618
+ end
619
+ acc
620
+ end
621
+
622
+ def preceding_node_of( node )
623
+ #puts "NODE: #{node.inspect}"
624
+ #puts "PREVIOUS NODE: #{node.previous_sibling_node.inspect}"
625
+ #puts "PARENT NODE: #{node.parent}"
626
+ psn = node.previous_sibling_node
627
+ if psn.nil?
628
+ if node.parent.nil? or node.parent.class == Document
629
+ return nil
630
+ end
631
+ return node.parent
632
+ #psn = preceding_node_of( node.parent )
633
+ end
634
+ while psn and psn.kind_of?( Element ) and psn.children.size > 0
635
+ psn = psn.children[-1]
636
+ end
637
+ psn
638
+ end
639
+
640
+ def following( node )
641
+ #puts "IN PRECEDING"
642
+ acc = []
643
+ p = next_sibling_node( node )
644
+ #puts "P = #{p.inspect}"
645
+ while p
646
+ acc << p
647
+ p = following_node_of( p )
648
+ #puts "P = #{p.inspect}"
649
+ end
650
+ acc
651
+ end
652
+
653
+ def following_node_of( node )
654
+ #puts "NODE: #{node.inspect}"
655
+ #puts "PREVIOUS NODE: #{node.previous_sibling_node.inspect}"
656
+ #puts "PARENT NODE: #{node.parent}"
657
+ if node.kind_of?( Element ) and node.children.size > 0
658
+ return node.children[0]
659
+ end
660
+ return next_sibling_node(node)
661
+ end
662
+
663
+ def next_sibling_node(node)
664
+ psn = node.next_sibling_node
665
+ while psn.nil?
666
+ if node.parent.nil? or node.parent.class == Document
667
+ return nil
668
+ end
669
+ node = node.parent
670
+ psn = node.next_sibling_node
671
+ #puts "psn = #{psn.inspect}"
672
+ end
673
+ return psn
674
+ end
675
+
676
+ def norm b
677
+ case b
678
+ when true, false
679
+ return b
680
+ when 'true', 'false'
681
+ return Functions::boolean( b )
682
+ when /^\d+(\.\d+)?$/
683
+ return Functions::number( b )
684
+ else
685
+ return Functions::string( b )
686
+ end
687
+ end
688
+
689
+ def equality_relational_compare( set1, op, set2 )
690
+ #puts "EQ_REL_COMP(#{set1.inspect} #{op.inspect} #{set2.inspect})"
691
+ if set1.kind_of?( Array ) and set2.kind_of?( Array )
692
+ #puts "#{set1.size} & #{set2.size}"
693
+ if set1.size == 1 and set2.size == 1
694
+ set1 = set1[0]
695
+ set2 = set2[0]
696
+ elsif set1.size == 0 or set2.size == 0
697
+ nd = set1.size==0 ? set2 : set1
698
+ rv = nd.collect { |il| compare( il, op, nil ) }
699
+ #puts "RV = #{rv.inspect}"
700
+ return rv
701
+ else
702
+ res = []
703
+ enum = SyncEnumerator.new( set1, set2 ).each { |i1, i2|
704
+ #puts "i1 = #{i1.inspect} (#{i1.class.name})"
705
+ #puts "i2 = #{i2.inspect} (#{i2.class.name})"
706
+ i1 = norm( i1 )
707
+ i2 = norm( i2 )
708
+ res << compare( i1, op, i2 )
709
+ }
710
+ return res
711
+ end
712
+ end
713
+ #puts "EQ_REL_COMP: #{set1.inspect} (#{set1.class.name}), #{op}, #{set2.inspect} (#{set2.class.name})"
714
+ #puts "COMPARING VALUES"
715
+ # If one is nodeset and other is number, compare number to each item
716
+ # in nodeset s.t. number op number(string(item))
717
+ # If one is nodeset and other is string, compare string to each item
718
+ # in nodeset s.t. string op string(item)
719
+ # If one is nodeset and other is boolean, compare boolean to each item
720
+ # in nodeset s.t. boolean op boolean(item)
721
+ if set1.kind_of?( Array ) or set2.kind_of?( Array )
722
+ #puts "ISA ARRAY"
723
+ if set1.kind_of? Array
724
+ a = set1
725
+ b = set2
726
+ else
727
+ a = set2
728
+ b = set1
729
+ end
730
+
731
+ case b
732
+ when true, false
733
+ return a.collect {|v| compare( Functions::boolean(v), op, b ) }
734
+ when Numeric
735
+ return a.collect {|v| compare( Functions::number(v), op, b )}
736
+ when /^\d+(\.\d+)?$/
737
+ b = Functions::number( b )
738
+ #puts "B = #{b.inspect}"
739
+ return a.collect {|v| compare( Functions::number(v), op, b )}
740
+ else
741
+ #puts "Functions::string( #{b}(#{b.class.name}) ) = #{Functions::string(b)}"
742
+ b = Functions::string( b )
743
+ return a.collect { |v| compare( Functions::string(v), op, b ) }
744
+ end
745
+ else
746
+ # If neither is nodeset,
747
+ # If op is = or !=
748
+ # If either boolean, convert to boolean
749
+ # If either number, convert to number
750
+ # Else, convert to string
751
+ # Else
752
+ # Convert both to numbers and compare
753
+ s1 = set1.to_s
754
+ s2 = set2.to_s
755
+ #puts "EQ_REL_COMP: #{set1}=>#{s1}, #{set2}=>#{s2}"
756
+ if s1 == 'true' or s1 == 'false' or s2 == 'true' or s2 == 'false'
757
+ #puts "Functions::boolean(#{set1})=>#{Functions::boolean(set1)}"
758
+ #puts "Functions::boolean(#{set2})=>#{Functions::boolean(set2)}"
759
+ set1 = Functions::boolean( set1 )
760
+ set2 = Functions::boolean( set2 )
761
+ else
762
+ if op == :eq or op == :neq
763
+ if s1 =~ /^\d+(\.\d+)?$/ or s2 =~ /^\d+(\.\d+)?$/
764
+ set1 = Functions::number( s1 )
765
+ set2 = Functions::number( s2 )
766
+ else
767
+ set1 = Functions::string( set1 )
768
+ set2 = Functions::string( set2 )
769
+ end
770
+ else
771
+ set1 = Functions::number( set1 )
772
+ set2 = Functions::number( set2 )
773
+ end
774
+ end
775
+ #puts "EQ_REL_COMP: #{set1} #{op} #{set2}"
776
+ #puts ">>> #{compare( set1, op, set2 )}"
777
+ return compare( set1, op, set2 )
778
+ end
779
+ return false
780
+ end
781
+
782
+ def compare a, op, b
783
+ #puts "COMPARE #{a.inspect}(#{a.class.name}) #{op} #{b.inspect}(#{b.class.name})"
784
+ case op
785
+ when :eq
786
+ a == b
787
+ when :neq
788
+ a != b
789
+ when :lt
790
+ a < b
791
+ when :lteq
792
+ a <= b
793
+ when :gt
794
+ a > b
795
+ when :gteq
796
+ a >= b
797
+ when :and
798
+ a and b
799
+ when :or
800
+ a or b
801
+ else
802
+ false
803
+ end
804
+ end
805
+ end
806
+ end