xmpp4r 0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. data/COPYING +340 -0
  2. data/ChangeLog +28 -0
  3. data/LICENSE +59 -0
  4. data/README +20 -0
  5. data/Rakefile +103 -0
  6. data/UPDATING +40 -0
  7. data/data/doc/xmpp4r/examples/advanced/adventure/README +57 -0
  8. data/data/doc/xmpp4r/examples/advanced/adventure/adventure.rb +23 -0
  9. data/data/doc/xmpp4r/examples/advanced/adventure/adventuremuc.rb +136 -0
  10. data/data/doc/xmpp4r/examples/advanced/adventure/cube.xml +15 -0
  11. data/data/doc/xmpp4r/examples/advanced/adventure/tower.xml +69 -0
  12. data/data/doc/xmpp4r/examples/advanced/adventure/world.rb +425 -0
  13. data/data/doc/xmpp4r/examples/advanced/fileserve.conf +11 -0
  14. data/data/doc/xmpp4r/examples/advanced/fileserve.rb +344 -0
  15. data/data/doc/xmpp4r/examples/advanced/getonline.rb +56 -0
  16. data/data/doc/xmpp4r/examples/advanced/gtkmucclient.rb +315 -0
  17. data/data/doc/xmpp4r/examples/advanced/migrate.rb +89 -0
  18. data/data/doc/xmpp4r/examples/advanced/minimuc.rb +266 -0
  19. data/data/doc/xmpp4r/examples/advanced/recvfile.rb +83 -0
  20. data/data/doc/xmpp4r/examples/advanced/rosterdiscovery.rb +130 -0
  21. data/data/doc/xmpp4r/examples/advanced/sendfile.conf +10 -0
  22. data/data/doc/xmpp4r/examples/advanced/sendfile.rb +72 -0
  23. data/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr.rb +51 -0
  24. data/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr_jabber.rb +43 -0
  25. data/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr_test.rb +10 -0
  26. data/data/doc/xmpp4r/examples/advanced/versionpoll.rb +90 -0
  27. data/data/doc/xmpp4r/examples/advanced/xmpping.rb +134 -0
  28. data/data/doc/xmpp4r/examples/advanced/xmppingrc.sample +9 -0
  29. data/data/doc/xmpp4r/examples/basic/change_password.rb +41 -0
  30. data/data/doc/xmpp4r/examples/basic/client.rb +68 -0
  31. data/data/doc/xmpp4r/examples/basic/component.rb +11 -0
  32. data/data/doc/xmpp4r/examples/basic/echo_nonthreaded.rb +32 -0
  33. data/data/doc/xmpp4r/examples/basic/echo_threaded.rb +32 -0
  34. data/data/doc/xmpp4r/examples/basic/jabbersend.rb +41 -0
  35. data/data/doc/xmpp4r/examples/basic/mass_sender.rb +67 -0
  36. data/data/doc/xmpp4r/examples/basic/mucinfo.rb +39 -0
  37. data/data/doc/xmpp4r/examples/basic/mucsimplebot.rb +83 -0
  38. data/data/doc/xmpp4r/examples/basic/register.rb +25 -0
  39. data/data/doc/xmpp4r/examples/basic/remove_registration.rb +18 -0
  40. data/data/doc/xmpp4r/examples/basic/roster.rb +42 -0
  41. data/data/doc/xmpp4r/examples/basic/rosterprint.rb +50 -0
  42. data/data/doc/xmpp4r/examples/basic/rosterrename.rb +34 -0
  43. data/data/doc/xmpp4r/examples/basic/rosterwatch.rb +172 -0
  44. data/data/doc/xmpp4r/examples/basic/send_vcard.rb +68 -0
  45. data/data/doc/xmpp4r/examples/basic/versionbot.rb +75 -0
  46. data/data/doc/xmpp4r/examples/buggy/jabber2jabber/jabber2jabber.rb +18 -0
  47. data/data/doc/xmpp4r/examples/buggy/miniedgarr_cgi.rb +192 -0
  48. data/data/doc/xmpp4r/examples/buggy/miniedgarr_watch.rb +82 -0
  49. data/lib/callbacks.rb +122 -0
  50. data/lib/xmpp4r/authenticationfailure.rb +13 -0
  51. data/lib/xmpp4r/bytestreams/helper/filetransfer.rb +315 -0
  52. data/lib/xmpp4r/bytestreams/helper/ibb/base.rb +256 -0
  53. data/lib/xmpp4r/bytestreams/helper/ibb/initiator.rb +30 -0
  54. data/lib/xmpp4r/bytestreams/helper/ibb/target.rb +46 -0
  55. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/base.rb +151 -0
  56. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/initiator.rb +85 -0
  57. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/server.rb +178 -0
  58. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/socks5.rb +56 -0
  59. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/target.rb +61 -0
  60. data/lib/xmpp4r/bytestreams/iq/bytestreams.rb +177 -0
  61. data/lib/xmpp4r/bytestreams/iq/si.rb +221 -0
  62. data/lib/xmpp4r/bytestreams.rb +11 -0
  63. data/lib/xmpp4r/client.rb +249 -0
  64. data/lib/xmpp4r/component.rb +103 -0
  65. data/lib/xmpp4r/connection.rb +166 -0
  66. data/lib/xmpp4r/dataforms/x/data.rb +248 -0
  67. data/lib/xmpp4r/dataforms.rb +1 -0
  68. data/lib/xmpp4r/debuglog.rb +34 -0
  69. data/lib/xmpp4r/delay/x/delay.rb +100 -0
  70. data/lib/xmpp4r/delay.rb +1 -0
  71. data/lib/xmpp4r/discovery/iq/discoinfo.rb +225 -0
  72. data/lib/xmpp4r/discovery/iq/discoitems.rb +162 -0
  73. data/lib/xmpp4r/discovery.rb +2 -0
  74. data/lib/xmpp4r/error.rb +230 -0
  75. data/lib/xmpp4r/errorexception.rb +32 -0
  76. data/lib/xmpp4r/feature_negotiation/iq/feature.rb +42 -0
  77. data/lib/xmpp4r/feature_negotiation.rb +1 -0
  78. data/lib/xmpp4r/idgenerator.rb +37 -0
  79. data/lib/xmpp4r/iq.rb +229 -0
  80. data/lib/xmpp4r/jid.rb +167 -0
  81. data/lib/xmpp4r/message.rb +171 -0
  82. data/lib/xmpp4r/muc/helper/mucbrowser.rb +107 -0
  83. data/lib/xmpp4r/muc/helper/mucclient.rb +382 -0
  84. data/lib/xmpp4r/muc/helper/simplemucclient.rb +222 -0
  85. data/lib/xmpp4r/muc/x/muc.rb +98 -0
  86. data/lib/xmpp4r/muc/x/mucuserinvite.rb +58 -0
  87. data/lib/xmpp4r/muc/x/mucuseritem.rb +148 -0
  88. data/lib/xmpp4r/muc.rb +6 -0
  89. data/lib/xmpp4r/presence.rb +255 -0
  90. data/lib/xmpp4r/query.rb +43 -0
  91. data/lib/xmpp4r/rexmladdons.rb +826 -0
  92. data/lib/xmpp4r/roster/helper/roster.rb +514 -0
  93. data/lib/xmpp4r/roster/iq/roster.rb +244 -0
  94. data/lib/xmpp4r/roster/x/roster.rb +155 -0
  95. data/lib/xmpp4r/roster.rb +4 -0
  96. data/lib/xmpp4r/sasl.rb +167 -0
  97. data/lib/xmpp4r/stream.rb +543 -0
  98. data/lib/xmpp4r/streamparser.rb +77 -0
  99. data/lib/xmpp4r/vcard/helper/vcard.rb +86 -0
  100. data/lib/xmpp4r/vcard/iq/vcard.rb +102 -0
  101. data/lib/xmpp4r/vcard.rb +3 -0
  102. data/lib/xmpp4r/version/helper/responder.rb +71 -0
  103. data/lib/xmpp4r/version/helper/simpleresponder.rb +44 -0
  104. data/lib/xmpp4r/version/iq/version.rb +118 -0
  105. data/lib/xmpp4r/version.rb +3 -0
  106. data/lib/xmpp4r/x.rb +43 -0
  107. data/lib/xmpp4r/xmlstanza.rb +174 -0
  108. data/lib/xmpp4r/xmpp4r.rb +16 -0
  109. data/lib/xmpp4r.rb +122 -0
  110. data/setup.rb +1360 -0
  111. data/test/bytestreams/tc_ibb.rb +186 -0
  112. data/test/bytestreams/tc_socks5bytestreams.rb +57 -0
  113. data/test/delay/tc_xdelay.rb +51 -0
  114. data/test/lib/clienttester.rb +110 -0
  115. data/test/muc/tc_muc_mucclient.rb +569 -0
  116. data/test/muc/tc_muc_simplemucclient.rb +72 -0
  117. data/test/roster/.tc_helper.rb.swp +0 -0
  118. data/test/roster/tc_helper.rb +389 -0
  119. data/test/roster/tc_iqqueryroster.rb +140 -0
  120. data/test/roster/tc_xroster.rb +70 -0
  121. data/test/tc_callbacks.rb +128 -0
  122. data/test/tc_class_names.rb +129 -0
  123. data/test/tc_client.rb +30 -0
  124. data/test/tc_error.rb +103 -0
  125. data/test/tc_idgenerator.rb +30 -0
  126. data/test/tc_iq.rb +109 -0
  127. data/test/tc_iqquery.rb +31 -0
  128. data/test/tc_jid.rb +202 -0
  129. data/test/tc_message.rb +114 -0
  130. data/test/tc_presence.rb +148 -0
  131. data/test/tc_stream.rb +182 -0
  132. data/test/tc_streamError.rb +87 -0
  133. data/test/tc_streamSend.rb +59 -0
  134. data/test/tc_streamThreaded.rb +168 -0
  135. data/test/tc_xmlstanza.rb +76 -0
  136. data/test/ts_xmpp4r.rb +34 -0
  137. data/test/vcard/tc_iqvcard.rb +52 -0
  138. data/test/version/tc_helper.rb +46 -0
  139. data/test/version/tc_iqqueryversion.rb +96 -0
  140. data/tools/doctoweb.bash +30 -0
  141. data/tools/gen_requires.bash +10 -0
  142. metadata +232 -0
