vpim 0.17 → 0.323

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,382 +0,0 @@
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
-
data/lib/vpim/rfc2425.rb~ DELETED
@@ -1,246 +0,0 @@
1
- =begin
2
- $Id: rfc2425.rb,v 1.10 2005/01/01 17:17:01 sam Exp $
3
-
4
- Copyright (C) 2005 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/vpim'
12
-
13
- module Vpim
14
- # Contains regular expression strings for the EBNF of RFC 2425.
15
- module Bnf #:nodoc:
16
-
17
- # 1*(ALPHA / DIGIT / "-")
18
- # Note: I think I can add A-Z here, and get rid of the "i" matches elsewhere.
19
- # Note: added '_' to allowed because its produced by Notes - X-LOTUS-CHILD_UID
20
- NAME = '[-a-z0-9_]+'
21
-
22
- # <"> <Any character except CTLs, DQUOTE> <">
23
- QSTR = '"([^"]*)"'
24
-
25
- # *<Any character except CTLs, DQUOTE, ";", ":", ",">
26
- PTEXT = '([^";:,]+)'
27
-
28
- # param-value = ptext / quoted-string
29
- PVALUE = "(?:#{QSTR}|#{PTEXT})"
30
-
31
- # param = name "=" param-value *("," param-value)
32
- # Note: v2.1 allows a type or encoding param-value to appear without the type=
33
- # or the encoding=. This is hideous, but we try and support it, if there
34
- # is no "=", then $2 will be "", and we will treat it as a v2.1 param.
35
- PARAM = ";(#{NAME})(=?)((?:#{PVALUE})?(?:,#{PVALUE})*)"
36
-
37
- # V3.0: contentline = [group "."] name *(";" param) ":" value
38
- # V2.1: contentline = *( group "." ) name *(";" param) ":" value
39
- #
40
- # We accept the V2.1 syntax for backwards compatibility.
41
- #LINE = "((?:#{NAME}\\.)*)?(#{NAME})([^:]*)\:(.*)"
42
- LINE = "^((?:#{NAME}\\.)*)?(#{NAME})((?:#{PARAM})*):(.*)$"
43
-
44
- # date = date-fullyear ["-"] date-month ["-"] date-mday
45
- # date-fullyear = 4 DIGIT
46
- # date-month = 2 DIGIT
47
- # date-mday = 2 DIGIT
48
- DATE = '(\d\d\d\d)-?(\d\d)-?(\d\d)'
49
-
50
- # time = time-hour [":"] time-minute [":"] time-second [time-secfrac] [time-zone]
51
- # time-hour = 2 DIGIT
52
- # time-minute = 2 DIGIT
53
- # time-second = 2 DIGIT
54
- # time-secfrac = "," 1*DIGIT
55
- # time-zone = "Z" / time-numzone
56
- # time-numzome = sign time-hour [":"] time-minute
57
- TIME = '(\d\d):?(\d\d):?(\d\d)(\.\d+)?(Z|[-+]\d\d:?\d\d)?'
58
- end
59
- end
60
-
61
- module Vpim
62
- # Split on \r\n or \n to get the lines, unfold continued lines (they
63
- # start with ' ' or \t), and return the array of unfolded lines.
64
- #
65
- # This also supports the (invalid) encoding convention of allowing empty
66
- # lines to be inserted for readability - it does this by dropping zero-length
67
- # lines.
68
- def Vpim.unfold(card) #:nodoc:
69
- unfolded = []
70
-
71
- card.each do |line|
72
- line.chomp!
73
- # If it's a continuation line, add it to the last.
74
- # If it's an empty line, drop it from the input.
75
- if( line =~ /^[ \t]/ )
76
- unfolded[-1] << line[1, line.size-1]
77
- elsif( line =~ /^$/ )
78
- else
79
- unfolded << line
80
- end
81
- end
82
-
83
- unfolded
84
- end
85
-
86
- # Convert a +sep+-seperated list of values into an array of values.
87
- def Vpim.decode_list(value, sep = ',') # :nodoc:
88
- list = []
89
-
90
- value.each(sep) {
91
- |item|
92
- list << yield(item) unless item =~ %r{^\s*#{sep}?$}
93
- }
94
- list
95
- end
96
-
97
- # Convert a RFC 2425 date into an array of [year, month, day].
98
- def Vpim.decode_date(v) # :nodoc:
99
- unless v =~ %r{\s*#{Bnf::DATE}\s*}
100
- raise Vpim::InvalidEncodingError, "date not valid (#{v})"
101
- end
102
- [$1.to_i, $2.to_i, $3.to_i]
103
- end
104
-
105
- # Note in the following the RFC2425 allows yyyy-mm-ddThh:mm:ss, but RFC2445
106
- # does not. I choose to encode to the subset that is valid for both.
107
-
108
- # Encode a Date object as "yyyymmdd".
109
- def Vpim.encode_date(d) # :nodoc:
110
- "%0.4d%0.2d%0.2d" % [ d.year, d.mon, d.day ]
111
- end
112
-
113
- # Encode a Date object as "yyyymmdd".
114
- def Vpim.encode_time(d) # :nodoc:
115
- "%0.4d%0.2d%0.2d" % [ d.year, d.mon, d.day ]
116
- end
117
-
118
- # Encode a Time or DateTime object as "yyyymmddThhmmss"
119
- def Vpim.encode_date_time(d) # :nodoc:
120
- "%0.4d%0.2d%0.2dT%0.2d%0.2d%0.2d" % [ d.year, d.mon, d.day, d.hour, d.min, d.sec ]
121
- end
122
-
123
- # Convert a RFC 2425 time into an array of [hour,min,sec,secfrac,timezone]
124
- def Vpim.decode_time(v) # :nodoc:
125
- unless match = %r{\s*#{Bnf::TIME}\s*}.match(v)
126
- raise Vpim::InvalidEncodingError, "time not valid (#{v})"
127
- end
128
- hour, min, sec, secfrac, tz = match.to_a[1..5]
129
-
130
- [hour.to_i, min.to_i, sec.to_i, secfrac ? secfrac.to_f : 0, tz]
131
- end
132
-
133
- # Convert a RFC 2425 date-time into an array of [hour,min,sec,secfrac,timezone]
134
- def Vpim.decode_date_time(v) # :nodoc:
135
- unless match = %r{\s*#{Bnf::DATE}T#{Bnf::TIME}\s*}.match(v)
136
- raise Vpim::InvalidEncodingError, "date-time '#{v}' not valid"
137
- end
138
- year, month, day, hour, min, sec, secfrac, tz = match.to_a[1..8]
139
-
140
- [
141
- # date
142
- year.to_i, month.to_i, day.to_i,
143
- # time
144
- hour.to_i, min.to_i, sec.to_i, secfrac ? secfrac.to_f : 0, tz
145
- ]
146
- end
147
-
148
- # Vpim.decode_boolean
149
- #
150
- # float
151
- #
152
- # float_list
153
- #
154
- # integer
155
- #
156
- # integer_list
157
- #
158
- # text_list
159
-
160
- # Convert a RFC 2425 date-list into an array of dates.
161
- def Vpim.decode_date_list(v) # :nodoc:
162
- dates = Vpim.decode_list(v) { |date| Vpim.decode_date(date) }
163
- end
164
-
165
- # Convert a RFC 2425 time-list into an array of times.
166
- def Vpim.decode_time_list(v) # :nodoc:
167
- times = Vpim.decode_list(v) { |time| Vpim.decode_time(time) }
168
- end
169
-
170
- # Convert a RFC 2425 date-time-list into an array of date-times.
171
- def Vpim.decode_date_time_list(v) # :nodoc:
172
- datetimes = Vpim.decode_list(v) { |datetime| Vpim.decode_date_time(datetime) }
173
- end
174
-
175
- # Convert RFC 2425 text into a String.
176
- # \\ -> \
177
- # \n -> NL
178
- # \N -> NL
179
- # \, -> ,
180
- def Vpim.decode_text(v) # :nodoc:
181
- v.gsub(/\\[nN]/, "\n").gsub(/\\,/, ",").gsub(/\\\\/) { |m| "\\" }
182
- end
183
-
184
-
185
- # Unfold the lines in +card+, then return an array of one Field object per
186
- # line.
187
- def Vpim.decode(card) #:nodoc:
188
- content = Vpim.unfold(card).collect { |line| DirectoryInfo::Field.decode(line) }
189
- end
190
-
191
-
192
- # Expand an array of fields into its syntactic entities. Each entity is a sequence
193
- # of fields where the sequences is delimited by a BEGIN/END field. Since
194
- # BEGIN/END delimited entities can be nested, we build a tree. Each entry in
195
- # the array is either a Field or an array of entries (where each entry is
196
- # either a Field, or an array of entries...).
197
- def Vpim.expand(src) #:nodoc:
198
- # output array to expand the src to
199
- dst = []
200
- # stack used to track our nesting level, as we see begin/end we start a
201
- # new/finish the current entity, and push/pop that entity from the stack
202
- current = [ dst ]
203
-
204
- for f in src
205
- if f.name? 'begin'
206
- e = [ f ]
207
-
208
- current.last.push(e)
209
- current.push(e)
210
-
211
- elsif f.name? 'end'
212
- current.last.push(f)
213
-
214
- unless current.last.first.value? current.last.last.value
215
- raise "BEGIN/END mismatch (#{current.last.first.value} != #{current.last.last.value})"
216
- end
217
-
218
- current.pop
219
-
220
- else
221
- current.last.push(f)
222
- end
223
- end
224
-
225
- dst
226
- end
227
-
228
- # Split an array into an array of all the fields at the outer level, and
229
- # an array of all the inner arrays of fields. Return the array [outer,
230
- # inner].
231
- def Vpim.outer_inner(fields) #:nodoc:
232
- # seperate into the outer-level fields, and the arrays of component
233
- # fields
234
- outer = []
235
- inner = []
236
- fields.each do |line|
237
- case line
238
- when Array; inner << line
239
- else; outer << line
240
- end
241
- end
242
- return outer, inner
243
- end
244
-
245
- end
246
-