sprsquish-blather 0.3.4 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. data/LICENSE +1 -1
  2. data/README.rdoc +41 -12
  3. data/examples/echo.rb +1 -1
  4. data/examples/execute.rb +0 -5
  5. data/examples/pubsub/cli.rb +64 -0
  6. data/examples/pubsub/ping_pong.rb +18 -0
  7. data/examples/rosterprint.rb +14 -0
  8. data/examples/xmpp4r/echo.rb +35 -0
  9. data/lib/blather/client/client.rb +19 -13
  10. data/lib/blather/client/dsl/pubsub.rb +133 -0
  11. data/lib/blather/client/dsl.rb +16 -0
  12. data/lib/blather/client.rb +1 -1
  13. data/lib/blather/core_ext/active_support/inheritable_attributes.rb +117 -0
  14. data/lib/blather/core_ext/active_support.rb +1 -117
  15. data/lib/blather/core_ext/nokogiri.rb +35 -0
  16. data/lib/blather/errors/sasl_error.rb +3 -1
  17. data/lib/blather/errors/stanza_error.rb +10 -17
  18. data/lib/blather/errors/stream_error.rb +11 -14
  19. data/lib/blather/errors.rb +3 -20
  20. data/lib/blather/jid.rb +1 -0
  21. data/lib/blather/roster.rb +9 -0
  22. data/lib/blather/roster_item.rb +6 -1
  23. data/lib/blather/stanza/disco/disco_info.rb +45 -33
  24. data/lib/blather/stanza/disco/disco_items.rb +32 -21
  25. data/lib/blather/stanza/disco.rb +7 -1
  26. data/lib/blather/stanza/iq/query.rb +16 -8
  27. data/lib/blather/stanza/iq/roster.rb +33 -22
  28. data/lib/blather/stanza/iq.rb +13 -8
  29. data/lib/blather/stanza/message.rb +20 -31
  30. data/lib/blather/stanza/presence/status.rb +13 -21
  31. data/lib/blather/stanza/presence/subscription.rb +11 -16
  32. data/lib/blather/stanza/presence.rb +3 -5
  33. data/lib/blather/stanza/pubsub/affiliations.rb +50 -0
  34. data/lib/blather/stanza/pubsub/create.rb +43 -0
  35. data/lib/blather/stanza/pubsub/errors.rb +9 -0
  36. data/lib/blather/stanza/pubsub/event.rb +77 -0
  37. data/lib/blather/stanza/pubsub/items.rb +63 -0
  38. data/lib/blather/stanza/pubsub/publish.rb +58 -0
  39. data/lib/blather/stanza/pubsub/retract.rb +53 -0
  40. data/lib/blather/stanza/pubsub/subscribe.rb +42 -0
  41. data/lib/blather/stanza/pubsub/subscription.rb +66 -0
  42. data/lib/blather/stanza/pubsub/subscriptions.rb +55 -0
  43. data/lib/blather/stanza/pubsub/unsubscribe.rb +42 -0
  44. data/lib/blather/stanza/pubsub.rb +63 -0
  45. data/lib/blather/stanza/pubsub_owner/delete.rb +34 -0
  46. data/lib/blather/stanza/pubsub_owner/purge.rb +34 -0
  47. data/lib/blather/stanza/pubsub_owner.rb +41 -0
  48. data/lib/blather/stanza.rb +35 -18
  49. data/lib/blather/stream/client.rb +1 -2
  50. data/lib/blather/stream/component.rb +9 -5
  51. data/lib/blather/stream/features/resource.rb +63 -0
  52. data/lib/blather/stream/{sasl.rb → features/sasl.rb} +53 -52
  53. data/lib/blather/stream/features/session.rb +44 -0
  54. data/lib/blather/stream/features/tls.rb +28 -0
  55. data/lib/blather/stream/features.rb +53 -0
  56. data/lib/blather/stream/parser.rb +70 -46
  57. data/lib/blather/stream.rb +76 -168
  58. data/lib/blather/xmpp_node.rb +113 -52
  59. data/lib/blather.rb +35 -12
  60. data/spec/blather/client/client_spec.rb +44 -58
  61. data/spec/blather/client/dsl/pubsub_spec.rb +465 -0
  62. data/spec/blather/client/dsl_spec.rb +19 -6
  63. data/spec/blather/core_ext/nokogiri_spec.rb +83 -0
  64. data/spec/blather/errors/sasl_error_spec.rb +8 -8
  65. data/spec/blather/errors/stanza_error_spec.rb +25 -33
  66. data/spec/blather/errors/stream_error_spec.rb +21 -16
  67. data/spec/blather/errors_spec.rb +4 -11
  68. data/spec/blather/jid_spec.rb +31 -30
  69. data/spec/blather/roster_item_spec.rb +34 -23
  70. data/spec/blather/roster_spec.rb +27 -12
  71. data/spec/blather/stanza/discos/disco_info_spec.rb +61 -42
  72. data/spec/blather/stanza/discos/disco_items_spec.rb +47 -35
  73. data/spec/blather/stanza/iq/query_spec.rb +34 -11
  74. data/spec/blather/stanza/iq/roster_spec.rb +47 -30
  75. data/spec/blather/stanza/iq_spec.rb +19 -14
  76. data/spec/blather/stanza/message_spec.rb +30 -17
  77. data/spec/blather/stanza/presence/status_spec.rb +43 -20
  78. data/spec/blather/stanza/presence/subscription_spec.rb +41 -21
  79. data/spec/blather/stanza/presence_spec.rb +34 -21
  80. data/spec/blather/stanza/pubsub/affiliations_spec.rb +57 -0
  81. data/spec/blather/stanza/pubsub/create_spec.rb +56 -0
  82. data/spec/blather/stanza/pubsub/event_spec.rb +84 -0
  83. data/spec/blather/stanza/pubsub/items_spec.rb +79 -0
  84. data/spec/blather/stanza/pubsub/publish_spec.rb +83 -0
  85. data/spec/blather/stanza/pubsub/retract_spec.rb +75 -0
  86. data/spec/blather/stanza/pubsub/subscribe_spec.rb +61 -0
  87. data/spec/blather/stanza/pubsub/subscription_spec.rb +97 -0
  88. data/spec/blather/stanza/pubsub/subscriptions_spec.rb +59 -0
  89. data/spec/blather/stanza/pubsub/unsubscribe_spec.rb +61 -0
  90. data/spec/blather/stanza/pubsub_owner/delete_spec.rb +50 -0
  91. data/spec/blather/stanza/pubsub_owner/purge_spec.rb +50 -0
  92. data/spec/blather/stanza/pubsub_owner_spec.rb +27 -0
  93. data/spec/blather/stanza/pubsub_spec.rb +62 -0
  94. data/spec/blather/stanza_spec.rb +53 -38
  95. data/spec/blather/stream/client_spec.rb +231 -88
  96. data/spec/blather/stream/component_spec.rb +14 -5
  97. data/spec/blather/stream/parser_spec.rb +145 -0
  98. data/spec/blather/xmpp_node_spec.rb +192 -96
  99. data/spec/fixtures/pubsub.rb +311 -0
  100. data/spec/spec_helper.rb +5 -4
  101. metadata +53 -17
  102. data/Rakefile +0 -139
  103. data/ext/extconf.rb +0 -65
  104. data/ext/push_parser.c +0 -209
  105. data/lib/blather/core_ext/libxml.rb +0 -28
  106. data/lib/blather/stream/resource.rb +0 -48
  107. data/lib/blather/stream/session.rb +0 -36
  108. data/lib/blather/stream/stream_handler.rb +0 -39
  109. data/lib/blather/stream/tls.rb +0 -33
  110. data/spec/blather/core_ext/libxml_spec.rb +0 -58
