vpim 0.16 → 0.17

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.
@@ -1,7 +1,5 @@
1
1
  =begin
2
- $Id: vcard.rb,v 1.7 2005/02/04 21:09:34 sam Exp $
3
-
4
- Copyright (C) 2005 Sam Roberts
2
+ Copyright (C) 2006 Sam Roberts
5
3
 
6
4
  This library is free software; you can redistribute it and/or modify it
7
5
  under the same terms as the ruby language itself, see the file COPYING for
@@ -11,49 +9,67 @@
11
9
  require 'vpim/vcard'
12
10
 
13
11
  module Vpim
14
- module Maker
12
+ module Maker #:nodoc:
15
13
  # A helper class to assist in building a vCard.
16
14
  #
17
- # This idea is modelled after ruby 1.8's rss/maker classes. Perhaps all these methods
18
- # should be added to Vpim::Vcard?
15
+ # Examples:
16
+ # - link:ex_mkvcard.txt: example of creating a vCard
17
+ # - link:ex_cpvcard.txt: example of copying and them modifying a vCard
18
+ # - link:ex_mkv21vcard.txt: example of creating version 2.1 vCard
19
19
  class Vcard
20
- # Make a vCard for +full_name+.
20
+ # Make a vCard.
21
+ #
22
+ # Yields +maker+, a Vpim::Maker::Vcard which allows fields to be added to
23
+ # +card+, and returns +card+, a Vpim::Vcard.
21
24
  #
22
- # Yields +card+, a Vpim::Maker::Vcard to which fields can be added, and returns a Vpim::Vcard.
25
+ # If +card+ is nil or not provided a new Vpim::Vcard is created and the
26
+ # fields are added to it.
27
+ #
28
+ # Defaults:
29
+ # - vCards must have both an N: and an FN: field, #make2 will fail if there
30
+ # is no FN: field in the +card+ when your block is finished adding fields.
31
+ # - If there is an FN: field, but no N: field, N: will be set from the information
32
+ # in FN:, see Vcard::Name#preformatted for more information.
33
+ # - vCards must have a VERSION: field. If one does not exist when your block is
34
+ # is finished adding fields then it will be set to 3.0.
35
+ def Vcard.make2(card = Vpim::Vcard.create, &block) # :yields: maker
36
+ new(nil, card).make(&block)
37
+ end
38
+
39
+ # Deprecated, use #make2.
23
40
  #
24
- # Note that calling #add_name is required, all other fields are optional.
25
- def Vcard.make(full_name, &block) # :yields: +card+
26
- new(full_name).make(&block)
41
+ # If set, the FN: field will be set to +full_name+. Otherwise, FN: will
42
+ # be set from the values in #add_name.
43
+ def Vcard.make(full_name = nil, &block) # :yields: maker
44
+ new(full_name, Vpim::Vcard.create).make(&block)
27
45
  end
28
46
 
29
47
  def make # :nodoc:
30
48
  yield self
31
- if !@initialized_N
32
- raise Vpim::InvalidEncodingError, 'It is mandatory to have a N field, see #add_name.'
49
+ unless @card['N']
50
+ raise Vpim::InvalidEncodingError, 'It is mandatory to have a name field, see #add_name.'
51
+ end
52
+ unless @card['FN']
53
+ @card << Vpim::DirectoryInfo::Field.create('FN', Vpim::Vcard::Name.new(@card['N'], '').formatted)
54
+ end
55
+ unless @card['VERSION']
56
+ @card << Vpim::DirectoryInfo::Field.create('VERSION', "3.0")
33
57
  end
34
58
  @card
35
59
  end
36
60
 
37
61
  private
38
62
 
39
- def initialize(full_name) # :nodoc:
40
- @card = Vpim::Vcard::create
41
- @card << Vpim::DirectoryInfo::Field.create('FN', full_name )
42
- @initialized_N = false
43
- # pp @card
63
+ def initialize(full_name, card) # :nodoc:
64
+ @card = card || Vpim::Vcard::create
65
+ if full_name
66
+ @card << Vpim::DirectoryInfo::Field.create('FN', full_name )
67
+ end
44
68
  end
