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.rb +35 -12
  10. data/lib/blather/client.rb +1 -1
  11. data/lib/blather/client/client.rb +19 -13
  12. data/lib/blather/client/dsl.rb +16 -0
  13. data/lib/blather/client/dsl/pubsub.rb +133 -0
  14. data/lib/blather/core_ext/active_support.rb +1 -117
  15. data/lib/blather/core_ext/active_support/inheritable_attributes.rb +117 -0
  16. data/lib/blather/core_ext/nokogiri.rb +35 -0
  17. data/lib/blather/errors.rb +3 -20
  18. data/lib/blather/errors/sasl_error.rb +3 -1
  19. data/lib/blather/errors/stanza_error.rb +10 -17
  20. data/lib/blather/errors/stream_error.rb +11 -14
  21. data/lib/blather/jid.rb +1 -0
  22. data/lib/blather/roster.rb +9 -0
  23. data/lib/blather/roster_item.rb +6 -1
  24. data/lib/blather/stanza.rb +35 -18
  25. data/lib/blather/stanza/disco.rb +7 -1
  26. data/lib/blather/stanza/disco/disco_info.rb +45 -33
  27. data/lib/blather/stanza/disco/disco_items.rb +32 -21
  28. data/lib/blather/stanza/iq.rb +13 -8
  29. data/lib/blather/stanza/iq/query.rb +16 -8
  30. data/lib/blather/stanza/iq/roster.rb +33 -22
  31. data/lib/blather/stanza/message.rb +20 -31
  32. data/lib/blather/stanza/presence.rb +3 -5
  33. data/lib/blather/stanza/presence/status.rb +13 -21
  34. data/lib/blather/stanza/presence/subscription.rb +11 -16
  35. data/lib/blather/stanza/pubsub.rb +63 -0
  36. data/lib/blather/stanza/pubsub/affiliations.rb +50 -0
  37. data/lib/blather/stanza/pubsub/create.rb +43 -0
  38. data/lib/blather/stanza/pubsub/errors.rb +9 -0
  39. data/lib/blather/stanza/pubsub/event.rb +77 -0
  40. data/lib/blather/stanza/pubsub/items.rb +63 -0
  41. data/lib/blather/stanza/pubsub/publish.rb +58 -0
  42. data/lib/blather/stanza/pubsub/retract.rb +53 -0
  43. data/lib/blather/stanza/pubsub/subscribe.rb +42 -0
  44. data/lib/blather/stanza/pubsub/subscription.rb +66 -0
  45. data/lib/blather/stanza/pubsub/subscriptions.rb +55 -0
  46. data/lib/blather/stanza/pubsub/unsubscribe.rb +42 -0
  47. data/lib/blather/stanza/pubsub_owner.rb +41 -0
  48. data/lib/blather/stanza/pubsub_owner/delete.rb +34 -0
  49. data/lib/blather/stanza/pubsub_owner/purge.rb +34 -0
  50. data/lib/blather/stream.rb +76 -168
  51. data/lib/blather/stream/client.rb +1 -2
  52. data/lib/blather/stream/component.rb +9 -5
  53. data/lib/blather/stream/features.rb +53 -0
  54. data/lib/blather/stream/features/resource.rb +63 -0
  55. data/lib/blather/stream/{sasl.rb → features/sasl.rb} +53 -52
  56. data/lib/blather/stream/features/session.rb +44 -0
  57. data/lib/blather/stream/features/tls.rb +28 -0
  58. data/lib/blather/stream/parser.rb +70 -46
  59. data/lib/blather/xmpp_node.rb +113 -52
  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 +54 -18
  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,120 +1,4 @@