@@ -1,6 +1,11 @@
1
1
  module Blather
2
2
 
3
3
  class Stream < EventMachine::Connection
4
+ class NoConnection < RuntimeError; end
5
+
6
+ STREAM_NS = 'http://etherx.jabber.org/streams'
7
+ attr_accessor :jid, :password
8
+
4
9
  ##
5
10
  # Start the stream between client and server
6
11
  # [client] must be an object that will respond to #call and #jid=
@@ -10,9 +15,35 @@ module Blather
10
15
  # [port] (optional) must be the port to connect to. defaults to 5222
11
16
  def self.start(client, jid, pass, host = nil, port = 5222)
12
17
  jid = JID.new jid
13
- host ||= jid.domain
18
+ if host
19
+ connect host, port, self, client, jid, pass
20
+ else
21
+ require 'resolv'
22
+ srv = []
23
+ Resolv::DNS.open { |dns| srv = dns.getresources("_xmpp-client._tcp.#{jid.domain}", Resolv::DNS::Resource::IN::SRV) }
24
+ if srv.empty?
25
+ connect jid.domain, port, self, client, jid, pass
26
+ else
27
+ srv.sort! { |a,b| (a.priority != b.priority) ? (a.priority <=> b.priority) : (b.weight <=> a.weight) }
28
+ conn = nil
29
+ srv.each { |r| break unless (conn = connect(r.target.to_s, r.port, self, client, jid, pass)) === false }
30
+ conn
31
+ end
32
+ end
33
+ end
14
34
 
