xmpp4r 0.3.1 → 0.3.2

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