1
- # Thanks to Rails ActiveSupport for everything in this file
2
-
3
- # Allows attributes to be shared within an inheritance hierarchy, but where each descendant gets a copy of
4
- # their parents' attributes, instead of just a pointer to the same. This means that the child can add elements
5
- # to, for example, an array without those additions being shared with either their parent, siblings, or
6
- # children, which is unlike the regular class-level attributes that are shared across the entire hierarchy.
7
- class Class # :nodoc:
8
- def class_inheritable_reader(*syms)
9
- syms.each do |sym|
10
- next if sym.is_a?(Hash)
11
- class_eval <<-EOS
12
- def self.#{sym}
13
- read_inheritable_attribute(:#{sym})
14
- end
15
-
16
- def #{sym}
17
- self.class.#{sym}
18
- end
19
- EOS
20
- end
21
- end
22
-
23
- def class_inheritable_writer(*syms)
24
- syms.each do |sym|
25
- class_eval <<-EOS
26
- def self.#{sym}=(obj)
27
- write_inheritable_attribute(:#{sym}, obj)
28
- end
29
- EOS
30
- end
31
- end
32
-
33
- def class_inheritable_array_writer(*syms)
34
- syms.each do |sym|
35
- class_eval <<-EOS
36
- def self.#{sym}=(obj)
37
- write_inheritable_array(:#{sym}, obj)
38
- end
39
- EOS
40
- end
41
- end
42
-
43
- def class_inheritable_hash_writer(*syms)
44
- syms.each do |sym|
45
- class_eval <<-EOS
46
- def self.#{sym}=(obj)
47
- write_inheritable_hash(:#{sym}, obj)
48
- end
49
- EOS
50
- end
51
- end
52
-
53
- def class_inheritable_accessor(*syms)
54
- class_inheritable_reader(*syms)
55
- class_inheritable_writer(*syms)
56
- end
57
-
58
- def class_inheritable_array(*syms)
59
- class_inheritable_reader(*syms)
60
- class_inheritable_array_writer(*syms)
61
- end
62
-
63
- def class_inheritable_hash(*syms)
64
- class_inheritable_reader(*syms)
65
- class_inheritable_hash_writer(*syms)
66
- end
67
-
68
- def inheritable_attributes
69
- @inheritable_attributes ||= EMPTY_INHERITABLE_ATTRIBUTES
70
- end
71
-
72
- def write_inheritable_attribute(key, value)
73
- if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
74
- @inheritable_attributes = {}
75
- end
76
- inheritable_attributes[key] = value
77
- end
78
-
79
- def write_inheritable_array(key, elements)
80
- write_inheritable_attribute(key, []) if read_inheritable_attribute(key).nil?
81
- write_inheritable_attribute(key, read_inheritable_attribute(key) + elements)
82
- end
83
-
84
- def write_inheritable_hash(key, hash)
85
- write_inheritable_attribute(key, {}) if read_inheritable_attribute(key).nil?
86
- write_inheritable_attribute(key, read_inheritable_attribute(key).merge(hash))
87
- end
88
-
89
- def read_inheritable_attribute(key)
90
- inheritable_attributes[key]
91
- end
92
-
93
- def reset_inheritable_attributes
94
- @inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
95
- end
96
-
97
- private
98
- # Prevent this constant from being created multiple times
99
- EMPTY_INHERITABLE_ATTRIBUTES = {}.freeze unless const_defined?(:EMPTY_INHERITABLE_ATTRIBUTES)
100
-
101
- def inherited_with_inheritable_attributes(child)
102
- inherited_without_inheritable_attributes(child) if respond_to?(:inherited_without_inheritable_attributes)
103
-
104
- if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
105
- new_inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
106
- else
107
- new_inheritable_attributes = inheritable_attributes.inject({}) do |memo, (key, value)|
108
- memo.update(key => value.duplicable? ? value.dup : value)
109
- end
110
- end
111
-
112
- child.instance_variable_set('@inheritable_attributes', new_inheritable_attributes)
113
- end
114
-
115
- alias inherited_without_inheritable_attributes inherited
116
- alias inherited inherited_with_inheritable_attributes
117
- end #Class
1
+ require File.join(File.dirname(__FILE__), 'active_support', 'inheritable_attributes')
118
2
 
119
3
  class Object # :nodoc:
120
4
  def duplicable?; true; end
@@ -0,0 +1,117 @@
1
+ # Thanks to Rails ActiveSupport for everything in this file
2
+
3
+ # Allows attributes to be shared within an inheritance hierarchy, but where each descendant gets a copy of
4
+ # their parents' attributes, instead of just a pointer to the same. This means that the child can add elements
5
+ # to, for example, an array without those additions being shared with either their parent, siblings, or
6
+ # children, which is unlike the regular class-level attributes that are shared across the entire hierarchy.
7
+ class Class # :nodoc:
8
+ def class_inheritable_reader(*syms)
9
+ syms.each do |sym|
10
+ next if sym.is_a?(Hash)
11
+ class_eval <<-EOS
12
+ def self.#{sym}
13
+ read_inheritable_attribute(:#{sym})
14
+ end
15
+
16
+ def #{sym}
17
+ self.class.#{sym}
18
+ end
19
+ EOS
20
+ end
21
+ end
22
+
23
+ def class_inheritable_writer(*syms)
24
+ syms.each do |sym|
25
+ class_eval <<-EOS
26
+ def self.#{sym}=(obj)
27
+ write_inheritable_attribute(:#{sym}, obj)
28
+ end
29
+ EOS
30
+ end
31
+ end
32
+
33
+ def class_inheritable_array_writer(*syms)
34
+ syms.each do |sym|
35
+ class_eval <<-EOS
36
+ def self.#{sym}=(obj)
37
+ write_inheritable_array(:#{sym}, obj)
38
+ end
39
+ EOS
40
+ end
41
+ end
42
+
43
+ def class_inheritable_hash_writer(*syms)
44
+ syms.each do |sym|
45
+ class_eval <<-EOS
46
+ def self.#{sym}=(obj)
47
+ write_inheritable_hash(:#{sym}, obj)
48
+ end
49
+ EOS
50
+ end
51
+ end
52
+
53
+ def class_inheritable_accessor(*syms)
54
+ class_inheritable_reader(*syms)
55
+ class_inheritable_writer(*syms)
56
+ end
57
+
58
+ def class_inheritable_array(*syms)
59
+ class_inheritable_reader(*syms)
60
+ class_inheritable_array_writer(*syms)
61
+ end
62
+
63
+ def class_inheritable_hash(*syms)
64
+ class_inheritable_reader(*syms)
65
+ class_inheritable_hash_writer(*syms)
66
+ end
67
+
68
+ def inheritable_attributes
69
+ @inheritable_attributes ||= EMPTY_INHERITABLE_ATTRIBUTES
70
+ end
71
+
72
+ def write_inheritable_attribute(key, value)
73
+ if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
74
+ @inheritable_attributes = {}
75
+ end
76
+ inheritable_attributes[key] = value
77
+ end
78
+
79
+ def write_inheritable_array(key, elements)
80
+ write_inheritable_attribute(key, []) if read_inheritable_attribute(key).nil?
81
+ write_inheritable_attribute(key, read_inheritable_attribute(key) + elements)
82
+ end
83
+
84
+ def write_inheritable_hash(key, hash)
85
+ write_inheritable_attribute(key, {}) if read_inheritable_attribute(key).nil?
86
+ write_inheritable_attribute(key, read_inheritable_attribute(key).merge(hash))
87
+ end
88
+
89
+ def read_inheritable_attribute(key)
90
+ inheritable_attributes[key]
91
+ end
92
+
93
+ def reset_inheritable_attributes
94
+ @inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
95
+ end
96
+
97
+ private
98
+ # Prevent this constant from being created multiple times
99
+ EMPTY_INHERITABLE_ATTRIBUTES = {}.freeze unless const_defined?(:EMPTY_INHERITABLE_ATTRIBUTES)
100
+
101
+ def inherited_with_inheritable_attributes(child)
102
+ inherited_without_inheritable_attributes(child) if respond_to?(:inherited_without_inheritable_attributes)
103
+
104
+ if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
105
+ new_inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
106
+ else
107
+ new_inheritable_attributes = inheritable_attributes.inject({}) do |memo, (key, value)|
108
+ memo.update(key => value.duplicable? ? value.dup : value)
109
+ end
110
+ end
111
+
112
+ child.instance_variable_set('@inheritable_attributes', new_inheritable_attributes)
113
+ end
114
+
115
+ alias inherited_without_inheritable_attributes inherited
116
+ alias inherited inherited_with_inheritable_attributes
117
+ end #Class
@@ -0,0 +1,35 @@
1
+ module Nokogiri # :nodoc:
2
+ module XML
3
+
4
+ class Node
5
+ alias_method :element_name, :name
6
+ alias_method :element_name=, :name=
7
+
8
+ alias_method :attr_set, :[]=
9
+ def []=(name, value)
10
+ name = name.to_s
11
+ value.nil? ? remove_attribute(name) : attr_set(name, value.to_s)
12
+ end
13
+
14
+ # alias_method :attr_get, :[]
15
+ # def [](name)
16
+ # attr_get name.to_s
17
+ # end
18
+
19
+ alias_method :nokogiri_xpath, :xpath
20
+ def xpath(*paths)
21
+ paths[0] = paths[0].to_s
22
+ if paths.size > 1 && (namespaces = paths.pop).is_a?(Hash)
23
+ paths << namespaces.inject({}) { |h,v| h[v[0].to_s] = v[1]; h }
24
+ end
25
+ nokogiri_xpath *paths
26
+ end
27
+ alias_method :find, :xpath
28
+
29
+ def find_first(*paths)
30
+ xpath(*paths).first
31
+ end
32
+ end
33
+
34
+ end #XML
35
+ end #Blather
@@ -34,31 +34,14 @@ module Blather
34
34
  end
35
35
  end
36
36
 
37
- ##
38
- # TLS negotiations broke down
39
- class TLSFailure < BlatherError
40
- register :tls_failure
41
- end
42
-
43
- class ParseWarning < BlatherError
44
- register :parse_warning
45
- attr_reader :libxml_error, :message
46
-
47
- def initialize(err)
48
- @libxml_error = err
49
- @message = err.to_s
50
- end
51
- end
52
-
53
37
  ##
54
38
  # Something bad happened while parsing the incoming stream
55
39
  class ParseError < BlatherError
56
40
  register :parse_error
57
- attr_reader :libxml_error, :message
41
+ attr_reader :message
58
42
 
59
- def initialize(err)
60
- @libxml_error = err
61
- @message = err.to_s
43
+ def initialize(msg)
44
+ @message = msg.to_s
62
45
  end
63
46
  end
64
47
  end
@@ -1,6 +1,8 @@
1
1
  module Blather
2
2
 
3
3
  class SASLError < BlatherError
4
+ SASL_ERR_NS = 'urn:ietf:params:xml:ns:xmpp-sasl'
5
+
4
6
  class_inheritable_accessor :err_name
5
7
  @@registrations = {}
6
8
 
@@ -16,7 +18,7 @@ class SASLError < BlatherError
16
18
  end
17
19
 
18
20
  def name
19
- @node.children.first.element_name.gsub('-', '_').to_sym if @node
21
+ @node.find_first('err_ns:*', :err_ns => SASL_ERR_NS).element_name.gsub('-', '_').to_sym if @node
20
22
  end
21
23
  end #SASLError
22
24
 
@@ -4,6 +4,7 @@ module Blather
4
4
  # Stanza errors
5
5
  # RFC3920 Section 9.3 (http://xmpp.org/rfcs/rfc3920.html#stanzas-error)
6
6
  class StanzaError < BlatherError
7
+ STANZA_ERR_NS = 'urn:ietf:params:xml:ns:xmpp-stanzas'
7
8
  VALID_TYPES = [:cancel, :continue, :modify, :auth, :wait]
8
9
 
9
10
  register :stanza_error
@@ -19,9 +20,9 @@ class StanzaError < BlatherError
19
20
 
20
21
  error_node = node.find_first '//*[local-name()="error"]'
21
22
 
22
- name = error_node.find_first('child::*[name()!="text"]', 'urn:ietf:params:xml:ns:xmpp-stanzas').element_name
23
+ name = error_node.find_first('child::*[name()!="text"]', STANZA_ERR_NS).element_name
23
24
  type = error_node['type']
24
- text = node.find_first '//err_ns:text', :err_ns => 'urn:ietf:params:xml:ns:xmpp-stanzas'
25
+ text = node.find_first 'descendant::*[name()="text"]', STANZA_ERR_NS
25
26
  text = text.content if text
26
27
 
27
28
  extras = error_node.find("descendant::*[name()!='text' and name()!='#{name}']").map { |n| n }
@@ -59,27 +60,19 @@ class StanzaError < BlatherError
59
60
  # Creates an XML node from the error
60
61
  def to_node
61
62
  node = self.original.reply
63
+ node.type = 'error'
64
+ node << (error_node = XMPPNode.new('error'))
62
65
 
63
- error_node = XMPPNode.new 'error'
64
- err = XMPPNode.new(@name)
66
+ error_node << (err = XMPPNode.new(@name, error_node.document))
65
67
  err.namespace = 'urn:ietf:params:xml:ns:xmpp-stanzas'
66
- error_node << err
67
68
 
68
69
  if self.text
69
- text = XMPPNode.new('text')
70
+ error_node << (text = XMPPNode.new('text', error_node.document))
70
71
  text.namespace = 'urn:ietf:params:xml:ns:xmpp-stanzas'
71
- text << self.text
72
- error_node << text
73
- end
74
-
75
- self.extras.each do |extra|
76
- extra_copy = extra.copy
77
- extra_copy.namespace = extra.namespace
78
- error_node << extra_copy
72
+ text.content = self.text
79
73
  end
80
74
 
81
- node << error_node
82
- node.type = 'error'
75
+ self.extras.each { |extra| error_node << extra.dup }
83
76
  node
84
77
  end
85
78
 
@@ -90,7 +83,7 @@ class StanzaError < BlatherError
90
83
  end
91
84
 
92
85
  def inspect # :nodoc:
93
- "Stanza Error (#{@name}): #{self.text}"
86
+ "Stanza Error (#{@name}): #{self.text} [#{self.extras}]"
94
87
  end
95
88
  alias_method :to_s, :inspect # :nodoc:
96
89
  end #StanzaError
@@ -4,6 +4,8 @@ module Blather
4
4
  # Stream Errors
5
5
  # RFC3920 Section 9.3 (http://xmpp.org/rfcs/rfc3920.html#streams-error-rules)
6
6
  class StreamError < BlatherError
7
+ STREAM_ERR_NS = 'urn:ietf:params:xml:ns:xmpp-streams'
8
+
7
9
  register :stream_error
8
10
 
9
11
  attr_reader :text, :extras
@@ -12,11 +14,12 @@ class StreamError < BlatherError
12
14
  # Factory method for instantiating the proper class
13
15
  # for the error
14
16
  def self.import(node)
15
- name = node.find_first('descendant::*[name()!="text"]', 'urn:ietf:params:xml:ns:xmpp-streams').element_name
16
- text = node.find_first '//err_ns:text', :err_ns => 'urn:ietf:params:xml:ns:xmpp-streams'
17
+ name = node.find_first('descendant::*[name()!="text"]', STREAM_ERR_NS).element_name
18
+
19
+ text = node.find_first 'descendant::*[name()="text"]', STREAM_ERR_NS
17
20
  text = text.content if text
18
21
 
19
- extras = node.find("descendant::*[name()!='text' and name()!='#{name}']").map { |n| n }
22
+ extras = node.find("descendant::*[namespace-uri()!='#{STREAM_ERR_NS}']").map { |n| n }
20
23
 
21
24
  self.new name, text, extras
22
25
  end
@@ -41,22 +44,16 @@ class StreamError < BlatherError
41
44
  def to_node
42
45
  node = XMPPNode.new('stream:error')
43
46
 
44
- err = XMPPNode.new(@name)
47
+ node << (err = XMPPNode.new(@name, node.document))
45
48
  err.namespace = 'urn:ietf:params:xml:ns:xmpp-streams'
46
- node << err
47
49
 
48
50
  if self.text
49
- text = XMPPNode.new('text')
51
+ node << (text = XMPPNode.new('text', node.document))
50
52
  text.namespace = 'urn:ietf:params:xml:ns:xmpp-streams'
51
- text << self.text
52
- node << text
53
+ text.content = self.text
53
54
  end
54
55
 
55
- self.extras.each do |extra|
56
- extra_copy = extra.copy
57
- extra_copy.namespace = extra.namespace
58
- node << extra_copy
59
- end
56
+ self.extras.each { |extra| node << extra.dup }
60
57
  node
61
58
  end
62
59
 
@@ -67,7 +64,7 @@ class StreamError < BlatherError
67
64
  end
68
65
 
69
66
  def inspect # :nodoc:
70
- "Stream Error (#{@name}): #{self.text}"
67
+ "Stream Error (#{@name}): #{self.text}" + (self.extras.empty? ? '' : " [#{self.extras}]")
71
68
  end
72
69
  alias_method :to_s, :inspect # :nodoc:
73
70
  end #StreamError