sprsquish-blather 0.3.4 → 0.4.0

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 (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