45
69
 
46
70
  public
47
71
 
48
- # Add an arbitrary Field, +field+.
49
- def add_field(field)
50
- @card << field
51
- end
52
-
53
- # Add a name field, N.
54
- #
55
- # Warning: This is the only mandatory field, besides the full name, which
56
- # is added from Vcard.make's +full_name+.
72
+ # Add a name field, N:.
57
73
  #
58
74
  # Attributes of N are:
59
75
  # - family: family name
@@ -64,6 +80,10 @@ module Vpim
64
80
  #
65
81
  # All attributes are optional.
66
82
  #
83
+ # Warning: This is the only mandatory field besides the full name, FN,
84
+ # but FN: can be set in #make, and if not set will be constucted as the
85
+ # string "#{prefix} #{given} #{additional} #{family}, #{suffix}".
86
+ #
67
87
  # FIXME: is it possible to deduce given/family from the full_name?
68
88
  #
69
89
  # FIXME: Each attribute can currently only have a single String value.
@@ -76,13 +96,23 @@ module Vpim
76
96
  'N',
77
97
  x.map { |s| s ? s.to_str : '' }
78
98
  )
79
- @initialized_N = true
80
99
  self
81
100
  end
82
101
 
83
- # Add a address field, ADR.
102
+ # Add a full name field, FN.
84
103
  #
85
- # Attributes of ADR that describe the address are:
104
+ # Normally the FN field value is derived from the N: field value, but
105
+ # it can be explicitly set.
106
+ def fullname=(fullname)
107
+ if @card.field('FN')
108
+ raise Vpim::InvalidEncodingError, "Not allowed to add more than one FN field to a vCard."
109
+ end
110
+ @card << Vpim::DirectoryInfo::Field.create( 'FN', fullname );
111
+ end
112
+
113
+ # Add a address field, ADR:.
114
+ #
115
+ # Attributes of ADR: that describe the address are:
86
116
  # - pobox: post office box
87
117
  # - extended: seldom used, its not clear what it is for
88
118
  # - street: street address, multiple components should be separated by a comma, ','
@@ -99,9 +129,9 @@ module Vpim
99
129
  # All attributes are optional. #location and #home can be set to arrays of
100
130
  # strings.
101
131
  #
102
- # TODO: Add #label to support LABEL.
132
+ # TODO - Add #label to support LABEL.
103
133
  #
104
- # FIXME: Need to escape specials in the String.
134
+ # FIXME - Need to escape specials in the String.
105
135
  def add_addr # :yield: adr