@@ -0,0 +1,826 @@
1
+ # =XMPP4R - XMPP Library for Ruby
2
+ # License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option.
3
+ # Website::http://home.gna.org/xmpp4r/
4
+
5
+ require 'rexml/document'
6
+ require 'rexml/parsers/xpathparser'
7
+ require 'rexml/source'
8
+
9
+ # Turn $VERBOSE off to suppress warnings about redefinition
10
+ oldverbose = $VERBOSE
11
+ $VERBOSE = false
12
+
13
+ # REXML module. This file only adds a few methods to the REXML module, to
14
+ # ease the coding.
15
+ module REXML
16
+ # this class adds a few helper methods to REXML::Element
17
+ class Element
18
+ ##
19
+ # Replaces or add a child element of name <tt>e</tt> with text <tt>t</tt>.
20
+ def replace_element_text(e, t)
21
+ el = first_element(e)
22
+ if el.nil?
23
+ el = REXML::Element::new(e)
24
+ add_element(el)
25
+ end
26
+ if t
27
+ el.text = t
28
+ end
29
+ self
30
+ end
31
+
32
+ ##
33
+ # Returns first element of name <tt>e</tt>
34
+ def first_element(e)
35
+ each_element(e) { |el| return el }
36
+ return nil
37
+ end
38
+
39
+ ##
40
+ # Returns text of first element of name <tt>e</tt>
41
+ def first_element_text(e)
42
+ el = first_element(e)
43
+ if el
44
+ return el.text
45
+ else
46
+ return nil
47
+ end
48
+ end
49
+
50
+ # This method does exactly the same thing as add(), but it can be
51
+ # overriden by subclasses to provide on-the-fly object creations.
52
+ # For example, if you import a REXML::Element of name 'plop', and you
53
+ # have a Plop class that subclasses REXML::Element, with typed_add you
54
+ # can get your REXML::Element to be "magically" converted to Plop.
55
+ def typed_add(e)
56
+ add(e)
57
+ end
58
+
59
+ ##
60
+ # import this element's children and attributes
61
+ def import(xmlelement)
62
+ if @name and @name != xmlelement.name
63
+ raise "Trying to import an #{xmlelement.name} to a #{@name} !"
64
+ end
65
+ add_attributes(xmlelement.attributes.clone)
66
+ @context = xmlelement.context
67
+ xmlelement.each do |e|
68
+ if e.kind_of? REXML::Element
69
+ typed_add(e.deep_clone)
70
+ else # text element, probably.
71
+ add(e.clone)
72
+ end
73
+ end
74
+ self
75
+ end
76
+
77
+ def self.import(xmlelement)
78
+ self.new.import(xmlelement)
79
+ end
80
+
81
+ ##
82
+ # Deletes one or more children elements,
83
+ # not just one like REXML::Element#delete_element
84
+ def delete_elements(element)
85
+ while(delete_element(element)) do end
86
+ end
87
+
88
+ end
89
+
90
+ # very dirty fix for the :progress problem in REXML from Ruby 1.8.3
91
+ # http://www.germane-software.com/projects/rexml/ticket/34
92
+ class IOSource
93
+ def position
94
+ 0
95
+ end
96
+
97
+ def current_line
98
+ [0, 0, ""]
99
+ end
100
+ end
101
+
102
+ ############################################################################
103
+ # The XPath parser has bugs. Here is a patch.
104
+ ############################################################################
105
+ # You don't want to use this class. Really. Use XPath, which is a wrapper
106
+ # for this class. Believe me. You don't want to poke around in here.
107
+ # There is strange, dark magic at work in this code. Beware. Go back! Go
108
+ # back while you still can!
109
+ class XPathParser
110
+ include XMLTokens
111
+ # LITERAL = /^'([^']*)'|^"([^"]*)"/u
112
+
113
+ def initialize( )
114
+ @parser = REXML::Parsers::XPathParser.new
115
+ @namespaces = {}
116
+ @variables = {}
117
+ end
118
+
119
+ def namespaces=( namespaces={} )
120
+ Functions::namespace_context = namespaces
121
+ @namespaces = namespaces
122
+ end
123
+
124
+ def variables=( vars={} )
125
+ Functions::variables = vars
126
+ @variables = vars
127
+ end
128
+
129
+ def parse path, nodeset
130
+ #puts "#"*40
131
+ path_stack = @parser.parse( path )
132
+ #puts "PARSE: #{path} => #{path_stack.inspect}"
133
+ #puts "PARSE: nodeset = #{nodeset.inspect}"
134
+ match( path_stack, nodeset )
135
+ end
136
+
137
+ def get_first path, nodeset
138
+ #puts "#"*40
139
+ path_stack = @parser.parse( path )
140
+ #puts "PARSE: #{path} => #{path_stack.inspect}"
141
+ #puts "PARSE: nodeset = #{nodeset.inspect}"
142
+ first( path_stack, nodeset )
143
+ end
144
+
145
+ def predicate path, nodeset
146
+ path_stack = @parser.parse( path )
147
+ expr( path_stack, nodeset )
148
+ end
149
+
150
+ def []=( variable_name, value )
151
+ @variables[ variable_name ] = value
152
+ end
153
+
154
+
155
+ # Performs a depth-first (document order) XPath search, and returns the
156
+ # first match. This is the fastest, lightest way to return a single result.
157
+ def first( path_stack, node )
158
+ #puts "#{depth}) Entering match( #{path.inspect}, #{tree.inspect} )"
159
+ return nil if path.size == 0
160
+
161
+ case path[0]
162
+ when :document
163
+ # do nothing
164
+ return first( path[1..-1], node )
165
+ when :child
166
+ for c in node.children
167
+ #puts "#{depth}) CHILD checking #{name(c)}"
168
+ r = first( path[1..-1], c )
169
+ #puts "#{depth}) RETURNING #{r.inspect}" if r
170
+ return r if r
171
+ end
172
+ when :qname
173
+ name = path[2]
174
+ #puts "#{depth}) QNAME #{name(tree)} == #{name} (path => #{path.size})"
175
+ if node.name == name
176
+ #puts "#{depth}) RETURNING #{tree.inspect}" if path.size == 3
177
+ return node if path.size == 3
178
+ return first( path[3..-1], node )
179
+ else
180
+ return nil
181
+ end
182
+ when :descendant_or_self
183
+ r = first( path[1..-1], node )
184
+ return r if r
185
+ for c in node.children
186
+ r = first( path, c )
187
+ return r if r
188
+ end
189
+ when :node
190
+ return first( path[1..-1], node )
191
+ when :any
192
+ return first( path[1..-1], node )
193
+ end
194
+ return nil
195
+ end
196
+
197
+
198
+ def match( path_stack, nodeset )
199
+ #puts "MATCH: path_stack = #{path_stack.inspect}"
200
+ #puts "MATCH: nodeset = #{nodeset.inspect}"
201
+ r = expr( path_stack, nodeset )
202
+ #puts "MAIN EXPR => #{r.inspect}"
203
+ r
204
+
205
+ #while ( path_stack.size > 0 and nodeset.size > 0 )
206
+ # #puts "MATCH: #{path_stack.inspect} '#{nodeset.collect{|n|n.class}.inspect}'"
207
+ # nodeset = expr( path_stack, nodeset )
208
+ # #puts "NODESET: #{nodeset.inspect}"
209
+ # #puts "PATH_STACK: #{path_stack.inspect}"
210
+ #end
211
+ #nodeset
212
+ end
213
+
214
+ private
215
+
216
+
217
+ # Expr takes a stack of path elements and a set of nodes (either a Parent
218
+ # or an Array and returns an Array of matching nodes
219
+ ALL = [ :attribute, :element, :text, :processing_instruction, :comment ] unless defined?(ALL)
220
+ ELEMENTS = [ :element ] unless defined?(ELEMENTS)
221
+ def expr( path_stack, nodeset, context=nil )
222
+ #puts "#"*15
223
+ #puts "In expr with #{path_stack.inspect}"
224
+ #puts "Returning" if path_stack.length == 0 || nodeset.length == 0
225
+ node_types = ELEMENTS
226
+ return nodeset if path_stack.length == 0 || nodeset.length == 0
227
+ while path_stack.length > 0
228
+ #puts "Path stack = #{path_stack.inspect}"
229
+ #puts "Nodeset is #{nodeset.inspect}"
230
+ case (op = path_stack.shift)
231
+ when :document
232
+ nodeset = [ nodeset[0].root_node ]
233
+ #puts ":document, nodeset = #{nodeset.inspect}"
234
+
235
+ when :qname
236
+ #puts "IN QNAME"
237
+ prefix = path_stack.shift
238
+ name = path_stack.shift
239
+ default_ns = @namespaces[prefix]
240
+ default_ns = default_ns ? default_ns : ''
241
+ nodeset.delete_if do |node|
242
+ ns = default_ns
243
+ # FIXME: This DOUBLES the time XPath searches take
244
+ ns = node.namespace( prefix ) if node.node_type == :element and ns == ''
245
+ #puts "NS = #{ns.inspect}"
246
+ #puts "node.node_type == :element => #{node.node_type == :element}"
247
+ if node.node_type == :element
248
+ #puts "node.name == #{name} => #{node.name == name}"
249
+ if node.name == name
250
+ #puts "node.namespace == #{ns.inspect} => #{node.namespace == ns}"
251
+ end
252
+ end
253
+ !(node.node_type == :element and
254
+ node.name == name and
255
+ node.namespace == ns )
256
+ end
257
+ node_types = ELEMENTS
258
+
259
+ when :any
260
+ #puts "ANY 1: nodeset = #{nodeset.inspect}"
261
+ #puts "ANY 1: node_types = #{node_types.inspect}"
262
+ nodeset.delete_if { |node| !node_types.include?(node.node_type) }
263
+ #puts "ANY 2: nodeset = #{nodeset.inspect}"
264
+
265
+ when :self
266
+ # This space left intentionally blank
267
+
268
+ when :processing_instruction
269
+ target = path_stack.shift
270
+ nodeset.delete_if do |node|
271
+ (node.node_type != :processing_instruction) or
272
+ ( target!='' and ( node.target != target ) )
273
+ end
274
+
275
+ when :text
276
+ nodeset.delete_if { |node| node.node_type != :text }
277
+
278
+ when :comment
279
+ nodeset.delete_if { |node| node.node_type != :comment }
280
+
281
+ when :node
282
+ # This space left intentionally blank
283
+ node_types = ALL
284
+
285
+ when :child
286
+ new_nodeset = []
287
+ nt = nil
288
+ for node in nodeset
289
+ nt = node.node_type
290
+ new_nodeset += node.children if nt == :element or nt == :document
291
+ end
292
+ nodeset = new_nodeset
293
+ node_types = ELEMENTS
294
+
295
+ when :literal
296
+ literal = path_stack.shift
297
+ if literal =~ /^\d+(\.\d+)?$/
298
+ return ($1 ? literal.to_f : literal.to_i)
299
+ end
300
+ return literal
301
+
302
+ when :attribute
303
+ new_nodeset = []
304
+ case path_stack.shift
305
+ when :qname
306
+ prefix = path_stack.shift
307
+ name = path_stack.shift
308
+ for element in nodeset
309
+ if element.node_type == :element
310
+ #puts element.name
311
+ attr = element.attribute( name, @namespaces[prefix] )
312
+ new_nodeset << attr if attr
313
+ end
314
+ end
315
+ when :any
316
+ #puts "ANY"
317
+ for element in nodeset
318
+ if element.node_type == :element
319
+ new_nodeset += element.attributes.to_a
320
+ end
321
+ end
322
+ end
323
+ nodeset = new_nodeset
324
+
325
+ when :parent
326
+ #puts "PARENT 1: nodeset = #{nodeset}"
327
+ nodeset = nodeset.collect{|n| n.parent}.compact
328
+ #nodeset = expr(path_stack.dclone, nodeset.collect{|n| n.parent}.compact)
329
+ #puts "PARENT 2: nodeset = #{nodeset.inspect}"
330
+ node_types = ELEMENTS
331
+
332
+ when :ancestor
333
+ new_nodeset = []
334
+ for node in nodeset
335
+ while node.parent
336
+ node = node.parent
337
+ new_nodeset << node unless new_nodeset.include? node
338
+ end
339
+ end
340
+ nodeset = new_nodeset
341
+ node_types = ELEMENTS
342
+
343
+ when :ancestor_or_self
344
+ new_nodeset = []
345
+ for node in nodeset
346
+ if node.node_type == :element
347
+ new_nodeset << node
348
+ while ( node.parent )
349
+ node = node.parent
350
+ new_nodeset << node unless new_nodeset.include? node
351
+ end
352
+ end
353
+ end
354
+ nodeset = new_nodeset
355
+ node_types = ELEMENTS
356
+
357
+ when :predicate
358
+ new_nodeset = []
359
+ subcontext = { :size => nodeset.size }
360
+ pred = path_stack.shift
361
+ nodeset.each_with_index { |node, index|
362
+ subcontext[ :node ] = node
363
+ #puts "PREDICATE SETTING CONTEXT INDEX TO #{index+1}"
364
+ subcontext[ :index ] = index+1
365
+ pc = pred.dclone
366
+ #puts "#{node.hash}) Recursing with #{pred.inspect} and [#{node.inspect}]"
367
+ result = expr( pc, [node], subcontext )
368
+ result = result[0] if result.kind_of? Array and result.length == 1
369
+ #puts "#{node.hash}) Result = #{result.inspect} (#{result.class.name})"
370
+ if result.kind_of? Numeric
371
+ #puts "Adding node #{node.inspect}" if result == (index+1)
372
+ new_nodeset << node if result == (index+1)
373
+ elsif result.instance_of? Array
374
+ #puts "Adding node #{node.inspect}" if result.size > 0
375
+ new_nodeset << node if result.size > 0
376
+ else
377
+ #puts "Adding node #{node.inspect}" if result
378
+ new_nodeset << node if result
379
+ end
380
+ }
381
+ #puts "New nodeset = #{new_nodeset.inspect}"
382
+ #puts "Path_stack = #{path_stack.inspect}"
383
+ nodeset = new_nodeset
384
+ =begin
385
+ predicate = path_stack.shift
386
+ ns = nodeset.clone
387
+ result = expr( predicate, ns )
388
+ #puts "Result = #{result.inspect} (#{result.class.name})"
389
+ #puts "nodeset = #{nodeset.inspect}"
390
+ if result.kind_of? Array
391
+ nodeset = result.zip(ns).collect{|m,n| n if m}.compact
392
+ else
393
+ nodeset = result ? nodeset : []
394
+ end
395
+ #puts "Outgoing NS = #{nodeset.inspect}"
396
+ =end
397
+
398
+ when :descendant_or_self
399
+ rv = descendant_or_self( path_stack, nodeset )
400
+ path_stack.clear
401
+ nodeset = rv
402
+ node_types = ELEMENTS
403
+
404
+ when :descendant
405
+ results = []
406
+ nt = nil
407
+ for node in nodeset
408
+ nt = node.node_type
409
+ results += expr( path_stack.dclone.unshift( :descendant_or_self ),
410
+ node.children ) if nt == :element or nt == :document
411
+ end
412
+ nodeset = results
413
+ node_types = ELEMENTS
414
+
415
+ when :following_sibling
416
+ #puts "FOLLOWING_SIBLING 1: nodeset = #{nodeset}"
417
+ results = []
418
+ for node in nodeset
419
+ all_siblings = node.parent.children
420
+ current_index = all_siblings.index( node )
421
+ following_siblings = all_siblings[ current_index+1 .. -1 ]
422
+ results += expr( path_stack.dclone, following_siblings )
423
+ end
424
+ #puts "FOLLOWING_SIBLING 2: nodeset = #{nodeset}"
425
+ nodeset = results
426
+
427
+ when :preceding_sibling
428
+ results = []
429
+ for node in nodeset
430
+ all_siblings = node.parent.children
431
+ current_index = all_siblings.index( node )
432
+ preceding_siblings = all_siblings[ 0 .. current_index-1 ].reverse
433
+ #results += expr( path_stack.dclone, preceding_siblings )
434
+ end
435
+ nodeset = preceding_siblings
436
+ node_types = ELEMENTS
437
+
438
+ when :preceding
439
+ new_nodeset = []
440
+ for node in nodeset
441
+ new_nodeset += preceding( node )
442
+ end
443
+ #puts "NEW NODESET => #{new_nodeset.inspect}"
444
+ nodeset = new_nodeset
445
+ node_types = ELEMENTS
446
+
447
+ when :following
448
+ new_nodeset = []
449
+ for node in nodeset
450
+ new_nodeset += following( node )
451
+ end
452
+ nodeset = new_nodeset
453
+ node_types = ELEMENTS
454
+
455
+ when :namespace
456
+ new_set = []
457
+ for node in nodeset
458
+ new_nodeset << node.namespace if node.node_type == :element or node.node_type == :attribute
459
+ end
460
+ nodeset = new_nodeset
461
+
462
+ when :variable
463
+ var_name = path_stack.shift
464
+ return @variables[ var_name ]
465
+
466
+ # :and, :or, :eq, :neq, :lt, :lteq, :gt, :gteq
467
+ when :eq, :neq, :lt, :lteq, :gt, :gteq, :and, :or
468
+ left = expr( path_stack.shift, nodeset, context )
469
+ #puts "LEFT => #{left.inspect} (#{left.class.name})"
470
+ right = expr( path_stack.shift, nodeset, context )
471
+ #puts "RIGHT => #{right.inspect} (#{right.class.name})"
472
+ res = equality_relational_compare( left, op, right )
473
+ #puts "RES => #{res.inspect}"
474
+ return res
475
+
476
+ when :div
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 :mod
482
+ left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
483
+ right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
484
+ return (left % right)
485
+
486
+ when :mult
487
+ left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
488
+ right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
489
+ return (left * right)
490
+
491
+ when :plus
492
+ left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
493
+ right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
494
+ return (left + right)
495
+
496
+ when :minus
497
+ left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
498
+ right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
499
+ return (left - right)
500
+
501
+ when :union
502
+ left = expr( path_stack.shift, nodeset, context )
503
+ right = expr( path_stack.shift, nodeset, context )
504
+ return (left | right)
505
+
506
+ when :neg
507
+ res = expr( path_stack, nodeset, context )
508
+ return -(res.to_f)
509
+
510
+ when :not
511
+ when :function
512
+ func_name = path_stack.shift.tr('-','_')
513
+ arguments = path_stack.shift
514
+ #puts "FUNCTION 0: #{func_name}(#{arguments.collect{|a|a.inspect}.join(', ')})"
515
+ subcontext = context ? nil : { :size => nodeset.size }
516
+
517
+ res = []
518
+ cont = context
519
+ nodeset.each_with_index { |n, i|
520
+ if subcontext
521
+ subcontext[:node] = n
522
+ subcontext[:index] = i
523
+ cont = subcontext
524
+ end
525
+ arg_clone = arguments.dclone
526
+ args = arg_clone.collect { |arg|
527
+ #puts "FUNCTION 1: Calling expr( #{arg.inspect}, [#{n.inspect}] )"
528
+ expr( arg, [n], cont )
529
+ }
530
+ #puts "FUNCTION 2: #{func_name}(#{args.collect{|a|a.inspect}.join(', ')})"
531
+ Functions.context = cont
532
+ res << Functions.send( func_name, *args )
533
+ #puts "FUNCTION 3: #{res[-1].inspect}"
534
+ }
535
+ return res
536
+
537
+ end
538
+ end # while
539
+ #puts "EXPR returning #{nodeset.inspect}"
540
+ return nodeset
541
+ end
542
+
543
+
544
+ ##########################################################
545
+ # FIXME
546
+ # The next two methods are BAD MOJO!
547
+ # This is my achilles heel. If anybody thinks of a better
548
+ # way of doing this, be my guest. This really sucks, but
549
+ # it took me three days to get it to work at all.
550
+ # ########################################################
551
+
552
+ def descendant_or_self( path_stack, nodeset )
553
+ rs = []
554
+ d_o_s( path_stack, nodeset, rs )
555
+ #puts "RS = #{rs.collect{|n|n.to_s}.inspect}"
556
+ document_order(rs.flatten.compact)
557
+ #rs.flatten.compact
558
+ end
559
+
560
+ def d_o_s( p, ns, r )
561
+ #puts "IN DOS with #{ns.inspect}; ALREADY HAVE #{r.inspect}"
562
+ nt = nil
563
+ ns.each_index do |i|
564
+ n = ns[i]
565
+ #puts "P => #{p.inspect}"
566
+ x = expr( p.dclone, [ n ] )
567
+ nt = n.node_type
568
+ d_o_s( p, n.children, x ) if nt == :element or nt == :document and n.children.size > 0
569
+ r.concat(x) if x.size > 0
570
+ end
571
+ end
572
+
573
+
574
+ # Reorders an array of nodes so that they are in document order
575
+ # It tries to do this efficiently.
576
+ #
577
+ # FIXME: I need to get rid of this, but the issue is that most of the XPath
578
+ # interpreter functions as a filter, which means that we lose context going
579
+ # in and out of function calls. If I knew what the index of the nodes was,
580
+ # I wouldn't have to do this. Maybe add a document IDX for each node?
581
+ # Problems with mutable documents. Or, rewrite everything.
582
+ def document_order( array_of_nodes )
583
+ new_arry = []
584
+ array_of_nodes.each { |node|
585
+ node_idx = []
586
+ np = node.node_type == :attribute ? node.element : node
587
+ while np.parent and np.parent.node_type == :element
588
+ node_idx << np.parent.index( np )
589
+ np = np.parent
590
+ end
591
+ new_arry << [ node_idx.reverse, node ]
592
+ }
593
+ #puts "new_arry = #{new_arry.inspect}"
594
+ new_arry.sort{ |s1, s2| s1[0] <=> s2[0] }.collect{ |s| s[1] }
595
+ end
596
+
597
+
598
+ def recurse( nodeset, &block )
599
+ for node in nodeset
600
+ yield node
601
+ recurse( node, &block ) if node.node_type == :element
602
+ end
603
+ end
604
+
605
+
606
+
607
+ # Builds a nodeset of all of the preceding nodes of the supplied node,
608
+ # in reverse document order
609
+ # preceding:: includes every element in the document that precedes this node,
610
+ # except for ancestors
611
+ def preceding( node )
612
+ #puts "IN PRECEDING"
613
+ ancestors = []
614
+ p = node.parent
615
+ while p
616
+ ancestors << p
617
+ p = p.parent
618
+ end
619
+
620
+ acc = []
621
+ p = preceding_node_of( node )
622
+ #puts "P = #{p.inspect}"
623
+ while p
624
+ if ancestors.include? p
625
+ ancestors.delete(p)
626
+ else
627
+ acc << p
628
+ end
629
+ p = preceding_node_of( p )
630
+ #puts "P = #{p.inspect}"
631
+ end
632
+ acc
633
+ end
634
+
635
+ def preceding_node_of( node )
636
+ #puts "NODE: #{node.inspect}"
637
+ #puts "PREVIOUS NODE: #{node.previous_sibling_node.inspect}"
638
+ #puts "PARENT NODE: #{node.parent}"
639
+ psn = node.previous_sibling_node
640
+ if psn.nil?
641
+ if node.parent.nil? or node.parent.class == Document
642
+ return nil
643
+ end
644
+ return node.parent
645
+ #psn = preceding_node_of( node.parent )
646
+ end
647
+ while psn and psn.kind_of? Element and psn.children.size > 0
648
+ psn = psn.children[-1]
649
+ end
650
+ psn
651
+ end
652
+
653
+ def following( node )
654
+ #puts "IN PRECEDING"
655
+ acc = []
656
+ p = next_sibling_node( node )
657
+ #puts "P = #{p.inspect}"
658
+ while p
659
+ acc << p
660
+ p = following_node_of( p )
661
+ #puts "P = #{p.inspect}"
662
+ end
663
+ acc
664
+ end
665
+
666
+ def following_node_of( node )
667
+ #puts "NODE: #{node.inspect}"
668
+ #puts "PREVIOUS NODE: #{node.previous_sibling_node.inspect}"
669
+ #puts "PARENT NODE: #{node.parent}"
670
+ if node.kind_of? Element and node.children.size > 0
671
+ return node.children[0]
672
+ end
673
+ return next_sibling_node(node)
674
+ end
675
+
676
+ def next_sibling_node(node)
677
+ psn = node.next_sibling_node
678
+ while psn.nil?
679
+ if node.parent.nil? or node.parent.class == Document
680
+ return nil
681
+ end
682
+ node = node.parent
683
+ psn = node.next_sibling_node
684
+ #puts "psn = #{psn.inspect}"
685
+ end
686
+ return psn
687
+ end
688
+
689
+ def norm b
690
+ case b
691
+ when true, false
692
+ return b
693
+ when 'true', 'false'
694
+ return Functions::boolean( b )
695
+ when /^\d+(\.\d+)?$/
696
+ return Functions::number( b )
697
+ else
698
+ return Functions::string( b )
699
+ end
700
+ end
701
+
702
+ def equality_relational_compare( set1, op, set2 )
703
+ #puts "EQ_REL_COMP(#{set1.inspect} #{op.inspect} #{set2.inspect})"
704
+ if set1.kind_of? Array and set2.kind_of? Array
705
+ #puts "#{set1.size} & #{set2.size}"
706
+ if set1.size == 1 and set2.size == 1
707
+ set1 = set1[0]
708
+ set2 = set2[0]
709
+ elsif set1.size == 0 or set2.size == 0
710
+ nd = set1.size==0 ? set2 : set1
711
+ rv = nd.collect { |il| compare( il, op, nil ) }
712
+ #puts "RV = #{rv.inspect}"
713
+ return rv
714
+ else
715
+ res = []
716
+ enum = SyncEnumerator.new( set1, set2 ).each { |i1, i2|
717
+ #puts "i1 = #{i1.inspect} (#{i1.class.name})"
718
+ #puts "i2 = #{i2.inspect} (#{i2.class.name})"
719
+ i1 = norm( i1 )
720
+ i2 = norm( i2 )
721
+ res << compare( i1, op, i2 )
722
+ }
723
+ return res
724
+ end
725
+ end
726
+ #puts "EQ_REL_COMP: #{set1.inspect} (#{set1.class.name}), #{op}, #{set2.inspect} (#{set2.class.name})"
727
+ #puts "COMPARING VALUES"
728
+ # If one is nodeset and other is number, compare number to each item
729
+ # in nodeset s.t. number op number(string(item))
730
+ # If one is nodeset and other is string, compare string to each item
731
+ # in nodeset s.t. string op string(item)
732
+ # If one is nodeset and other is boolean, compare boolean to each item
733
+ # in nodeset s.t. boolean op boolean(item)
734
+ if set1.kind_of? Array or set2.kind_of? Array
735
+ #puts "ISA ARRAY"
736
+ if set1.kind_of? Array
737
+ a = set1
738
+ b = set2
739
+ else
740
+ a = set2
741
+ b = set1
742
+ end
743
+
744
+ case b
745
+ when true, false
746
+ return a.collect {|v| compare( Functions::boolean(v), op, b ) }
747
+ when Numeric
748
+ return a.collect {|v| compare( Functions::number(v), op, b )}
749
+ when /^\d+(\.\d+)?$/
750
+ b = Functions::number( b )
751
+ #puts "B = #{b.inspect}"
752
+ return a.collect {|v| compare( Functions::number(v), op, b )}
753
+ else
754
+ #puts "Functions::string( #{b}(#{b.class.name}) ) = #{Functions::string(b)}"
755
+ b = Functions::string( b )
756
+ return a.collect { |v| compare( Functions::string(v), op, b ) }
757
+ end
758
+ else
759
+ # If neither is nodeset,
760
+ # If op is = or !=
761
+ # If either boolean, convert to boolean
762
+ # If either number, convert to number
763
+ # Else, convert to string
764
+ # Else
765
+ # Convert both to numbers and compare
766
+ s1 = set1.to_s
767
+ s2 = set2.to_s
768
+ #puts "EQ_REL_COMP: #{set1}=>#{s1}, #{set2}=>#{s2}"
769
+ if s1 == 'true' or s1 == 'false' or s2 == 'true' or s2 == 'false'
770
+ #puts "Functions::boolean(#{set1})=>#{Functions::boolean(set1)}"
771
+ #puts "Functions::boolean(#{set2})=>#{Functions::boolean(set2)}"
772
+ set1 = Functions::boolean( set1 )
773
+ set2 = Functions::boolean( set2 )
774
+ else
775
+ if op == :eq or op == :neq
776
+ if s1 =~ /^\d+(\.\d+)?$/ or s2 =~ /^\d+(\.\d+)?$/
777
+ set1 = Functions::number( s1 )
778
+ set2 = Functions::number( s2 )
779
+ else
780
+ set1 = Functions::string( set1 )
781
+ set2 = Functions::string( set2 )
782
+ end
783
+ else
784
+ set1 = Functions::number( set1 )
785
+ set2 = Functions::number( set2 )
786
+ end
787
+ end
788
+ #puts "EQ_REL_COMP: #{set1} #{op} #{set2}"
789
+ #puts ">>> #{compare( set1, op, set2 )}"
790
+ return compare( set1, op, set2 )
791
+ end
792
+ return false
793
+ end
794
+
795
+ def compare a, op, b
796
+ #puts "COMPARE #{a.inspect}(#{a.class.name}) #{op} #{b.inspect}(#{b.class.name})"
797
+ case op
798
+ when :eq
799
+ a == b
800
+ when :neq
801
+ a != b
802
+ when :lt
803
+ a < b
804
+ when :lteq
805
+ a <= b
806
+ when :gt
807
+ a > b
808
+ when :gteq
809
+ a >= b
810
+ when :and
811
+ a and b
812
+ when :or
813
+ a or b
814
+ else
815
+ false
816
+ end
817
+ end
818
+ end
819
+
820
+
821
+ ############################################################################
822
+ end
823
+
824
+ # Restore the old $VERBOSE setting
825
+ $VERBOSE = oldverbose
826
+