15
- EM.connect host, port, self, client, jid, pass
35
+ ##
36
+ # Attempt a connection
37
+ # Stream will raise +NoConnection+ if it receives #unbind before #post_init
38
+ # this catches that and returns false prompting for another attempt
39
+ def self.connect(host, port, conn, client, jid, pass)
40
+ EM.connect host, port, conn, client, jid, pass
41
+ rescue NoConnection
42
+ false
43
+ end
44
+
45
+ [:started, :stopped, :ready, :negotiating].each do |state|
46
+ define_method("#{state}?") { @state == state }
16
47
  end
17
48
 
18
49
  ##
@@ -21,73 +52,53 @@ module Blather
21
52
  # responds to #to_s
22
53
  def send(stanza)
23
54
  #TODO Queue if not ready
24
- LOG.debug "SENDING: (#{caller[1]}) #{stanza}"
55
+ Blather.logger.debug "SENDING: (#{caller[1]}) #{stanza}"
25
56
  send_data stanza.respond_to?(:to_xml) ? stanza.to_xml : stanza.to_s
26
57
  end
27
58
 
28
- ##
29
- # True if the stream is in the stopped state
30
- def stopped?
31
- @state == :stopped
32
- end
33
-
34
- ##
35
- # True when the stream is in the negotiation phase.
36
- def negotiating?
37
- ![:stopped, :ready].include? @state
38
- end
39
-
40
- ##
41
- # True when the stream is ready
42
- # The stream is ready immediately after receiving <stream:stream>
43
- # and before any feature negotion. Once feature negoation starts
44
- # the stream will not be ready until all negotations have completed
45
- # successfully.
46
- def ready?
47
- @state == :ready
48
- end
49
-
50
59
  ##
51
60
  # Called by EM.connect to initialize stream variables
52
61
  def initialize(client, jid, pass) # :nodoc:
53
62
  super()
54
63
 
55
64
  @error = nil
56
- @client = client
65
+ @receiver = @client = client
57
66
 
58
67
  self.jid = jid
59
- @pass = pass
60
-
61
- @to = @jid.domain
68
+ @to = self.jid.domain
69
+ @password = pass
62
70
  end
63
71
 
64
72
  ##
65
73
  # Called when EM completes the connection to the server
66
74
  # this kicks off the starttls/authorize/bind process
67
75
  def connection_completed # :nodoc:
68
- # @keepalive = EM::Timer.new(60) { send_data ' ' }
69
- @state = :stopped
70
- dispatch
76
+ # @keepalive = EM::PeriodicTimer.new(60) { send_data ' ' }
77
+ start
71
78
  end
72
79
 
73
80
  ##
74
81
  # Called by EM with data from the wire
75
82
  def receive_data(data) # :nodoc:
76
- LOG.debug "\n#{'-'*30}\n"
77
- LOG.debug "<< #{data}"
78
- @parser.receive_data data
83
+ Blather.logger.debug "\n#{'-'*30}\n"
84
+ Blather.logger.debug "<< #{data}"
85
+ @parser << data
79
86
 