106
136
  x = Struct.new(
107
137
  :location, :preferred, :delivery,
@@ -124,12 +154,12 @@ module Vpim
124
154
  self
125
155
  end
126
156
 
127
- # Add a telephone number field, TEL.
157
+ # Add a telephone number field, TEL:.
128
158
  #
129
159
  # +number+ is supposed to be a "X.500 Telephone Number" according to RFC 2426, if you happen
130
160
  # to be familiar with that. Otherwise, anything that looks like a phone number should be OK.
131
161
  #
132
- # Attributes of TEL are:
162
+ # Attributes of TEL: are:
133
163
  # - location: home, work, msg, cell, car, pager - often used, can be set to other values
134
164
  # - preferred: true - often used, set if this is the preferred telephone number
135
165
  # - capability: voice,fax,video,bbs,modem,isdn,pcs - fax is useful, the others are rarely used
@@ -153,9 +183,9 @@ module Vpim
153
183
  self
154
184
  end
155
185
 
156
- # Add a email address field, EMAIL.
186
+ # Add a email address field, EMAIL:.
157
187
  #
158
- # Attributes of EMAIL are:
188
+ # Attributes of EMAIL: are:
159
189
  # - location: home, work - often used, can be set to other values
160
190
  # - preferred: true - often used, set if this is the preferred email address
161
191
  # - protocol: internet,x400 - internet is the default, set this for other kinds
@@ -179,12 +209,12 @@ module Vpim
179
209
  self
180
210
  end
181
211
 
182
- # Add a nickname field, NICKNAME.
212
+ # Add a nickname field, NICKNAME:.
183
213
  def nickname=(nickname)
184
214
  @card << Vpim::DirectoryInfo::Field.create( 'NICKNAME', nickname );
185
215
  end
186
216
 
187
- # Add a birthday field, BDAY.
217
+ # Add a birthday field, BDAY:.
188
218
  #
189
219
  # +birthday+ must be a time or date object.
190
220
  #
@@ -205,10 +235,10 @@ TODO - need text=() implemented in Field
205
235
  end
206
236
  =end
207
237
 
208
- # Add an instant-messaging/point of presence address field, IMPP. The address
238
+ # Add an instant-messaging/point of presence address field, IMPP:. The address
209
239
  # is a URL, with the syntax depending on the protocol.
210
240
  #
211
- # Attributes of IMPP are:
241
+ # Attributes of IMPP: are:
212
242
  # - preferred: true - set if this is the preferred address
213
243
  # - location: home, work, mobile - location of address
214
244
  # - purpose: personal,business - purpose of communications
@@ -220,7 +250,7 @@ TODO - need text=() implemented in Field
220
250
  # the user to know the URL for their own address, hopefully not too much
221
251
  # of a burden.
222
252
  #
223
- # IMPP is defined in draft-jennings-impp-vcard-04.txt. It refers to the
253
+ # IMPP: is defined in draft-jennings-impp-vcard-04.txt. It refers to the
224
254
  # URI scheme of a number of messaging protocols, but doesn't give
225
255
  # references to all of them:
226
256
  # - "xmpp" indicates to use XMPP, draft-saintandre-xmpp-uri-06.txt
@@ -250,13 +280,13 @@ TODO - need text=() implemented in Field
250
280
  self
251
281
  end
252
282
 
253
- # Add an Apple style AIM account name, +xaim+ is an AIM screen name.
283
+ # Add an X-AIM: account name where +xaim+ is an AIM screen name.
254
284
  #
255
285
  # I don't know if this is conventional, or supported by anything other
256
286
  # than AddressBook.app, but an example is:
257
287
  # X-AIM;type=HOME;type=pref:exampleaccount
258
288
  #
259
- # Attributes of X-AIM are:
289
+ # Attributes of X-AIM: are:
260
290
  # - preferred: true - set if this is the preferred address
261
291
  # - location: home, work, mobile - location of address
262
292
  #
@@ -281,10 +311,10 @@ TODO - need text=() implemented in Field
281
311
  end
282
312
 
283
313
 
284
- # Add a photo field, PHOTO.
314
+ # Add a photo field, PHOTO:.
285
315
  #
286
- # Attributes of PHOTO are:
287
- # - image: set to image data to inclue inline
316
+ # Attributes of PHOTO: are:
317
+ # - image: set to image data to include inline
288
318
  # - link: set to the URL of the image data
289
319
  # - type: string identifying the image type, supposed to be an "IANA registered image format",
290
320
  # or a non-registered image format (usually these start with an x-)
@@ -331,6 +361,54 @@ TODO - need text=() implemented in Field
331
361
  self
332
362
  end
333
363
 
364
+ # Add a Field, +field+.
365
+ def add_field(field)
366
+ fieldname = field.name.upcase
367
+ case
368
+ when [ 'BEGIN', 'END' ].include?(fieldname)
369
+ raise Vpim::InvalidEncodingError, "Not allowed to manually add #{field.name} to a vCard."
370
+
371
+ when [ 'VERSION', 'N', 'FN' ].include?(fieldname)
372
+ if @card.field(fieldname)
373
+ raise Vpim::InvalidEncodingError, "Not allowed to add more than one #{fieldname} to a vCard."
374
+ end
375
+ @card << field
376
+
377
+ else
378
+ @card << field
379
+ end
380
+ end
381
+
382
+ # Copy the fields from +card+ into self using #add_field. If a block is
383
+ # provided, each Field from +card+ is yielded. The block should return a
384
+ # Field to add, or nil. The Field doesn't have to be the one yielded,
385
+ # allowing the field to be copied and modified (see Field#copy) before adding, or
386
+ # not added at all if the block yields nil.
387
+ #
388
+ # The vCard fields BEGIN: and END: aren't copied, and VERSION:, N:, and FN: are copied
389
+ # only if the card doesn't have them already.
390
+ def copy(card) # :yields: Field
391
+ card.each do |field|
392
+ fieldname = field.name.upcase
393
+ case
394
+ when [ 'BEGIN', 'END' ].include?(fieldname)
395
+ # Never copy these
396
+
397
+ when [ 'VERSION', 'N', 'FN' ].include?(fieldname) && @card.field(fieldname)
398
+ # Copy these only if they don't already exist.
399
+
400
+ else
401
+ if block_given?
402
+ field = yield field
403
+ end
404
+
405
+ if field
406
+ add_field(field)
407
+ end
408
+ end
409
+ end
410
+ end
411
+
334
412
  end
335
413
  end
336
414
  end
@@ -0,0 +1,382 @@
1
+ =begin
2
+ $Id: vcard.rb,v 1.7 2005/02/04 21:09:34 sam Exp $
3
+
4
+ Copyright (C) 2006 Sam Roberts
5
+
6
+ This library is free software; you can redistribute it and/or modify it
7
+ under the same terms as the ruby language itself, see the file COPYING for
8
+ details.
9
+ =end
10
+
11
+ require 'vpim/vcard'
12
+
13
+ module Vpim
14
+ module Maker
15
+ # A helper class to assist in building a vCard.
16
+ #
17
+ # Examples:
18
+ # - link:ex_mkvcard.txt: an example of making a vCard
19
+ # - link:ex_cpvcard.txt: an example of copying and modifying a vCard
20
+ #
21
+ # Note - The Maker module is modelled after rss/maker, but if a Vcard was
22
+ # mutable these methods could be added to Vpim::Vcard. Hm.
23
+ class Vcard
24
+ # Make a vCard for +full_name+.
25
+ #
26
+ # Yields +card+, a Vpim::Maker::Vcard to which fields can be added, and returns a Vpim::Vcard.
27
+ #
28
+ # Note that calling #add_name is required, all other fields are optional.
29
+ def Vcard.make(full_name, &block) # :yields: +card+
30
+ new(full_name).make(&block)
31
+ end
32
+
33
+ def make # :nodoc:
34
+ yield self
35
+ unless @card['N']
36
+ raise Vpim::InvalidEncodingError, 'It is mandatory to have a name field, see #add_name.'
37
+ end
38
+ @card
39
+ end
40
+
41
+ private
42
+
43
+ def initialize(full_name) # :nodoc:
44
+ @card = Vpim::Vcard::create
45
+ @card << Vpim::DirectoryInfo::Field.create('FN', full_name )
46
+ # pp @card
47
+ end
48
+
49
+ public
50
+
51
+ # Add an arbitrary Field, +field+.
52
+ def add_field(field)
53
+ fieldname = field.name.upcase
54
+ case
55
+ when [ 'BEGIN', 'END', 'VERSION' ].include?(fieldname)
56
+ raise Vpim::InvalidEncodingError, "Not allowed to manually add #{field.name} to a vCard."
57
+
58
+ when [ 'N', 'FN' ].include?(fieldname)
59
+ if @card.field(fieldname)
60
+ raise Vpim::InvalidEncodingError, "Not allowed to add more than one #{fieldname} to a vCard."
61
+ end
62
+ @card << field
63
+
64
+ else
65
+ @card << field
66
+ end
67
+ end
68
+
69
+ # Copy the fields from +card+ into self using #add_field. If a block is
70
+ # provided, each Field from +card+ is yielded. The block should return a
71
+ # Field to add, or nil. The Field doesn't have to be the one yielded,
72
+ # this allows modified fields to be copied in (use Field#copy), or fields
73
+ # to be filtered out (if the block yields nil).
74
+ #
75
+ # The vCard fields BEGIN, END, VERSION aren't copied, and N and FN aren't copied
76
+ # if the target already has them
77
+ def copy(card) # :yields: Field
78
+ card.each do |field|
79
+ fieldname = field.name.upcase
80
+ case
81
+ when [ 'BEGIN', 'END', 'VERSION' ].include?(fieldname)
82
+ # Never copy these
83
+
84
+ when [ 'N', 'FN' ].include?(fieldname) && @card.field(fieldname)
85
+ # Copy these only if they don't already exist.
86
+
87
+ else
88
+ if block_given?
89
+ field = yield field
90
+ end
91
+
92
+ if field
93
+ add_field(field)
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ # Add a name field, N.
100
+ #
101
+ # Warning: This is the only mandatory field, besides the full name, which
102
+ # is added from Vcard.make's +full_name+.
103
+ #
104
+ # Attributes of N are:
105
+ # - family: family name
106
+ # - given: given name
107
+ # - additional: additional names
108
+ # - prefix: such as "Ms." or "Dr."
109
+ # - suffix: such as "BFA", or "Sensei"
110
+ #
111
+ # All attributes are optional.
112
+ #
113
+ # FIXME: is it possible to deduce given/family from the full_name?
114
+ #
115
+ # FIXME: Each attribute can currently only have a single String value.
116
+ #
117
+ # FIXME: Need to escape specials in the String.
118
+ def add_name # :yield: n
119
+ x = Struct.new(:family, :given, :additional, :prefix, :suffix).new
120
+ yield x
121
+ @card << Vpim::DirectoryInfo::Field.create(
122
+ 'N',
123
+ x.map { |s| s ? s.to_str : '' }
124
+ )
125
+ self
126
+ end
127
+
128
+ # Add a address field, ADR.
129
+ #
130
+ # Attributes of ADR that describe the address are:
131
+ # - pobox: post office box
132
+ # - extended: seldom used, its not clear what it is for
133
+ # - street: street address, multiple components should be separated by a comma, ','
134
+ # - locality: usually the city
135
+ # - region: usually the province or state
136
+ # - postalcode: postal code
137
+ # - country: country name, no standard for country naming is specified
138
+ #
139
+ # Attributes that describe how the address is used, and customary values, are:
140
+ # - location: home, work - often used, can be set to other values
141
+ # - preferred: true - often used, set if this is the preferred address
142
+ # - delivery: postal, parcel, dom (domestic), intl (international) - rarely used
143
+ #
144
+ # All attributes are optional. #location and #home can be set to arrays of
145
+ # strings.
146
+ #
147
+ # TODO: Add #label to support LABEL.
148
+ #
149
+ # FIXME: Need to escape specials in the String.
150
+ def add_addr # :yield: adr
151
+ x = Struct.new(
152
+ :location, :preferred, :delivery,
153
+ :pobox, :extended, :street, :locality, :region, :postalcode, :country
154
+ ).new
155
+ yield x
156
+
157
+ values = x.to_a[3, 7].map { |s| s ? s.to_str : '' }
158
+
159
+ # All these attributes go into the TYPE parameter.
160
+ params = [ x[:location], x[:delivery] ]
161
+ params << 'pref' if x[:preferred]
162
+ params = params.flatten.uniq.compact.map { |s| s.to_str }
163
+
164
+ paramshash = {}
165
+
166
+ paramshash['type'] = params if params.first
167
+
168
+ @card << Vpim::DirectoryInfo::Field.create( 'ADR', values, paramshash)
169
+ self
170
+ end
171
+
172
+ # Add a telephone number field, TEL.
173
+ #
174
+ # +number+ is supposed to be a "X.500 Telephone Number" according to RFC 2426, if you happen
175
+ # to be familiar with that. Otherwise, anything that looks like a phone number should be OK.
176
+ #
177
+ # Attributes of TEL are:
178
+ # - location: home, work, msg, cell, car, pager - often used, can be set to other values
179
+ # - preferred: true - often used, set if this is the preferred telephone number
180
+ # - capability: voice,fax,video,bbs,modem,isdn,pcs - fax is useful, the others are rarely used
181
+ #
182
+ # All attributes are optional, and so is the block.
183
+ def add_tel(number) # :yield: tel
184
+ params = {}
185
+ if block_given?
186
+ x = Struct.new( :location, :preferred, :capability ).new
187
+
188
+ yield x
189
+
190
+ x[:preferred] = 'pref' if x[:preferred]
191
+
192
+ types = x.to_a.flatten.uniq.compact.map { |s| s.to_str }
193
+
194
+ params['type'] = types if types.first
195
+ end
196
+
197
+ @card << Vpim::DirectoryInfo::Field.create( 'TEL', number, params)
198
+ self
199
+ end
200
+
201
+ # Add a email address field, EMAIL.
202
+ #
203
+ # Attributes of EMAIL are:
204
+ # - location: home, work - often used, can be set to other values
205
+ # - preferred: true - often used, set if this is the preferred email address
206
+ # - protocol: internet,x400 - internet is the default, set this for other kinds
207
+ #
208
+ # All attributes are optional, and so is the block.
209
+ def add_email(email) # :yield: email
210
+ params = {}
211
+ if block_given?
212
+ x = Struct.new( :location, :preferred, :protocol ).new
213
+
214
+ yield x
215
+
216
+ x[:preferred] = 'pref' if x[:preferred]
217
+
218
+ types = x.to_a.flatten.uniq.compact.map { |s| s.to_str }
219
+
220
+ params['type'] = types if types.first
221
+ end
222
+
223
+ @card << Vpim::DirectoryInfo::Field.create( 'EMAIL', email, params)
224
+ self
225
+ end
226
+
227
+ # Add a nickname field, NICKNAME.
228
+ def nickname=(nickname)
229
+ @card << Vpim::DirectoryInfo::Field.create( 'NICKNAME', nickname );
230
+ end
231
+
232
+ # Add a birthday field, BDAY.
233
+ #
234
+ # +birthday+ must be a time or date object.
235
+ #
236
+ # Warning: It may confuse both humans and software if you add multiple
237
+ # birthdays.
238
+ def birthday=(birthday)
239
+ if !birthday.respond_to? :month
240
+ raise Vpim::InvalidEncodingError, 'birthday doesn\'t have #month, so it is not a date or time object.'
241
+ end
242
+ @card << Vpim::DirectoryInfo::Field.create( 'BDAY', birthday );
243
+ end
244
+ =begin
245
+ TODO - need text=() implemented in Field
246
+
247
+ # Add a note field, NOTE. It can contain newlines, they will be escaped.
248
+ def note=(note)
249
+ @card << Vpim::DirectoryInfo::Field.create( 'NOTE', note );
250
+ end
251
+ =end
252
+
253
+ # Add an instant-messaging/point of presence address field, IMPP. The address
254
+ # is a URL, with the syntax depending on the protocol.
255
+ #
256
+ # Attributes of IMPP are:
257
+ # - preferred: true - set if this is the preferred address
258
+ # - location: home, work, mobile - location of address
259
+ # - purpose: personal,business - purpose of communications
260
+ #
261
+ # All attributes are optional, and so is the block.
262
+ #
263
+ # The URL syntaxes for the messaging schemes is fairly complicated, so I
264
+ # don't try and build the URLs here, maybe in the future. This forces
265
+ # the user to know the URL for their own address, hopefully not too much
266
+ # of a burden.
267
+ #
268
+ # IMPP is defined in draft-jennings-impp-vcard-04.txt. It refers to the
269
+ # URI scheme of a number of messaging protocols, but doesn't give
270
+ # references to all of them:
271
+ # - "xmpp" indicates to use XMPP, draft-saintandre-xmpp-uri-06.txt
272
+ # - "irc" or "ircs" indicates to use IRC, draft-butcher-irc-url-04.txt
273
+ # - "sip" indicates to use SIP/SIMPLE, RFC 3261
274
+ # - "im" or "pres" indicates to use a CPIM or CPP gateway, RFC 3860 and RFC 3859
275
+ # - "ymsgr" indicates to use yahoo
276
+ # - "msn" might indicate to use Microsoft messenger
277
+ # - "aim" indicates to use AOL
278
+ #
279
+ def add_impp(url) # :yield: impp
280
+ params = {}
281
+
282
+ if block_given?
283
+ x = Struct.new( :location, :preferred, :purpose ).new
284
+
285
+ yield x
286
+
287
+ x[:preferred] = 'pref' if x[:preferred]
288
+
289
+ types = x.to_a.flatten.uniq.compact.map { |s| s.to_str }
290
+
291
+ params['type'] = types if types.first
292
+ end
293
+
294
+ @card << Vpim::DirectoryInfo::Field.create( 'IMPP', url, params)
295
+ self
296
+ end
297
+
298
+ # Add an Apple style AIM account name, +xaim+ is an AIM screen name.
299
+ #
300
+ # I don't know if this is conventional, or supported by anything other
301
+ # than AddressBook.app, but an example is:
302
+ # X-AIM;type=HOME;type=pref:exampleaccount
303
+ #
304
+ # Attributes of X-AIM are:
305
+ # - preferred: true - set if this is the preferred address
306
+ # - location: home, work, mobile - location of address
307
+ #
308
+ # All attributes are optional, and so is the block.
309
+ def add_x_aim(xaim) # :yield: xaim
310
+ params = {}
311
+
312
+ if block_given?
313
+ x = Struct.new( :location, :preferred ).new
314
+
315
+ yield x
316
+
317
+ x[:preferred] = 'pref' if x[:preferred]
318
+
319
+ types = x.to_a.flatten.uniq.compact.map { |s| s.to_str }
320
+
321
+ params['type'] = types if types.first
322
+ end
323
+
324
+ @card << Vpim::DirectoryInfo::Field.create( 'X-AIM', xaim, params)
325
+ self
326
+ end
327
+
328
+
329
+ # Add a photo field, PHOTO.
330
+ #
331
+ # Attributes of PHOTO are:
332
+ # - image: set to image data to inclue inline
333
+ # - link: set to the URL of the image data
334
+ # - type: string identifying the image type, supposed to be an "IANA registered image format",
335
+ # or a non-registered image format (usually these start with an x-)
336
+ #
337
+ # An error will be raised if neither image or link is set, or if both image
338
+ # and link is set.
339
+ #
340
+ # Setting type is optional for a link image, because either the URL, the
341
+ # image file extension, or a HTTP Content-Type may specify the type. If
342
+ # it's not a link, setting type is mandatory, though it can be set to an
343
+ # empty string, <code>''</code>, if the type is unknown.
344
+ #
345
+ # TODO - I'm not sure about this API. I'm thinking maybe it should be
346
+ # #add_photo(image, type), and that I should detect when the image is a
347
+ # URL, and make type mandatory if it wasn't a URL.
348
+ def add_photo # :yield: photo
349
+ x = Struct.new(:image, :link, :type).new
350
+ yield x
351
+ if x[:image] && x[:link]
352
+ raise Vpim::InvalidEncodingError, 'Image is not allowed to be both inline and a link.'
353
+ end
354
+
355
+ value = x[:image] || x[:link]
356
+
357
+ if !value
358
+ raise Vpim::InvalidEncodingError, 'A image link or inline data must be provided.'
359
+ end
360
+
361
+ params = {}
362
+
363
+ # Don't set type to the empty string.
364
+ params['type'] = x[:type] if( x[:type] && x[:type].length > 0 )
365
+
366
+ if x[:link]
367
+ params['value'] = 'uri'
368
+ else # it's inline, base-64 encode it
369
+ params['encoding'] = :b64
370
+ if !x[:type]
371
+ raise Vpim::InvalidEncodingError, 'Inline image data must have it\'s type set.'
372
+ end
373
+ end
374
+
375
+ @card << Vpim::DirectoryInfo::Field.create( 'PHOTO', value, params )
376
+ self
377
+ end
378
+
379
+ end
380
+ end
381
+ end
382
+