blather 0.2.1 → 0.2.2
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.
- data/LICENSE +2 -0
- data/README.rdoc +54 -29
- data/Rakefile +94 -13
- data/VERSION.yml +4 -0
- data/examples/drb_client.rb +2 -4
- data/examples/echo.rb +13 -8
- data/examples/pubsub/cli.rb +64 -0
- data/examples/pubsub/ping_pong.rb +18 -0
- data/examples/pubsub/pubsub_dsl.rb +52 -0
- data/examples/pubsub_client.rb +39 -0
- data/examples/rosterprint.rb +14 -0
- data/examples/xmpp4r/echo.rb +35 -0
- data/ext/extconf.rb +65 -0
- data/lib/blather.rb +18 -121
- data/lib/blather/client.rb +13 -0
- data/lib/blather/client/client.rb +165 -0
- data/lib/blather/client/dsl.rb +99 -0
- data/lib/blather/client/pubsub.rb +53 -0
- data/lib/blather/client/pubsub/node.rb +27 -0
- data/lib/blather/core_ext/active_support.rb +1 -0
- data/lib/blather/core_ext/libxml.rb +7 -1
- data/lib/blather/errors.rb +39 -18
- data/lib/blather/errors/sasl_error.rb +87 -0
- data/lib/blather/errors/stanza_error.rb +262 -0
- data/lib/blather/errors/stream_error.rb +253 -0
- data/lib/blather/jid.rb +9 -16
- data/lib/blather/roster.rb +9 -0
- data/lib/blather/roster_item.rb +7 -4
- data/lib/blather/stanza.rb +19 -25
- data/lib/blather/stanza/disco.rb +9 -0
- data/lib/blather/stanza/disco/disco_info.rb +84 -0
- data/lib/blather/stanza/disco/disco_items.rb +59 -0
- data/lib/blather/stanza/iq.rb +16 -4
- data/lib/blather/stanza/iq/query.rb +6 -4
- data/lib/blather/stanza/iq/roster.rb +38 -38
- data/lib/blather/stanza/pubsub.rb +33 -0
- data/lib/blather/stanza/pubsub/affiliations.rb +52 -0
- data/lib/blather/stanza/pubsub/errors.rb +9 -0
- data/lib/blather/stanza/pubsub/event.rb +21 -0
- data/lib/blather/stanza/pubsub/items.rb +59 -0
- data/lib/blather/stanza/pubsub/owner.rb +9 -0
- data/lib/blather/stanza/pubsub/subscriptions.rb +57 -0
- data/lib/blather/stream.rb +125 -57
- data/lib/blather/stream/client.rb +26 -0
- data/lib/blather/stream/component.rb +34 -0
- data/lib/blather/stream/parser.rb +17 -27
- data/lib/blather/stream/resource.rb +21 -24
- data/lib/blather/stream/sasl.rb +60 -37
- data/lib/blather/stream/session.rb +12 -19
- data/lib/blather/stream/stream_handler.rb +39 -0
- data/lib/blather/stream/tls.rb +22 -18
- data/lib/blather/xmpp_node.rb +91 -17
- data/spec/blather/core_ext/libxml_spec.rb +58 -0
- data/spec/blather/errors/sasl_error_spec.rb +56 -0
- data/spec/blather/errors/stanza_error_spec.rb +148 -0
- data/spec/blather/errors/stream_error_spec.rb +114 -0
- data/spec/blather/errors_spec.rb +40 -0
- data/spec/blather/jid_spec.rb +0 -7
- data/spec/blather/roster_item_spec.rb +5 -0
- data/spec/blather/roster_spec.rb +6 -6
- data/spec/blather/stanza/discos/disco_info_spec.rb +207 -0
- data/spec/blather/stanza/discos/disco_items_spec.rb +136 -0
- data/spec/blather/stanza/iq/query_spec.rb +9 -2
- data/spec/blather/stanza/iq/roster_spec.rb +117 -1
- data/spec/blather/stanza/iq_spec.rb +29 -0
- data/spec/blather/stanza/presence/subscription_spec.rb +12 -1
- data/spec/blather/stanza/presence_spec.rb +29 -0
- data/spec/blather/stanza/pubsub/affiliations_spec.rb +46 -0
- data/spec/blather/stanza/pubsub/items_spec.rb +59 -0
- data/spec/blather/stanza/pubsub/subscriptions_spec.rb +63 -0
- data/spec/blather/stanza/pubsub_spec.rb +26 -0
- data/spec/blather/stanza_spec.rb +13 -1
- data/spec/blather/stream/client_spec.rb +787 -0
- data/spec/blather/stream/component_spec.rb +86 -0
- data/spec/blather/xmpp_node_spec.rb +75 -22
- data/spec/fixtures/pubsub.rb +157 -0
- data/spec/spec_helper.rb +6 -14
- metadata +86 -74
- data/CHANGELOG +0 -5
- data/Manifest +0 -47
- data/blather.gemspec +0 -41
- data/lib/blather/stanza/error.rb +0 -31
- data/spec/blather/stream_spec.rb +0 -462
- data/spec/build_safe.rb +0 -20
data/lib/blather/stream/sasl.rb
CHANGED
@@ -1,66 +1,68 @@
|
|
1
1
|
module Blather # :nodoc:
|
2
|
-
|
2
|
+
class Stream # :nodoc:
|
3
3
|
|
4
|
-
class SASL # :nodoc:
|
5
|
-
class UnknownMechanism < BlatherError
|
4
|
+
class SASL < StreamHandler # :nodoc:
|
5
|
+
class UnknownMechanism < BlatherError
|
6
|
+
handler_heirarchy ||= []
|
7
|
+
handler_heirarchy << :unknown_mechanism
|
8
|
+
end
|
6
9
|
|
7
|
-
SASL_NS = 'urn:ietf:params:xml:ns:xmpp-sasl'
|
10
|
+
SASL_NS = 'urn:ietf:params:xml:ns:xmpp-sasl'.freeze
|
8
11
|
|
9
12
|
def initialize(stream, jid, pass = nil)
|
10
|
-
|
13
|
+
super stream
|
11
14
|
@jid = jid
|
12
15
|
@pass = pass
|
13
|
-
@
|
14
|
-
@mechanism = 0
|
16
|
+
@mechanism_idx = 0
|
15
17
|
@mechanisms = []
|
16
|
-
|
17
|
-
init_callbacks
|
18
|
-
end
|
19
|
-
|
20
|
-
def init_callbacks
|
21
|
-
@callbacks['mechanisms'] = proc {
|
22
|
-
@mechanisms = @node.children
|
23
|
-
set_mechanism
|
24
|
-
authenticate
|
25
|
-
}
|
26
18
|
end
|
27
19
|
|
28
20
|
def set_mechanism
|
29
|
-
mod = case (mechanism = @mechanisms[@
|
21
|
+
mod = case (mechanism = @mechanisms[@mechanism_idx].content)
|
30
22
|
when 'DIGEST-MD5' then DigestMD5
|
31
23
|
when 'PLAIN' then Plain
|
32
24
|
when 'ANONYMOUS' then Anonymous
|
33
25
|
else
|
26
|
+
# Send a failure node and kill the stream
|
34
27
|
@stream.send "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><invalid-mechanism/></failure>"
|
35
|
-
|
28
|
+
@failure.call UnknownMechanism.new("Unknown SASL mechanism (#{mechanism})")
|
29
|
+
return false
|
36
30
|
end
|
37
31
|
|
38
32
|
extend mod
|
33
|
+
true
|
39
34
|
end
|
40
35
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
36
|
+
##
|
37
|
+
# Handle incoming nodes
|
38
|
+
# Cycle through possible mechanisms until we either
|
39
|
+
# run out of them or none work
|
40
|
+
def handle(node)
|
41
|
+
if node.element_name == 'failure'
|
42
|
+
if @mechanisms[@mechanism_idx += 1]
|
43
|
+
set_mechanism
|
44
|
+
authenticate
|
45
|
+
else
|
46
|
+
failure node
|
47
|
+
end
|
46
48
|
else
|
47
|
-
|
49
|
+
super
|
48
50
|
end
|
49
51
|
end
|
50
52
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
def failure(&callback)
|
56
|
-
@callbacks['failure'] = callback
|
53
|
+
protected
|
54
|
+
def failure(node = nil)
|
55
|
+
@failure.call SASLError.import(node)
|
57
56
|
end
|
58
57
|
|
59
|
-
|
58
|
+
##
|
59
|
+
# Base64 Encoder
|
60
60
|
def b64(str)
|
61
61
|
[str].pack('m').gsub(/\s/,'')
|
62
62
|
end
|
63
63
|
|
64
|
+
##
|
65
|
+
# Builds a standard auth node
|
64
66
|
def auth_node(mechanism, content = nil)
|
65
67
|
node = XMPPNode.new 'auth', content
|
66
68
|
node['xmlns'] = SASL_NS
|
@@ -68,16 +70,33 @@ module Stream # :nodoc:
|
|
68
70
|
node
|
69
71
|
end
|
70
72
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
73
|
+
##
|
74
|
+
# Respond to the <mechanisms> node sent by the server
|
75
|
+
def mechanisms
|
76
|
+
@mechanisms = @node.children
|
77
|
+
authenticate if set_mechanism
|
78
|
+
end
|
75
79
|
|
80
|
+
##
|
81
|
+
# Digest MD5 authentication
|
82
|
+
module DigestMD5 # :nodoc:
|
83
|
+
##
|
84
|
+
# Lets the server know we're going to try DigestMD5 authentication
|
76
85
|
def authenticate
|
77
86
|
@stream.send auth_node('DIGEST-MD5')
|
78
87
|
end
|
79
88
|
|
89
|
+
##
|
90
|
+
# Receive the challenge command.
|
91
|
+
def challenge
|
92
|
+
decode_challenge
|
93
|
+
respond
|
94
|
+
end
|
95
|
+
|
80
96
|
private
|
97
|
+
##
|
98
|
+
# Decodes digest strings 'foo=bar,baz="faz"'
|
99
|
+
# into {'foo' => 'bar', 'baz' => 'faz'}
|
81
100
|
def decode_challenge
|
82
101
|
text = @node.content.unpack('m').first
|
83
102
|
res = {}
|
@@ -92,12 +111,16 @@ module Stream # :nodoc:
|
|
92
111
|
@realm ||= res['realm']
|
93
112
|
end
|
94
113
|
|
114
|
+
##
|
115
|
+
# Builds the properly encoded challenge response
|
95
116
|
def generate_response
|
96
117
|
a1 = "#{d("#{@response[:username]}:#{@response[:realm]}:#{@pass}")}:#{@response[:nonce]}:#{@response[:cnonce]}"
|
97
118
|
a2 = "AUTHENTICATE:#{@response[:'digest-uri']}"
|
98
119
|
h("#{h(a1)}:#{@response[:nonce]}:#{@response[:nc]}:#{@response[:cnonce]}:#{@response[:qop]}:#{h(a2)}")
|
99
120
|
end
|
100
121
|
|
122
|
+
##
|
123
|
+
# Send challenge response
|
101
124
|
def respond
|
102
125
|
node = XMPPNode.new 'response'
|
103
126
|
node['xmlns'] = SASL_NS
|
@@ -121,6 +144,7 @@ module Stream # :nodoc:
|
|
121
144
|
LOG.debug "CH RESP TXT: #{@response.map { |k,v| "#{k}=#{v}" } * ','}"
|
122
145
|
|
123
146
|
# order is to simplify testing
|
147
|
+
# Ruby 1.9 eliminates the need for this with ordered hashes
|
124
148
|
order = [:nonce, :charset, :username, :realm, :cnonce, :nc, :qop, :'digest-uri', :response]
|
125
149
|
node.content = b64(order.map { |k| v = @response[k]; "#{k}=#{v}" } * ',')
|
126
150
|
end
|
@@ -143,7 +167,6 @@ module Stream # :nodoc:
|
|
143
167
|
@stream.send auth_node('ANONYMOUS', b64(@jid.node))
|
144
168
|
end
|
145
169
|
end #Anonymous
|
146
|
-
|
147
170
|
end #SASL
|
148
171
|
|
149
172
|
end #Stream
|
@@ -1,26 +1,15 @@
|
|
1
1
|
module Blather # :nodoc:
|
2
|
-
|
2
|
+
class Stream # :nodoc:
|
3
3
|
|
4
|
-
class Session # :nodoc:
|
4
|
+
class Session < StreamHandler # :nodoc:
|
5
5
|
def initialize(stream, to)
|
6
|
-
|
6
|
+
super stream
|
7
7
|
@to = to
|
8
|
-
@callbacks = {}
|
9
|
-
end
|
10
|
-
|
11
|
-
def success(&callback)
|
12
|
-
@callbacks[:success] = callback
|
13
|
-
end
|
14
|
-
|
15
|
-
def failure(&callback)
|
16
|
-
@callbacks[:failure] = callback
|
17
|
-
end
|
18
|
-
|
19
|
-
def receive(node)
|
20
|
-
@node = node
|
21
|
-
__send__(@node.element_name == 'iq' ? @node['type'] : @node.element_name)
|
22
8
|
end
|
23
9
|
|
10
|
+
private
|
11
|
+
##
|
12
|
+
# Send a start session command
|
24
13
|
def session
|
25
14
|
response = Stanza::Iq.new :set
|
26
15
|
response.to = @to
|
@@ -30,12 +19,16 @@ module Stream # :nodoc:
|
|
30
19
|
@stream.send response
|
31
20
|
end
|
32
21
|
|
22
|
+
##
|
23
|
+
# The server should respond with a <result> node if all is well
|
33
24
|
def result
|
34
|
-
|
25
|
+
success
|
35
26
|
end
|
36
27
|
|
28
|
+
##
|
29
|
+
# Server returned an error.
|
37
30
|
def error
|
38
|
-
|
31
|
+
failure StanzaError.import(@node)
|
39
32
|
end
|
40
33
|
end
|
41
34
|
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Blather # :nodoc:
|
2
|
+
class Stream # :nodoc:
|
3
|
+
|
4
|
+
class StreamHandler # :nodoc:
|
5
|
+
def on_success(&block); @success = block; end
|
6
|
+
def on_failure(&block); @failure = block; end
|
7
|
+
|
8
|
+
def initialize(stream)
|
9
|
+
@stream = stream
|
10
|
+
end
|
11
|
+
|
12
|
+
def handle(node)
|
13
|
+
@node = node
|
14
|
+
method = @node.element_name == 'iq' ? @node['type'] : @node.element_name
|
15
|
+
if self.respond_to?(method, true)
|
16
|
+
self.__send__ method
|
17
|
+
else
|
18
|
+
@failure.call UnknownResponse.new(@node)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
##
|
24
|
+
# Handle error response from the server
|
25
|
+
def error
|
26
|
+
failure
|
27
|
+
end
|
28
|
+
|
29
|
+
def success(message_back = nil)
|
30
|
+
@success.call message_back
|
31
|
+
end
|
32
|
+
|
33
|
+
def failure(err = nil)
|
34
|
+
@failure.call err
|
35
|
+
end
|
36
|
+
end #StreamHandler
|
37
|
+
|
38
|
+
end #Stream
|
39
|
+
end #Blather
|
data/lib/blather/stream/tls.rb
CHANGED
@@ -1,27 +1,31 @@
|
|
1
1
|
module Blather # :nodoc:
|
2
|
-
|
2
|
+
class Stream # :nodoc:
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
4
|
+
# TLS negotiation invovles 3 node types:
|
5
|
+
# * starttls -- Server asking for TLS to be started
|
6
|
+
# * proceed -- Server saying it's ready for a TLS connection to be started
|
7
|
+
# * failure -- Failed TLS negotiation. Failure results in a closed connection.
|
8
|
+
# so there's no message to pass back to the tream
|
9
|
+
class TLS < StreamHandler # :nodoc:
|
10
|
+
private
|
11
|
+
##
|
12
|
+
# After receiving <starttls> from the server send one
|
13
|
+
# back to let it know we're ready to start TLS
|
14
|
+
def starttls
|
15
|
+
@stream.send "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"
|
13
16
|
end
|
14
17
|
|
15
|
-
|
16
|
-
|
18
|
+
##
|
19
|
+
# Server's ready for TLS, so start it up
|
20
|
+
def proceed
|
21
|
+
@stream.start_tls
|
22
|
+
success
|
17
23
|
end
|
18
24
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
def receive(node)
|
24
|
-
@callbacks[node.element_name].call if @callbacks[node.element_name]
|
25
|
+
##
|
26
|
+
# Negotiations failed
|
27
|
+
def failure
|
28
|
+
super StreamError::TLSFailure.new
|
25
29
|
end
|
26
30
|
end #TLS
|
27
31
|
|
data/lib/blather/xmpp_node.rb
CHANGED
@@ -5,9 +5,11 @@ module Blather
|
|
5
5
|
# All XML classes subclass XMPPNode
|
6
6
|
# it allows the addition of helpers
|
7
7
|
class XMPPNode < XML::Node
|
8
|
+
BASE_NAMES = %w[presence message iq].freeze
|
9
|
+
|
8
10
|
@@registrations = {}
|
9
11
|
|
10
|
-
class_inheritable_accessor :
|
12
|
+
class_inheritable_accessor :ns,
|
11
13
|
:name
|
12
14
|
|
13
15
|
##
|
@@ -16,10 +18,10 @@ module Blather
|
|
16
18
|
# This registers a namespace that is used when looking
|
17
19
|
# up the class name of the object to instantiate when a new
|
18
20
|
# stanza is received
|
19
|
-
def self.register(name,
|
21
|
+
def self.register(name, ns = nil)
|
20
22
|
self.name = name.to_s
|
21
|
-
self.
|
22
|
-
@@registrations[[self.name, self.
|
23
|
+
self.ns = ns
|
24
|
+
@@registrations[[self.name, self.ns]] = self
|
23
25
|
end
|
24
26
|
|
25
27
|
##
|
@@ -34,7 +36,7 @@ module Blather
|
|
34
36
|
# of that class and imports all the <tt>node</tt>'s attributes
|
35
37
|
# and children into it.
|
36
38
|
def self.import(node)
|
37
|
-
klass = class_from_registration(node.element_name, node.
|
39
|
+
klass = class_from_registration(node.element_name, node.namespace)
|
38
40
|
if klass && klass != self
|
39
41
|
klass.import(node)
|
40
42
|
else
|
@@ -42,6 +44,72 @@ module Blather
|
|
42
44
|
end
|
43
45
|
end
|
44
46
|
|
47
|
+
##
|
48
|
+
# Provides an attribute reader helper. Default behavior is to
|
49
|
+
# conver the values of the attribute into a symbol. This can
|
50
|
+
# be turned off by passing <tt>:to_sym => false</tt>
|
51
|
+
#
|
52
|
+
# class Node
|
53
|
+
# attribute_reader :type
|
54
|
+
# attribute_reader :name, :to_sym => false
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# n = Node.new
|
58
|
+
# n.attributes[:type] = 'foo'
|
59
|
+
# n.type == :foo
|
60
|
+
# n.attributes[:name] = 'bar'
|
61
|
+
# n.name == 'bar'
|
62
|
+
def self.attribute_reader(*syms)
|
63
|
+
opts = syms.last.is_a?(Hash) ? syms.pop : {}
|
64
|
+
syms.flatten.each do |sym|
|
65
|
+
class_eval(<<-END, __FILE__, __LINE__)
|
66
|
+
def #{sym}
|
67
|
+
attributes[:#{sym}]#{".to_sym unless attributes[:#{sym}].blank?" unless opts[:to_sym] == false}
|
68
|
+
end
|
69
|
+
END
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# Provides an attribute writer helper.
|
75
|
+
#
|
76
|
+
# class Node
|
77
|
+
# attribute_writer :type
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
# n = Node.new
|
81
|
+
# n.type = 'foo'
|
82
|
+
# n.attributes[:type] == 'foo'
|
83
|
+
def self.attribute_writer(*syms)
|
84
|
+
syms.flatten.each do |sym|
|
85
|
+
next if sym.is_a?(Hash)
|
86
|
+
class_eval(<<-END, __FILE__, __LINE__)
|
87
|
+
def #{sym}=(value)
|
88
|
+
attributes[:#{sym}] = value
|
89
|
+
end
|
90
|
+
END
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
##
|
95
|
+
# Provides an attribute accessor helper combining
|
96
|
+
# <tt>attribute_reader</tt> and <tt>attribute_writer</tt>
|
97
|
+
#
|
98
|
+
# class Node
|
99
|
+
# attribute_accessor :type
|
100
|
+
# attribute_accessor :name, :to_sym => false
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
# n = Node.new
|
104
|
+
# n.type = 'foo'
|
105
|
+
# n.type == :foo
|
106
|
+
# n.name = 'bar'
|
107
|
+
# n.name == 'bar'
|
108
|
+
def self.attribute_accessor(*syms)
|
109
|
+
attribute_reader *syms
|
110
|
+
attribute_writer *syms
|
111
|
+
end
|
112
|
+
|
45
113
|
##
|
46
114
|
# Automatically sets the namespace registered by the subclass
|
47
115
|
def initialize(name = nil, content = nil)
|
@@ -49,7 +117,7 @@ module Blather
|
|
49
117
|
content = content.to_s if content
|
50
118
|
|
51
119
|
super name.to_s, content
|
52
|
-
self.
|
120
|
+
self.namespace = self.class.ns unless BASE_NAMES.include?(name.to_s)
|
53
121
|
end
|
54
122
|
|
55
123
|
##
|
@@ -58,19 +126,22 @@ module Blather
|
|
58
126
|
self.class.import self
|
59
127
|
end
|
60
128
|
|
61
|
-
def
|
62
|
-
|
129
|
+
def namespace=(ns)
|
130
|
+
if ns
|
131
|
+
ns = {nil => ns} unless ns.is_a?(Hash)
|
132
|
+
ns.each { |n| XML::Namespace.new self, *n }
|
133
|
+
end
|
63
134
|
end
|
64
135
|
|
65
|
-
def
|
66
|
-
|
136
|
+
def namespace(prefix = nil)
|
137
|
+
(ns = namespaces.find_by_prefix(prefix)) ? ns.href : nil
|
67
138
|
end
|
68
139
|
|
69
140
|
##
|
70
141
|
# Remove a child with the name and (optionally) namespace given
|
71
142
|
def remove_child(name, ns = nil)
|
72
143
|
name = name.to_s
|
73
|
-
self.
|
144
|
+
self.detect { |n| n.remove! if n.element_name == name && (!ns || n.namespace == ns) }
|
74
145
|
end
|
75
146
|
|
76
147
|
##
|
@@ -90,7 +161,9 @@ module Blather
|
|
90
161
|
##
|
91
162
|
# Create a copy
|
92
163
|
def copy(deep = true)
|
93
|
-
self.class.new
|
164
|
+
copy = self.class.new.inherit(self)
|
165
|
+
copy.element_name = self.element_name
|
166
|
+
copy
|
94
167
|
end
|
95
168
|
|
96
169
|
##
|
@@ -104,21 +177,22 @@ module Blather
|
|
104
177
|
##
|
105
178
|
# Inherit only <tt>stanza</tt>'s attributes
|
106
179
|
def inherit_attrs(attrs)
|
107
|
-
attrs.each { |a|
|
180
|
+
attrs.each { |a| attributes[a.name] = a.value }
|
108
181
|
self
|
109
182
|
end
|
110
183
|
|
111
184
|
##
|
112
|
-
# Turn itself into
|
113
|
-
def
|
185
|
+
# Turn itself into an XML string and remove all whitespace between nodes
|
186
|
+
def to_xml
|
114
187
|
# TODO: Fix this for HTML nodes (and any other that might require whitespace)
|
115
|
-
|
188
|
+
to_s.gsub(">\n<", '><')
|
116
189
|
end
|
117
190
|
|
118
191
|
##
|
119
192
|
# Override #find to work when a node isn't attached to a document
|
120
193
|
def find(what, nslist = nil)
|
121
|
-
|
194
|
+
what = what.to_s
|
195
|
+
(self.doc ? super(what, nslist) : select { |i| i.element_name == what })
|
122
196
|
end
|
123
197
|
end #XMPPNode
|
124
198
|
|