80
- rescue ParseWarning => e
81
- @client.receive_data e
82
87
  rescue ParseError => e
83
88
  @error = e
84
- send "<stream:error><xml-not-well-formed xmlns='urn:ietf:params:xml:ns:xmpp-streams'/></stream:error>"
89
+ send "<stream:error><xml-not-well-formed xmlns='#{StreamError::STREAM_ERR_NS}'/></stream:error>"
85
90
  stop
86
91
  end
87
92
 
93
+ def post_init
94
+ @connected = true
95
+ end
96
+
88
97
  ##
89
98
  # Called by EM when the connection is closed
90
99
  def unbind # :nodoc:
100
+ raise NoConnection unless @connected
101
+
91
102
  # @keepalive.cancel
92
103
  @state = :stopped
93
104
  @client.receive_data @error if @error
@@ -97,54 +108,41 @@ module Blather
97
108
  ##
98
109
  # Called by the parser with parsed nodes
99
110
  def receive(node) # :nodoc:
100
- LOG.debug "RECEIVING (#{node.element_name}) #{node}"
111
+ Blather.logger.debug "RECEIVING (#{node.element_name}) #{node}"
101
112
  @node = node
102
113
 
103
- if @node.find_first('//stream:error', :stream => 'http://etherx.jabber.org/streams')
104
- handle_stream_error
105
- return
106
- end
107
-
108
- case @node.element_name
109
- when 'stream'
110
- @state = :ready if @state == :stopped
111
-
112
- when 'stream:end'
113
- stop
114
-
115
- when 'features'
116
- @features = @node.children
117
- @state = :features
118
- dispatch
119
-
120
- else
121
- dispatch
122
-
114
+ if @node.namespace && @node.namespace.prefix == 'stream'
115
+ case @node.element_name
116
+ when 'stream'
117
+ @state = :ready if @state == :stopped
118
+ return
119
+ when 'error'
120
+ handle_stream_error
121
+ return
122
+ when 'end'
123
+ stop
124
+ return
125
+ when 'features'
126
+ @state = :negotiating
127
+ @receiver = Features.new(
128
+ self,
129
+ proc { ready! },
130
+ proc { |err| @error = err; stop }
131
+ )
132
+ end
123
133
  end
134
+ @receiver.receive_data @node.to_stanza
124
135
  end
125
136
 
126
137
  ##
127
138
  # Ensure the JID gets attached to the client
128
139
  def jid=(new_jid) # :nodoc:
129
- LOG.debug "NEW JID: #{new_jid}"
140
+ Blather.logger.debug "NEW JID: #{new_jid}"
130
141
  @jid = JID.new new_jid
131
142
  @client.jid = @jid
132
143
  end
133
144
 
134
145
  protected
135
- ##
136
- # Dispatch based on current state
137
- def dispatch
138
- __send__ @state
139
- end
140
-
141
- ##
142
- # Start the stream
143
- # Each time the stream is started or re-started we need to kill off the old
144
- # parser so as not to confuse it
145
- def start
146
- end
147
-
148
146
  ##
149
147
  # Stop the stream
150
148
  def stop
@@ -154,105 +152,15 @@ module Blather
154
152
  end
155
153
  end
156
154
 
157
- ##
158
- # Called when @state == :stopped to start the stream
159
- # Counter intuitive, I know
160
- def stopped
161
- start
162
- end
163
-
164
- ##
165
- # Called when @state == :ready
166
- # Simply passes the stanza to the client
167
- def ready
168
- @client.receive_data @node.to_stanza
169
- end
170
-
171
155
  def handle_stream_error
172
- @error = StreamError.import @node
156
+ @error = StreamError.import(@node)
173
157
  stop
174
- @state = :error
175
158
  end
176
159
 
