shingara-blather 0.4.9 → 0.4.14
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/README.md +15 -1
- data/lib/blather.rb +5 -0
- data/lib/blather/core_ext/nokogiri.rb +2 -0
- data/lib/blather/roster.rb +3 -3
- data/lib/blather/roster_item.rb +14 -3
- data/lib/blather/stanza/disco/disco_info.rb +20 -2
- data/lib/blather/stanza/disco/disco_items.rb +10 -1
- data/lib/blather/stanza/iq/command.rb +324 -0
- data/lib/blather/stanza/iq/vcard.rb +147 -0
- data/lib/blather/stanza/message.rb +49 -5
- data/lib/blather/stanza/presence/status.rb +11 -3
- data/lib/blather/stanza/pubsub.rb +9 -21
- data/lib/blather/stanza/x.rb +398 -0
- data/lib/blather/stream.rb +3 -2
- data/lib/blather/stream/parser.rb +6 -5
- data/lib/blather/xmpp_node.rb +1 -1
- data/spec/blather/roster_item_spec.rb +39 -1
- data/spec/blather/stanza/discos/disco_info_spec.rb +23 -0
- data/spec/blather/stanza/discos/disco_items_spec.rb +11 -0
- data/spec/blather/stanza/iq/command_spec.rb +206 -0
- data/spec/blather/stanza/iq/vcard_query_spec.rb +96 -0
- data/spec/blather/stanza/message_spec.rb +89 -14
- data/spec/blather/stanza/pubsub/retract_spec.rb +1 -1
- data/spec/blather/stanza/pubsub_spec.rb +0 -5
- data/spec/blather/stanza/x_spec.rb +235 -0
- data/spec/blather/stream/client_spec.rb +14 -1
- data/spec/blather/stream/parser_spec.rb +6 -0
- data/spec/blather/xmpp_node_spec.rb +4 -3
- data/spec/spec_helper.rb +28 -0
- metadata +35 -5
@@ -0,0 +1,147 @@
|
|
1
|
+
module Blather
|
2
|
+
class Stanza
|
3
|
+
class Iq
|
4
|
+
|
5
|
+
# # Vcard Stanza
|
6
|
+
#
|
7
|
+
# [XEP-0054 vcard-temp](http://xmpp.org/extensions/xep-0054.html)
|
8
|
+
#
|
9
|
+
# This is a base class for any vcard based Iq stanzas. It provides a base set
|
10
|
+
# of methods for working with vcard stanzas
|
11
|
+
#
|
12
|
+
# @example Retrieving One's vCard
|
13
|
+
# iq = Blather::Stanza::Iq::Vcard.new :get
|
14
|
+
# client.write_with_handler iq do |response|
|
15
|
+
# puts response.vcard
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# @example Updating One's vCard
|
19
|
+
# iq = Blather::Stanza::Iq::Vcard.new :set
|
20
|
+
# iq.vcard['NICKNAME'] = 'Romeo'
|
21
|
+
# client.write_with_handler iq do |response|
|
22
|
+
# puts response
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# @example Viewing Another User's vCard
|
26
|
+
# iq = Blather::Stanza::Iq::Vcard.new :get, 'mercutio@example.org'
|
27
|
+
# client.write_with_handler iq do |response|
|
28
|
+
# puts response.vcard
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# @handler :vcard
|
32
|
+
class Vcard < Iq
|
33
|
+
|
34
|
+
register :vcard, :vCard, 'vcard-temp'
|
35
|
+
|
36
|
+
# Overrides the parent method to ensure a vcard node is created
|
37
|
+
#
|
38
|
+
# @see Blather::Stanza::Iq.new
|
39
|
+
def self.new(type = nil, to = nil, id = nil)
|
40
|
+
node = super
|
41
|
+
node.vcard
|
42
|
+
node
|
43
|
+
end
|
44
|
+
|
45
|
+
# Overrides the parent method to ensure the current vcard node is destroyed
|
46
|
+
#
|
47
|
+
# @see Blather::Stanza::Iq#inherit
|
48
|
+
def inherit(node)
|
49
|
+
vcard.remove
|
50
|
+
super
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
# Find or create vcard node
|
55
|
+
#
|
56
|
+
# @return [Vcard::Vcard]
|
57
|
+
def vcard
|
58
|
+
Vcard.find_or_create self
|
59
|
+
end
|
60
|
+
|
61
|
+
# Replaces vcard node
|
62
|
+
#
|
63
|
+
# @param [Vcard::Vcard, XML::Node] info the stanza's new vcard node
|
64
|
+
#
|
65
|
+
# @return [Vcard::Vcard]
|
66
|
+
def vcard=(info)
|
67
|
+
vcard.remove
|
68
|
+
self << info
|
69
|
+
Vcard.find_or_create self
|
70
|
+
end
|
71
|
+
|
72
|
+
class Vcard < XMPPNode
|
73
|
+
|
74
|
+
VCARD_NS = 'vcard-temp'
|
75
|
+
|
76
|
+
# Create a new Vcard::Vcard object
|
77
|
+
#
|
78
|
+
# @param [XML::Node, nil] node a node to inherit from
|
79
|
+
#
|
80
|
+
# @return [Vcard::Vcard]
|
81
|
+
def self.new(node = nil)
|
82
|
+
new_node = super :vCard
|
83
|
+
new_node.namespace = VCARD_NS
|
84
|
+
new_node.inherit node if node
|
85
|
+
new_node
|
86
|
+
end
|
87
|
+
|
88
|
+
# Find or create vCard node in Vcard Iq and converts it to Vcard::Vcard
|
89
|
+
#
|
90
|
+
# @param [Vcard] parent a Vcard Iq where to find or create vCard
|
91
|
+
#
|
92
|
+
# @return [Vcard::Vcard]
|
93
|
+
def self.find_or_create(parent)
|
94
|
+
if found_vcard = parent.find_first('//ns:vCard', :ns => VCARD_NS)
|
95
|
+
vcard = self.new found_vcard
|
96
|
+
found_vcard.remove
|
97
|
+
else
|
98
|
+
vcard = self.new
|
99
|
+
end
|
100
|
+
parent << vcard
|
101
|
+
|
102
|
+
vcard
|
103
|
+
end
|
104
|
+
|
105
|
+
# Find the element's value by name
|
106
|
+
#
|
107
|
+
# @param [String] name the name of the element
|
108
|
+
#
|
109
|
+
# @return [String, nil]
|
110
|
+
def [](name)
|
111
|
+
name = name.split("/").map{|child| "ns:#{child}"}.join("/")
|
112
|
+
|
113
|
+
if elem = find_first(name, :ns => VCARD_NS)
|
114
|
+
elem.content
|
115
|
+
else
|
116
|
+
nil
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Set the element's value
|
121
|
+
#
|
122
|
+
# @param [String] name the name of the element
|
123
|
+
# @param [String, nil] value the new value of element
|
124
|
+
#
|
125
|
+
# @return [String, nil]
|
126
|
+
def []=(name, value)
|
127
|
+
elem = nil
|
128
|
+
parent = self
|
129
|
+
|
130
|
+
name.split("/").each do |child|
|
131
|
+
elem = parent.find_first("ns:#{child}", :ns => VCARD_NS)
|
132
|
+
unless elem
|
133
|
+
elem = XMPPNode.new(child, parent.document)
|
134
|
+
parent << elem
|
135
|
+
parent = elem
|
136
|
+
else
|
137
|
+
parent = elem
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
elem.content = value
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -159,6 +159,9 @@ class Stanza
|
|
159
159
|
class Message < Stanza
|
160
160
|
VALID_TYPES = [:chat, :error, :groupchat, :headline, :normal].freeze
|
161
161
|
|
162
|
+
VALID_CHAT_STATES = [:active, :composing, :gone, :inactive, :paused].freeze
|
163
|
+
CHAT_STATE_NS = 'http://jabber.org/protocol/chatstates'.freeze
|
164
|
+
|
162
165
|
HTML_NS = 'http://jabber.org/protocol/xhtml-im'.freeze
|
163
166
|
HTML_BODY_NS = 'http://www.w3.org/1999/xhtml'.freeze
|
164
167
|
|
@@ -172,7 +175,7 @@ class Stanza
|
|
172
175
|
klass = class_from_registration(e.element_name, ns)
|
173
176
|
end
|
174
177
|
|
175
|
-
if klass && klass != self
|
178
|
+
if klass && klass != self && klass != Blather::Stanza::X
|
176
179
|
klass.import(node)
|
177
180
|
else
|
178
181
|
new(node[:type]).inherit(node)
|
@@ -189,9 +192,18 @@ class Stanza
|
|
189
192
|
node.to = to
|
190
193
|
node.type = type
|
191
194
|
node.body = body
|
195
|
+
node.chat_state = :active if [:chat, :groupchat].include?(type)
|
192
196
|
node
|
193
197
|
end
|
194
198
|
|
199
|
+
# Overrides the parent method to ensure the current chat state is removed
|
200
|
+
#
|
201
|
+
# @see Blather::Stanza::Iq#inherit
|
202
|
+
def inherit(node)
|
203
|
+
xpath('ns:*', :ns => CHAT_STATE_NS).remove
|
204
|
+
super
|
205
|
+
end
|
206
|
+
|
195
207
|
# Check if the Message is of type :chat
|
196
208
|
#
|
197
209
|
# @return [true, false]
|
@@ -262,8 +274,9 @@ class Stanza
|
|
262
274
|
end
|
263
275
|
|
264
276
|
unless b = h.find_first('ns:body', :ns => HTML_BODY_NS)
|
265
|
-
|
277
|
+
b = XMPPNode.new('body', self.document)
|
266
278
|
b.namespace = HTML_BODY_NS
|
279
|
+
h << b
|
267
280
|
end
|
268
281
|
|
269
282
|
b
|
@@ -273,7 +286,7 @@ class Stanza
|
|
273
286
|
#
|
274
287
|
# @return [String]
|
275
288
|
def xhtml
|
276
|
-
self.xhtml_node.
|
289
|
+
self.xhtml_node.inner_html.strip
|
277
290
|
end
|
278
291
|
|
279
292
|
# Set the message xhtml
|
@@ -281,7 +294,7 @@ class Stanza
|
|
281
294
|
#
|
282
295
|
# @param [#to_s] valid xhtml
|
283
296
|
def xhtml=(xhtml_body)
|
284
|
-
self.xhtml_node.
|
297
|
+
self.xhtml_node.inner_html = Nokogiri::XML::DocumentFragment.parse(xhtml_body)
|
285
298
|
end
|
286
299
|
|
287
300
|
# Get the message subject
|
@@ -326,7 +339,38 @@ class Stanza
|
|
326
339
|
set_content_for :thread, thread
|
327
340
|
find_first('thread')[:parent] = parent
|
328
341
|
end
|
342
|
+
|
343
|
+
# Returns the message's x:data form child
|
344
|
+
def form
|
345
|
+
X.find_or_create self
|
346
|
+
end
|
347
|
+
|
348
|
+
# Get the message chat state
|
349
|
+
#
|
350
|
+
# @return [Symbol]
|
351
|
+
def chat_state
|
352
|
+
if (elem = find_first('ns:*', :ns => CHAT_STATE_NS)) && VALID_CHAT_STATES.include?(name = elem.name.to_sym)
|
353
|
+
name
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
# Set the message chat state
|
358
|
+
#
|
359
|
+
# @param [#to_s] chat_state the message chat state. Must be one of VALID_CHAT_STATES
|
360
|
+
def chat_state=(chat_state)
|
361
|
+
if chat_state && !VALID_CHAT_STATES.include?(chat_state.to_sym)
|
362
|
+
raise ArgumentError, "Invalid Chat State (#{chat_state}), use: #{VALID_CHAT_STATES*' '}"
|
363
|
+
end
|
364
|
+
|
365
|
+
xpath('ns:*', :ns => CHAT_STATE_NS).remove
|
366
|
+
|
367
|
+
if chat_state
|
368
|
+
state = XMPPNode.new(chat_state, self.document)
|
369
|
+
state.namespace = CHAT_STATE_NS
|
370
|
+
self << state
|
371
|
+
end
|
372
|
+
end
|
329
373
|
end
|
330
374
|
|
331
375
|
end
|
332
|
-
end
|
376
|
+
end
|
@@ -193,8 +193,9 @@ class Presence
|
|
193
193
|
set_content_for :status, message
|
194
194
|
end
|
195
195
|
|
196
|
-
# Compare status based on priority
|
197
|
-
#
|
196
|
+
# Compare status based on priority and state:
|
197
|
+
# unavailable status is always less valuable than others
|
198
|
+
# Raises an error if the JIDs aren't the same
|
198
199
|
#
|
199
200
|
# @param [Blather::Stanza::Presence::Status] o
|
200
201
|
# @return [true,false]
|
@@ -202,7 +203,14 @@ class Presence
|
|
202
203
|
unless self.from && o.from && self.from.stripped == o.from.stripped
|
203
204
|
raise ArgumentError, "Cannot compare status from different JIDs: #{[self.from, o.from].inspect}"
|
204
205
|
end
|
205
|
-
|
206
|
+
|
207
|
+
if (self.type.nil? && o.type.nil?) || (!self.type.nil? && !o.type.nil?)
|
208
|
+
self.priority <=> o.priority
|
209
|
+
elsif self.type.nil? && !o.type.nil?
|
210
|
+
1
|
211
|
+
elsif !self.type.nil? && o.type.nil?
|
212
|
+
-1
|
213
|
+
end
|
206
214
|
end
|
207
215
|
|
208
216
|
end #Status
|
@@ -65,8 +65,6 @@ class Stanza
|
|
65
65
|
# This fragment is found in many places throughout the pubsub spec
|
66
66
|
# This is a convenience class to attach methods to the node
|
67
67
|
class PubSubItem < XMPPNode
|
68
|
-
ATOM_NS = 'http://www.w3.org/2005/Atom'.freeze
|
69
|
-
|
70
68
|
# Create a new PubSubItem
|
71
69
|
#
|
72
70
|
# @param [String, nil] id the id of the stanza
|
@@ -96,32 +94,22 @@ class Stanza
|
|
96
94
|
|
97
95
|
# Get the item's payload
|
98
96
|
#
|
99
|
-
#
|
100
|
-
#
|
101
|
-
# @return [String, nil]
|
97
|
+
# @return [String, XMPPNode, nil]
|
102
98
|
def payload
|
103
|
-
|
99
|
+
children.empty? ? nil : children.to_s
|
104
100
|
end
|
105
101
|
|
106
102
|
# Set the item's payload
|
107
103
|
#
|
108
|
-
# @param [String, nil] payload the payload
|
104
|
+
# @param [String, XMPPNode, nil] payload the payload
|
109
105
|
def payload=(payload)
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
def entry
|
117
|
-
e = find_first('ns:entry', :ns => ATOM_NS) ||
|
118
|
-
find_first('entry', :ns => ATOM_NS)
|
119
|
-
|
120
|
-
unless e
|
121
|
-
self << (e = XMPPNode.new('entry', self.document))
|
122
|
-
e.namespace = ATOM_NS
|
106
|
+
children.map &:remove
|
107
|
+
return unless payload
|
108
|
+
if payload.is_a?(String)
|
109
|
+
self.content = payload
|
110
|
+
else
|
111
|
+
self << payload
|
123
112
|
end
|
124
|
-
e
|
125
113
|
end
|
126
114
|
end # PubSubItem
|
127
115
|
|
@@ -0,0 +1,398 @@
|
|
1
|
+
module Blather
|
2
|
+
class Stanza
|
3
|
+
# # X Stanza
|
4
|
+
#
|
5
|
+
# [XEP-0004 Data Forms](http://xmpp.org/extensions/xep-0004.html)
|
6
|
+
#
|
7
|
+
# Data Form node that allows for semi-structured data exchange
|
8
|
+
#
|
9
|
+
# @handler :x
|
10
|
+
class X < XMPPNode
|
11
|
+
register :x, 'jabber:x:data'
|
12
|
+
|
13
|
+
VALID_TYPES = [:cancel, :form, :result, :submit].freeze
|
14
|
+
|
15
|
+
# Create a new X node
|
16
|
+
# @param [:cancel, :form, :result, :submit, nil] type the x:form type
|
17
|
+
# @param [Array<Array, X::Field>, nil] fields a list of fields.
|
18
|
+
# These are passed directly to X::Field.new
|
19
|
+
# @return [X] a new X stanza
|
20
|
+
def self.new(type = nil, fields = [])
|
21
|
+
new_node = super :x
|
22
|
+
|
23
|
+
case type
|
24
|
+
when Nokogiri::XML::Node
|
25
|
+
new_node.inherit type
|
26
|
+
when Hash
|
27
|
+
new_node.type = type[:type]
|
28
|
+
new_node.fields = type[:fields]
|
29
|
+
else
|
30
|
+
new_node.type = type
|
31
|
+
new_node.fields = fields
|
32
|
+
end
|
33
|
+
new_node
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.find_or_create(parent)
|
37
|
+
if found_x = parent.find_first('//ns:x', :ns => self.registered_ns)
|
38
|
+
x = self.new found_x
|
39
|
+
found_x.remove
|
40
|
+
else
|
41
|
+
x = self.new
|
42
|
+
end
|
43
|
+
parent << x
|
44
|
+
x
|
45
|
+
end
|
46
|
+
|
47
|
+
# The Form's type
|
48
|
+
# @return [Symbol]
|
49
|
+
def type
|
50
|
+
read_attr :type, :to_sym
|
51
|
+
end
|
52
|
+
|
53
|
+
# Set the Form's type
|
54
|
+
# @param [:cancel, :form, :result, :submit] type the new type for the form
|
55
|
+
def type=(type)
|
56
|
+
if type && !VALID_TYPES.include?(type.to_sym)
|
57
|
+
raise ArgumentError, "Invalid Type (#{type}), use: #{VALID_TYPES*' '}"
|
58
|
+
end
|
59
|
+
write_attr :type, type
|
60
|
+
end
|
61
|
+
|
62
|
+
# List of field objects
|
63
|
+
# @return [Blather::Stanza::X::Field]
|
64
|
+
def fields
|
65
|
+
self.find('ns:field', :ns => self.class.registered_ns).map do |field|
|
66
|
+
Field.new(field)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Find a field by var
|
71
|
+
# @param var the var for the field you wish to find
|
72
|
+
def field(var)
|
73
|
+
fields.detect { |f| f.var == var }
|
74
|
+
end
|
75
|
+
|
76
|
+
# Add an array of fields to form
|
77
|
+
# @param fields the array of fields, passed directly to Field.new
|
78
|
+
def fields=(fields)
|
79
|
+
remove_children :field
|
80
|
+
[fields].flatten.each do |field|
|
81
|
+
self << (f = Field.new(field))
|
82
|
+
f.namespace = self.namespace
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Check if the x is of type :cancel
|
87
|
+
#
|
88
|
+
# @return [true, false]
|
89
|
+
def cancel?
|
90
|
+
self.type == :cancel
|
91
|
+
end
|
92
|
+
|
93
|
+
# Check if the x is of type :form
|
94
|
+
#
|
95
|
+
# @return [true, false]
|
96
|
+
def form?
|
97
|
+
self.type == :form
|
98
|
+
end
|
99
|
+
|
100
|
+
# Check if the x is of type :result
|
101
|
+
#
|
102
|
+
# @return [true, false]
|
103
|
+
def result?
|
104
|
+
self.type == :result
|
105
|
+
end
|
106
|
+
|
107
|
+
# Check if the x is of type :submit
|
108
|
+
#
|
109
|
+
# @return [true, false]
|
110
|
+
def submit?
|
111
|
+
self.type == :submit
|
112
|
+
end
|
113
|
+
|
114
|
+
# Retrieve the form's instructions
|
115
|
+
#
|
116
|
+
# @return [String]
|
117
|
+
def instructions
|
118
|
+
content_from 'ns:instructions', :ns => self.registered_ns
|
119
|
+
end
|
120
|
+
|
121
|
+
# Set the form's instructions
|
122
|
+
#
|
123
|
+
# @param [String] instructions the form's instructions
|
124
|
+
def instructions=(instructions)
|
125
|
+
self.remove_children :instructions
|
126
|
+
if instructions
|
127
|
+
self << (i = XMPPNode.new(:instructions, self.document))
|
128
|
+
i.namespace = self.namespace
|
129
|
+
i << instructions
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Retrieve the form's title
|
134
|
+
#
|
135
|
+
# @return [String]
|
136
|
+
def title
|
137
|
+
content_from 'ns:title', :ns => self.registered_ns
|
138
|
+
end
|
139
|
+
|
140
|
+
# Set the form's title
|
141
|
+
#
|
142
|
+
# @param [String] title the form's title
|
143
|
+
def title=(title)
|
144
|
+
self.remove_children :title
|
145
|
+
if title
|
146
|
+
self << (t = XMPPNode.new(:title))
|
147
|
+
t.namespace = self.namespace
|
148
|
+
t << title
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
class Field < XMPPNode
|
153
|
+
VALID_TYPES = [:boolean, :fixed, :hidden, :"jid-multi", :"jid-single", :"list-multi", :"list-single", :"text-multi", :"text-private", :"text-single"].freeze
|
154
|
+
|
155
|
+
# Create a new X Field
|
156
|
+
# @overload new(node)
|
157
|
+
# Imports the XML::Node to create a Field object
|
158
|
+
# @param [XML::Node] node the node object to import
|
159
|
+
# @overload new(opts = {})
|
160
|
+
# Creates a new Field using a hash of options
|
161
|
+
# @param [Hash] opts a hash of options
|
162
|
+
# @option opts [String] :var the variable for the field
|
163
|
+
# @option opts [:boolean, :fixed, :hidden, :"jid-multi", :"jid-single", :"list-multi", :"list-single", :"text-multi", :"text-private", :"text-single"] :type the type of the field
|
164
|
+
# @option opts [String] :label the label for the field
|
165
|
+
# @option [String, nil] :value the value for the field
|
166
|
+
# @option [String, nil] :description the description for the field
|
167
|
+
# @option [true, false, nil] :required the required flag for the field
|
168
|
+
# @param [Array<Array, X::Field::Option>, nil] :options a list of field options.
|
169
|
+
# These are passed directly to X::Field::Option.new
|
170
|
+
# @overload new(type, var = nil, label = nil)
|
171
|
+
# Create a new Field by name
|
172
|
+
# @param [String, nil] var the variable for the field
|
173
|
+
# @param [:boolean, :fixed, :hidden, :"jid-multi", :"jid-single", :"list-multi", :"list-single", :"text-multi", :"text-private", :"text-single"] type the type of the field
|
174
|
+
# @param [String, nil] label the label for the field
|
175
|
+
# @param [String, nil] value the value for the field
|
176
|
+
# @param [String, nil] description the description for the field
|
177
|
+
# @param [true, false, nil] required the required flag for the field
|
178
|
+
# @param [Array<Array, X::Field::Option>, nil] options a list of field options.
|
179
|
+
# These are passed directly to X::Field::Option.new
|
180
|
+
def self.new(var, type = nil, label = nil, value = nil, description = nil, required = false, options = [])
|
181
|
+
new_node = super :field
|
182
|
+
|
183
|
+
case var
|
184
|
+
when Nokogiri::XML::Node
|
185
|
+
new_node.inherit var
|
186
|
+
when Hash
|
187
|
+
new_node.var = var[:var]
|
188
|
+
new_node.type = var[:type]
|
189
|
+
new_node.label = var[:label]
|
190
|
+
new_node.value = var[:value]
|
191
|
+
new_node.desc = var[:description]
|
192
|
+
new_node.required = var[:required]
|
193
|
+
new_node.options = var[:options]
|
194
|
+
else
|
195
|
+
new_node.var = var
|
196
|
+
new_node.type = type
|
197
|
+
new_node.label = label
|
198
|
+
new_node.value = value
|
199
|
+
new_node.desc = description
|
200
|
+
new_node.required = required
|
201
|
+
new_node.options = options
|
202
|
+
end
|
203
|
+
new_node
|
204
|
+
end
|
205
|
+
|
206
|
+
# The Field's type
|
207
|
+
# @return [String]
|
208
|
+
def type
|
209
|
+
read_attr :type
|
210
|
+
end
|
211
|
+
|
212
|
+
# Set the Field's type
|
213
|
+
# @param [#to_sym] type the new type for the field
|
214
|
+
def type=(type)
|
215
|
+
if type && !VALID_TYPES.include?(type.to_sym)
|
216
|
+
raise ArgumentError, "Invalid Type (#{type}), use: #{VALID_TYPES*' '}"
|
217
|
+
end
|
218
|
+
write_attr :type, type
|
219
|
+
end
|
220
|
+
|
221
|
+
# The Field's var
|
222
|
+
# @return [String]
|
223
|
+
def var
|
224
|
+
read_attr :var
|
225
|
+
end
|
226
|
+
|
227
|
+
# Set the Field's var
|
228
|
+
# @param [String] var the new var for the field
|
229
|
+
def var=(var)
|
230
|
+
write_attr :var, var
|
231
|
+
end
|
232
|
+
|
233
|
+
# The Field's label
|
234
|
+
# @return [String]
|
235
|
+
def label
|
236
|
+
read_attr :label
|
237
|
+
end
|
238
|
+
|
239
|
+
# Set the Field's label
|
240
|
+
# @param [String] label the new label for the field
|
241
|
+
def label=(label)
|
242
|
+
write_attr :label, label
|
243
|
+
end
|
244
|
+
|
245
|
+
# Get the field's value
|
246
|
+
#
|
247
|
+
# @param [String]
|
248
|
+
def value
|
249
|
+
if self.namespace
|
250
|
+
content_from 'ns:value', :ns => self.namespace.href
|
251
|
+
else
|
252
|
+
content_from :value
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
# Set the field's value
|
257
|
+
#
|
258
|
+
# @param [String] value the field's value
|
259
|
+
def value=(value)
|
260
|
+
self.remove_children :value
|
261
|
+
if value
|
262
|
+
self << (v = XMPPNode.new(:value))
|
263
|
+
v.namespace = self.namespace
|
264
|
+
v << value
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
# Get the field's description
|
269
|
+
#
|
270
|
+
# @param [String]
|
271
|
+
def desc
|
272
|
+
if self.namespace
|
273
|
+
content_from 'ns:desc', :ns => self.namespace.href
|
274
|
+
else
|
275
|
+
content_from :desc
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
# Set the field's description
|
280
|
+
#
|
281
|
+
# @param [String] description the field's description
|
282
|
+
def desc=(description)
|
283
|
+
self.remove_children :desc
|
284
|
+
if description
|
285
|
+
self << (d = XMPPNode.new(:desc))
|
286
|
+
d.namespace = self.namespace
|
287
|
+
d << description
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
# Get the field's required flag
|
292
|
+
#
|
293
|
+
# @param [true, false]
|
294
|
+
def required?
|
295
|
+
!self.find_first('required').nil?
|
296
|
+
end
|
297
|
+
|
298
|
+
# Set the field's required flag
|
299
|
+
#
|
300
|
+
# @param [true, false] required the field's required flag
|
301
|
+
def required=(required)
|
302
|
+
self.remove_children(:required) unless required
|
303
|
+
self << XMPPNode.new(:required) if required
|
304
|
+
end
|
305
|
+
|
306
|
+
# Extract list of option objects
|
307
|
+
#
|
308
|
+
# @return [Blather::Stanza::X::Field::Option]
|
309
|
+
def options
|
310
|
+
self.find(:option).map { |f| Option.new(f) }
|
311
|
+
end
|
312
|
+
|
313
|
+
# Add an array of options to field
|
314
|
+
# @param options the array of options, passed directly to Option.new
|
315
|
+
def options=(options)
|
316
|
+
remove_children :option
|
317
|
+
if options
|
318
|
+
[options].flatten.each { |o| self << Option.new(o) }
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
# Compare two Field objects by type, var and label
|
323
|
+
# @param [X::Field] o the Field object to compare against
|
324
|
+
# @return [true, false]
|
325
|
+
def eql?(o)
|
326
|
+
raise "Cannot compare #{self.class} with #{o.class}" unless o.is_a?(self.class)
|
327
|
+
![:type, :var, :label, :desc, :required?, :value].detect { |m| o.send(m) != self.send(m) }
|
328
|
+
end
|
329
|
+
alias_method :==, :eql?
|
330
|
+
|
331
|
+
class Option < XMPPNode
|
332
|
+
# Create a new X Field Option
|
333
|
+
# @overload new(node)
|
334
|
+
# Imports the XML::Node to create a Field option object
|
335
|
+
# @param [XML::Node] node the node object to import
|
336
|
+
# @overload new(opts = {})
|
337
|
+
# Creates a new Field option using a hash of options
|
338
|
+
# @param [Hash] opts a hash of options
|
339
|
+
# @option opts [String] :value the value of the field option
|
340
|
+
# @option opts [String] :label the human readable label for the field option
|
341
|
+
# @overload new(value, label = nil)
|
342
|
+
# Create a new Field option by name
|
343
|
+
# @param [String] value the value of the field option
|
344
|
+
# @param [String, nil] label the human readable label for the field option
|
345
|
+
def self.new(value, label = nil)
|
346
|
+
new_node = super :option
|
347
|
+
|
348
|
+
case value
|
349
|
+
when Nokogiri::XML::Node
|
350
|
+
new_node.inherit value
|
351
|
+
when Hash
|
352
|
+
new_node.value = value[:value]
|
353
|
+
new_node.label = value[:label]
|
354
|
+
else
|
355
|
+
new_node.value = value
|
356
|
+
new_node.label = label
|
357
|
+
end
|
358
|
+
new_node
|
359
|
+
end
|
360
|
+
|
361
|
+
# The Field Option's value
|
362
|
+
# @return [String]
|
363
|
+
def value
|
364
|
+
if self.namespace
|
365
|
+
content_from 'ns:value', :ns => self.namespace.href
|
366
|
+
else
|
367
|
+
content_from :value
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
# Set the Field Option's value
|
372
|
+
# @param [String] value the new value for the field option
|
373
|
+
def value=(value)
|
374
|
+
self.remove_children :value
|
375
|
+
if value
|
376
|
+
self << (v = XMPPNode.new(:value))
|
377
|
+
v.namespace = self.namespace
|
378
|
+
v << value
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
# The Field Option's label
|
383
|
+
# @return [String]
|
384
|
+
def label
|
385
|
+
read_attr :label
|
386
|
+
end
|
387
|
+
|
388
|
+
# Set the Field Option's label
|
389
|
+
# @param [String] label the new label for the field option
|
390
|
+
def label=(label)
|
391
|
+
write_attr :label, label
|
392
|
+
end
|
393
|
+
end # Option
|
394
|
+
end # Field
|
395
|
+
end # X
|
396
|
+
|
397
|
+
end #Stanza
|
398
|
+
end
|