177
- ##
178
- # Called when @state == :features
179
- # Runs through the list of features starting each one in turn
180
- def features
181
- feature = @features.first
182
- LOG.debug "FEATURE: #{feature}"
183
- @state = case feature ? feature.namespaces.default.href : nil
184
- when 'urn:ietf:params:xml:ns:xmpp-tls' then :establish_tls
185
- when 'urn:ietf:params:xml:ns:xmpp-sasl' then :authenticate_sasl
186
- when 'urn:ietf:params:xml:ns:xmpp-bind' then :bind_resource
187
- when 'urn:ietf:params:xml:ns:xmpp-session' then :establish_session
188
- else :ready
189
- end
190
-
191
- # Dispatch to the individual feature methods unless
192
- # feature negotiation is complete
193
- dispatch unless ready?
194
- end
195
-
196
- ##
197
- # Start TLS
198
- def establish_tls
199
- unless @tls
200
- @tls = TLS.new self
201
- # on success destroy the TLS object and restart the stream
202
- @tls.on_success { LOG.debug "TLS: SUCCESS"; @tls = nil; start }
203
- # on failure stop the stream
204
- @tls.on_failure { |err| LOG.debug "TLS: FAILURE"; @error = err; stop }
205
-
206
- @node = @features.shift
207
- end
208
- @tls.handle @node
209
- end
210
-
211
- ##
212
- # Authenticate via SASL
213
- def authenticate_sasl
214
- unless @sasl
215
- @sasl = SASL.new(self, @jid, @pass)
216
- # on success destroy the SASL object and restart the stream
217
- @sasl.on_success { LOG.debug "SASL SUCCESS"; @sasl = nil; start }
218
- # on failure set the error and stop the stream
219
- @sasl.on_failure { |err| LOG.debug "SASL FAIL"; @error = err; stop }
220
-
221
- @node = @features.shift
222
- end
223
- @sasl.handle @node
224
- end
225
-
226
- ##
227
- # Bind to the resource provided by either the client or the server
228
- def bind_resource
229
- unless @resource
230
- @resource = Resource.new self, @jid
231
- # on success destroy the Resource object, set the jid, continue along the features dispatch process
232
- @resource.on_success { |jid| LOG.debug "RESOURCE: SUCCESS"; @resource = nil; self.jid = jid; @state = :features; dispatch }
233
- # on failure end the stream
234
- @resource.on_failure { |err| LOG.debug "RESOURCE: FAILURE"; @error = err; stop }
235
-
236
- @node = @features.shift
237
- end
238
- @resource.handle @node
239
- end
240
-
241
- ##
242
- # Establish the session between client and server
243
- def establish_session
244
- unless @session
245
- @session = Session.new self, @to
246
- # on success destroy the session object, let the client know the stream has been started
247
- # then continue the features dispatch process
248
- @session.on_success { LOG.debug "SESSION: SUCCESS"; @session = nil; @client.post_init; @state = :features; dispatch }
249
- # on failure end the stream
250
- @session.on_failure { |err| LOG.debug "SESSION: FAILURE"; @error = err; stop }
251
-
252
- @node = @features.shift
253
- end
254
- @session.handle @node
160
+ def ready!
161
+ @state = :started
162
+ @receiver = @client
163
+ @client.post_init
255
164
  end
256
165
  end
257
-
258
166
  end
@@ -4,13 +4,13 @@ module Blather
4
4
  # Base XML Node
5
5
  # All XML classes subclass XMPPNode
6
6
  # it allows the addition of helpers
7
- class XMPPNode < XML::Node
7
+ class XMPPNode < Nokogiri::XML::Node
8
8
  BASE_NAMES = %w[presence message iq].freeze
9
9
 
10
10
  @@registrations = {}
11
11
 
12
- class_inheritable_accessor :ns,
13
- :name
12
+ class_inheritable_accessor :registered_ns,
13
+ :registered_name
14
14
 
15
15
  ##
16
16
  # Lets a subclass register itself
@@ -19,9 +19,9 @@ module Blather
19
19
  # up the class name of the object to instantiate when a new
20
20
  # stanza is received
21
21
  def self.register(name, ns = nil)
22
- self.name = name.to_s
23
- self.ns = ns
24
- @@registrations[[self.name, self.ns]] = self
22
+ self.registered_name = name.to_s
23
+ self.registered_ns = ns
24
+ @@registrations[[self.registered_name, self.registered_ns]] = self
25
25
  end
26
26
 
27
27
  ##
@@ -36,7 +36,7 @@ module Blather
36
36
  # of that class and imports all the <tt>node</tt>'s attributes
37
37
  # and children into it.
38
38
  def self.import(node)
39
- klass = class_from_registration(node.element_name, node.namespace)
39
+ klass = class_from_registration(node.element_name, (node.namespace.href if node.namespace))
40
40
  if klass && klass != self
41
41
  klass.import(node)
42
42
  else
@@ -55,16 +55,18 @@ module Blather
55
55
  # end
56
56
  #
57
57
  # n = Node.new
58
- # n.attributes[:type] = 'foo'
58
+ # n[:type] = 'foo'
59
59
  # n.type == :foo
60
- # n.attributes[:name] = 'bar'
60
+ # n[:name] = 'bar'
61
61
  # n.name == 'bar'
62
62
  def self.attribute_reader(*syms)
63
63
  opts = syms.last.is_a?(Hash) ? syms.pop : {}
64
+ convert_str = "val.#{opts[:call]} if val" if opts[:call]
64
65
  syms.flatten.each do |sym|
65
66
  class_eval(<<-END, __FILE__, __LINE__)
66
67
  def #{sym}
67
- attributes[:#{sym}]#{".to_sym unless attributes[:#{sym}].blank?" unless opts[:to_sym] == false}
68
+ val = self[:#{sym}]
69
+ #{convert_str}
68
70
  end
69
71
  END
70
72
  end
@@ -79,13 +81,13 @@ module Blather
79
81
  #
80
82
  # n = Node.new
81
83
  # n.type = 'foo'
82
- # n.attributes[:type] == 'foo'
84
+ # n[:type] == 'foo'
83
85
  def self.attribute_writer(*syms)
84
86
  syms.flatten.each do |sym|
85
87
  next if sym.is_a?(Hash)
86
88
  class_eval(<<-END, __FILE__, __LINE__)
87
89
  def #{sym}=(value)
88
- attributes[:#{sym}] = value
90
+ self[:#{sym}] = value
89
91
  end
90
92
  END
91
93
  end
@@ -110,14 +112,74 @@ module Blather
110
112
  attribute_writer *syms
111
113
  end
112
114
 
115
+ ##
116
+ # Provides a content reader helper that returns the content of a node
117
+ # +method+ is the method to create
118
+ # +conversion+ is a method to call on the content before sending it back
119
+ # +node+ is the name of the content node (this defaults to the method name)
120
+ #
121
+ # class Node
122
+ # content_attr_reader :body
123
+ # content_attr_reader :type, :to_sym
124
+ # content_attr_reader :id, :to_i, :identity
125
+ # end
126
+ #
127
+ # n = Node.new 'foo'
128
+ # n.to_s == "<foo><body>foobarbaz</body><type>error</type><identity>1000</identity></foo>"
129
+ # n.body == 'foobarbaz'
130
+ # n.type == :error
131
+ # n.id == 1000
132
+ def self.content_attr_reader(method, conversion = nil, node = nil)
133
+ node ||= method
134
+ conversion = "val.#{conversion} if val.respond_to?(:#{conversion})" if conversion
135
+ class_eval(<<-END, __FILE__, __LINE__)
136
+ def #{method}
137
+ val = content_from :#{node}
138
+ #{conversion}
139
+ end
140
+ END
141
+ end
142
+
143
+ ##
144
+ # Provides a content writer helper that creates or updates the content of a node
145
+ # +method+ is the method to create
146
+ # +node+ is the name of the node to create (defaults to the method name)
147
+ #
148
+ # class Node
149
+ # content_attr_writer :body
150
+ # content_attr_writer :id, :identity
151
+ # end
152
+ #
153
+ # n = Node.new 'foo'
154
+ # n.body = 'thebodytext'
155
+ # n.id = 'id-text'
156
+ # n.to_s == '<foo><body>thebodytext</body><identity>id-text</identity></foo>'
157
+ def self.content_attr_writer(method, node = nil)
158
+ node ||= method
159
+ class_eval(<<-END, __FILE__, __LINE__)
160
+ def #{method}=(val)
161
+ set_content_for :#{node}, val
162
+ end
163
+ END
164
+ end
165
+
166
+ ##
167
+ # Provides a quick way of building +content_attr_reader+ and +content_attr_writer+
168
+ # for the same method and node
169
+ def self.content_attr_accessor(method, conversion = nil, node = nil)
170
+ content_attr_reader method, conversion, node
171
+ content_attr_writer method, node
172
+ end
173
+
113
174
  ##
114
175
  # Automatically sets the namespace registered by the subclass
115
- def initialize(name = nil, content = nil)
116
- name ||= self.class.name
117
- content = content.to_s if content
176
+ def self.new(name = nil, doc = nil)
177
+ name ||= self.registered_name
118
178
 
119
- super name.to_s, content
120
- self.namespace = self.class.ns unless BASE_NAMES.include?(name.to_s)
179
+ node = super name.to_s, (doc || Nokogiri::XML::Document.new)
180
+ node.document.root = node unless doc
181
+ node.namespace = self.registered_ns unless BASE_NAMES.include?(name.to_s)
182
+ node
121
183
  end
122
184
 
123
185
  ##
@@ -126,80 +188,79 @@ module Blather
126
188
  self.class.import self
127
189
  end
128
190
 
191
+ alias_method :nokogiri_namespace=, :namespace=
129
192
  def namespace=(namespaces)
130
193
  case namespaces
131
- when XML::Namespace
132
- self.namespaces.namespace = namespaces
194
+ when Nokogiri::XML::Namespace
195
+ self.nokogiri_namespace = namespaces
133
196
  when String
134
- self.namespaces.namespace = XML::Namespace.new(self, nil, namespaces)
197
+ self.add_namespace nil, namespaces
135
198
  when Hash
199
+ if ns = namespaces.delete(nil)
200
+ self.add_namespace nil, ns
201
+ end
136
202
  namespaces.each do |p, n|
137
- self.namespaces.namespace = XML::Namespace.new(self, p, n)
203
+ ns = self.add_namespace p, n
204
+ self.nokogiri_namespace = ns
138
205
  end
139
206
  end
140
207
  end
141
208
 
142
- def namespace(prefix = nil)
143
- (ns = namespaces.find_by_prefix(prefix)) ? ns.href : nil
209
+ def namespace_href
210
+ namespace.href if namespace
144
211
  end
145
212
 
146
213
  ##
147
214
  # Remove a child with the name and (optionally) namespace given
148
215
  def remove_child(name, ns = nil)
149
- name = name.to_s
150
- self.detect { |n| n.remove! if n.element_name == name && (!ns || n.namespace == ns) }
216
+ child = xpath(name, ns).first
217
+ child.remove if child
151
218
  end
152
219
 
153
220
  ##
154
221
  # Remove all children with a given name
155
222
  def remove_children(name)
156
- name = name.to_s
157
- self.find(name).each { |n| n.remove! }
223
+ xpath("./*[local-name()='#{name}']").remove
158
224
  end
159
225
 
160
226
  ##
161
227
  # Pull the content from a child
162
- def content_from(name)
163
- name = name.to_s
164
- (child = self.detect { |n| n.element_name == name }) ? child.content : nil
228
+ def content_from(name, ns = nil)
229
+ child = xpath(name, ns).first
230
+ child.content if child
165
231
  end
166
232
 
167
233
  ##
168
- # Create a copy
169
- def copy(deep = true)
170
- copy = self.class.new.inherit(self)
171
- copy.element_name = self.element_name
172
- copy
234
+ # Sets the content for the specified node.
235
+ # If the node exists it is updated. If not a new node is created
236
+ # If the node exists and the content is nil, the node will be removed entirely
237
+ def set_content_for(node, content = nil)
238
+ if content
239
+ child = xpath(node).first
240
+ self << (child = XMPPNode.new(node, self.document)) unless child
241
+ child.content = content
242
+ else
243
+ remove_child node
244
+ end
173
245
  end
174
246
 
247
+ alias_method :copy, :dup
248
+
175
249
  ##
176
250
  # Inherit all of <tt>stanza</tt>'s attributes and children
177
251
  def inherit(stanza)
252
+ set_namespace stanza.namespace if stanza.namespace
178
253
  inherit_attrs stanza.attributes
179
- stanza.children.each { |c| self << c.copy(true) }
254
+ stanza.children.each { |c| self << c.dup }
180
255
  self
181
256
  end
182
257
 
183
258
  ##
184
259
  # Inherit only <tt>stanza</tt>'s attributes
185
260
  def inherit_attrs(attrs)
186
- attrs.each { |a| attributes[a.name] = a.value }
261
+ attrs.each { |name, value| self[name] = value }
187
262
  self
188
263
  end
189
-
190
- ##
191
- # Turn itself into an XML string and remove all whitespace between nodes
192
- def to_xml
193
- # TODO: Fix this for HTML nodes (and any other that might require whitespace)
194
- to_s.gsub(">\n<", '><')
195
- end
196
-
197
- ##
198
- # Override #find to work when a node isn't attached to a document
199
- def find(what, nslist = nil)
200
- what = what.to_s
201
- (self.doc ? super(what, nslist) : select { |i| i.element_name == what })
202
- end
203
264
  end #XMPPNode
204
265
 
205
- end
266
+ end
data/lib/blather.rb CHANGED
@@ -1,14 +1,13 @@
1
1
  # Require the necessary files
2
- require File.join(File.dirname(__FILE__), *%w[.. ext push_parser])
3
2
  %w[
4
3
  rubygems
5
4
  eventmachine
6
- xml/libxml
5
+ nokogiri
7
6
  digest/md5
8
7
  logger
9
8
 
10
9
  blather/core_ext/active_support
11
- blather/core_ext/libxml
10
+ blather/core_ext/nokogiri
12
11
 
13
12
  blather/errors
14
13
  blather/errors/sasl_error
@@ -31,20 +30,44 @@ require File.join(File.dirname(__FILE__), *%w[.. ext push_parser])
31
30
  blather/stanza/presence/status
32
31
  blather/stanza/presence/subscription
33
32
 
33
+ blather/stanza/pubsub
34
+ blather/stanza/pubsub/affiliations
35
+ blather/stanza/pubsub/create
36
+ blather/stanza/pubsub/event
37
+ blather/stanza/pubsub/items
38
+ blather/stanza/pubsub/publish
39
+ blather/stanza/pubsub/retract
40
+ blather/stanza/pubsub/subscribe
41
+ blather/stanza/pubsub/subscription
42
+ blather/stanza/pubsub/subscriptions
43
+ blather/stanza/pubsub/unsubscribe
44
+
45
+ blather/stanza/pubsub_owner
46
+ blather/stanza/pubsub_owner/delete
47
+ blather/stanza/pubsub_owner/purge
48
+
34
49
  blather/stream
35
50
  blather/stream/client
36
51
  blather/stream/component
37
- blather/stream/stream_handler
38
52
  blather/stream/parser
39
- blather/stream/resource
40
- blather/stream/sasl
41
- blather/stream/session
42
- blather/stream/tls
53
+ blather/stream/features
54
+ blather/stream/features/resource
55
+ blather/stream/features/sasl
56
+ blather/stream/features/session
57
+ blather/stream/features/tls
43
58
  ].each { |r| require r }
44
59
 
45
- XML.indent_tree_output = false
46
-
47
60
  module Blather
48
- LOG = Logger.new($stdout) unless const_defined?(:LOG)
49
- LOG.level = Logger::INFO
61
+ @@logger = nil
62
+ def self.logger
63
+ unless @@logger
64
+ self.logger = Logger.new($stdout)
65
+ self.logger.level = Logger::INFO
66
+ end
67
+ @@logger
68
+ end
69
+
70
+ def self.logger=(logger)
71
+ @@logger = logger
72
+ end
50
73
  end