vpim 0.323 → 0.357
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/vpim.rb +1 -2
- data/lib/vpim/address.rb +181 -0
- data/lib/vpim/attachment.rb +102 -0
- data/lib/vpim/dirinfo.rb +12 -2
- data/lib/vpim/field.rb +26 -14
- data/lib/vpim/icalendar.rb +54 -200
- data/lib/vpim/maker/vcard.rb +2 -404
- data/lib/vpim/property/base.rb +22 -0
- data/lib/vpim/property/common.rb +26 -5
- data/lib/vpim/property/location.rb +10 -1
- data/lib/vpim/property/priority.rb +8 -0
- data/lib/vpim/property/recurrence.rb +47 -0
- data/lib/vpim/property/resources.rb +8 -0
- data/lib/vpim/rfc2425.rb +44 -7
- data/lib/vpim/rrule.rb +1 -1
- data/lib/vpim/vcard.rb +1230 -106
- data/lib/vpim/version.rb +3 -1
- data/lib/vpim/vevent.rb +2 -90
- data/lib/vpim/vjournal.rb +46 -0
- data/lib/vpim/vpim.rb +15 -73
- data/lib/vpim/vtodo.rb +82 -0
- metadata +7 -2
@@ -1,3 +1,11 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (C) 2006 Sam Roberts
|
3
|
+
|
4
|
+
This library is free software; you can redistribute it and/or modify it
|
5
|
+
under the same terms as the ruby language itself, see the file COPYING for
|
6
|
+
details.
|
7
|
+
=end
|
8
|
+
|
1
9
|
module Vpim
|
2
10
|
class Icalendar
|
3
11
|
module Property
|
@@ -10,7 +18,8 @@ module Vpim
|
|
10
18
|
|
11
19
|
# Array of Float, +[ latitude, longitude]+.
|
12
20
|
#
|
13
|
-
# North
|
21
|
+
# North of the equator is positive latitude, east of the meridian is
|
22
|
+
# positive longitude.
|
14
23
|
#
|
15
24
|
# See RFC2445 for more info... there are lots of special cases.
|
16
25
|
def geo
|
@@ -1,3 +1,11 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (C) 2006 Sam Roberts
|
3
|
+
|
4
|
+
This library is free software; you can redistribute it and/or modify it
|
5
|
+
under the same terms as the ruby language itself, see the file COPYING for
|
6
|
+
details.
|
7
|
+
=end
|
8
|
+
|
1
9
|
module Vpim
|
2
10
|
class Icalendar
|
3
11
|
module Property
|
@@ -0,0 +1,47 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (C) 2006 Sam Roberts
|
3
|
+
|
4
|
+
This library is free software; you can redistribute it and/or modify it
|
5
|
+
under the same terms as the ruby language itself, see the file COPYING for
|
6
|
+
details.
|
7
|
+
=end
|
8
|
+
|
9
|
+
module Vpim
|
10
|
+
class Icalendar
|
11
|
+
module Property
|
12
|
+
|
13
|
+
# Occurrences are calculated from DTSTART: and RRULE:. If there is not
|
14
|
+
# RRULE:, the component recurs only once, at the start time.
|
15
|
+
#
|
16
|
+
# Limitations:
|
17
|
+
#
|
18
|
+
# Only a single RRULE: is currently supported, this is the most common
|
19
|
+
# case.
|
20
|
+
#
|
21
|
+
# Implementation of multiple RRULE:s, and RDATE:, EXRULE:, and EXDATE: is
|
22
|
+
# on the todo list. Its not a very high priority, because I haven't seen
|
23
|
+
# calendars using the full range of recurrence features, and haven't
|
24
|
+
# received feedback from any users requesting these features. So, if you
|
25
|
+
# need it, contact me and implementation will get on the schedule.
|
26
|
+
module Recurrence
|
27
|
+
# The times this event occurs, as a Vpim::Rrule.
|
28
|
+
def occurences
|
29
|
+
start = dtstart
|
30
|
+
unless start
|
31
|
+
raise ArgumentError, "Components with no DTSTART: don't have occurences!"
|
32
|
+
end
|
33
|
+
Vpim::Rrule.new(start, propvalue('RRULE'))
|
34
|
+
end
|
35
|
+
|
36
|
+
# Check if this event overlaps with the time period later than or equal to +t0+, but
|
37
|
+
# earlier than +t1+.
|
38
|
+
def occurs_in?(t0, t1)
|
39
|
+
occurences.each_until(t1).detect { |t| tend = t + (duration || 0); tend > t0 }
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
|
@@ -1,3 +1,11 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (C) 2006 Sam Roberts
|
3
|
+
|
4
|
+
This library is free software; you can redistribute it and/or modify it
|
5
|
+
under the same terms as the ruby language itself, see the file COPYING for
|
6
|
+
details.
|
7
|
+
=end
|
8
|
+
|
1
9
|
module Vpim
|
2
10
|
class Icalendar
|
3
11
|
module Property
|
data/lib/vpim/rfc2425.rb
CHANGED
@@ -53,6 +53,9 @@ module Vpim
|
|
53
53
|
# time-zone = "Z" / time-numzone
|
54
54
|
# time-numzome = sign time-hour [":"] time-minute
|
55
55
|
TIME = '(\d\d):?(\d\d):?(\d\d)(\.\d+)?(Z|[-+]\d\d:?\d\d)?'
|
56
|
+
|
57
|
+
# integer = (["+"] / "-") 1*DIGIT
|
58
|
+
INTEGER = '[-+]?\d+'
|
56
59
|
end
|
57
60
|
end
|
58
61
|
|
@@ -148,14 +151,23 @@ module Vpim
|
|
148
151
|
# float
|
149
152
|
#
|
150
153
|
# float_list
|
151
|
-
|
152
|
-
|
154
|
+
=begin
|
155
|
+
=end
|
156
|
+
|
157
|
+
# Convert an RFC2425 INTEGER value into an Integer
|
158
|
+
def Vpim.decode_integer(v) # :nodoc:
|
159
|
+
unless match = %r{\s*#{Bnf::INTEGER}\s*}.match(v)
|
160
|
+
raise Vpim::InvalidEncodingError, "integer not valid (#{v})"
|
161
|
+
end
|
162
|
+
v.to_i
|
163
|
+
end
|
164
|
+
|
153
165
|
#
|
154
166
|
# integer_list
|
155
167
|
#
|
156
168
|
# text_list
|
157
169
|
|
158
|
-
# Convert a
|
170
|
+
# Convert a RFC2425 date-list into an array of dates.
|
159
171
|
def Vpim.decode_date_list(v) # :nodoc:
|
160
172
|
Vpim.decode_list(v) do |date|
|
161
173
|
date.strip!
|
@@ -191,24 +203,49 @@ module Vpim
|
|
191
203
|
# \N -> NL
|
192
204
|
# \, -> ,
|
193
205
|
# \; -> ;
|
206
|
+
#
|
207
|
+
# I've seen double-quote escaped by iCal.app. Hmm. Ok, if you aren't supposed
|
208
|
+
# to escape anything but the above, everything else is ambiguous, so I'll
|
209
|
+
# just support it.
|
194
210
|
def Vpim.decode_text(v) # :nodoc:
|
211
|
+
# FIXME - I think this should trim leading and trailing space
|
195
212
|
v.gsub(/\\(.)/) do
|
196
213
|
case $1
|
197
|
-
when '\\', ',', ';'
|
198
|
-
$1
|
199
214
|
when 'n', 'N'
|
200
215
|
"\n"
|
201
216
|
else
|
202
|
-
|
217
|
+
$1
|
203
218
|
end
|
204
219
|
end
|
205
220
|
end
|
221
|
+
|
222
|
+
def Vpim.encode_text(v) #:nodoc:
|
223
|
+
v.to_str.gsub(/([.\n])/) do
|
224
|
+
case $1
|
225
|
+
when "\n"
|
226
|
+
"\\n"
|
227
|
+
when "\\", ",", ";"
|
228
|
+
"\\#{$1}"
|
229
|
+
else
|
230
|
+
$1
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def Vpim.encode_text_list(v, sep = ",") #:nodoc:
|
236
|
+
v.to_ary.map{ |t| Vpim.encode_text(t) }.join(sep)
|
237
|
+
end
|
206
238
|
|
207
239
|
# Convert a +sep+-seperated list of TEXT values into an array of values.
|
208
240
|
def Vpim.decode_text_list(value, sep = ',') # :nodoc:
|
209
|
-
|
241
|
+
# Need to do in two stages, as best I can find.
|
242
|
+
list = value.scan(/([^#{sep}\\]*(?:\\.[^#{sep}\\]*)*)#{sep}/).map do |v|
|
210
243
|
Vpim.decode_text(v.first)
|
211
244
|
end
|
245
|
+
if value.match(/([^#{sep}\\]*(?:\\.[^#{sep}\\]*)*)$/)
|
246
|
+
list << $1
|
247
|
+
end
|
248
|
+
list
|
212
249
|
end
|
213
250
|
|
214
251
|
|
data/lib/vpim/rrule.rb
CHANGED
@@ -135,7 +135,7 @@ module Vpim
|
|
135
135
|
# less than +dountil+).
|
136
136
|
#
|
137
137
|
# Also, iteration will not currently continue past the limit of a Time
|
138
|
-
# object, which is some time in 2037 with the 32-bit time_t
|
138
|
+
# object, which is some time in 2037 with the 32-bit time_t common on
|
139
139
|
# most systems.
|
140
140
|
def each(dountil = nil) #:yield: ytime
|
141
141
|
t = @dtstart.clone
|
data/lib/vpim/vcard.rb
CHANGED
@@ -6,8 +6,12 @@
|
|
6
6
|
details.
|
7
7
|
=end
|
8
8
|
|
9
|
-
require 'vpim/dirinfo'
|
10
9
|
require 'vpim/vpim'
|
10
|
+
require 'vpim/attachment'
|
11
|
+
require 'vpim/dirinfo'
|
12
|
+
|
13
|
+
require 'open-uri'
|
14
|
+
require 'stringio'
|
11
15
|
|
12
16
|
module Vpim
|
13
17
|
# A vCard, a specialization of a directory info object.
|
@@ -16,9 +20,31 @@ module Vpim
|
|
16
20
|
# - RFC2426: vCard MIME Directory Profile (vCard 3.0)
|
17
21
|
# - RFC2425: A MIME Content-Type for Directory Information
|
18
22
|
#
|
19
|
-
# This implements vCard 3.0, but it is also capable of
|
23
|
+
# This implements vCard 3.0, but it is also capable of working with vCard 2.1
|
24
|
+
# if used with care.
|
25
|
+
#
|
26
|
+
# All line values can be accessed with Vcard#value, Vcard#values, or even by
|
27
|
+
# iterating through Vcard#lines. Line types that don't have specific support
|
28
|
+
# and non-standard line types ("X-MY-SPECIAL", for example) will be returned
|
29
|
+
# as a String, with any base64 or quoted-printable encoding removed.
|
30
|
+
#
|
31
|
+
# Specific support exists to return more useful values for the standard vCard
|
32
|
+
# types, where appropriate.
|
33
|
+
#
|
34
|
+
# The wrapper functions (#birthday, #nicknames, #emails, etc.) exist
|
35
|
+
# partially as an API convenience, and partially as a place to document
|
36
|
+
# the values returned for the more complex types, like PHOTO and EMAIL.
|
37
|
+
#
|
38
|
+
# For types that do not sensibly occur multiple times (like BDAY or GEO),
|
39
|
+
# sometimes a wrapper exists only to return a single line, using #value.
|
40
|
+
# However, if you find the need, you can still call #values to get all the
|
41
|
+
# lines, and both the singular and plural forms will eventually be
|
42
|
+
# implemented.
|
20
43
|
#
|
21
|
-
#
|
44
|
+
# If there is sufficient demand, specific support for vCard 2.1 could be
|
45
|
+
# implemented.
|
46
|
+
#
|
47
|
+
# For more information see:
|
22
48
|
# - link:rfc2426.txt: vCard MIME Directory Profile (vCard 3.0)
|
23
49
|
# - link:rfc2425.txt: A MIME Content-Type for Directory Information
|
24
50
|
# - http://www.imc.org/pdi/pdiproddev.html: vCard 2.1 Specifications
|
@@ -26,17 +52,6 @@ module Vpim
|
|
26
52
|
# vCards are usually transmitted in files with <code>.vcf</code>
|
27
53
|
# extensions.
|
28
54
|
#
|
29
|
-
# TODO - an open question is what exactly "vcard 2.1" support means. While I
|
30
|
-
# decode vCard 2.1 correctly, I don't encode it. Should I implement a
|
31
|
-
# transcoder, so vCards can be decoded from either version, and then written
|
32
|
-
# to either version? Maybe an option to Field#encode()?
|
33
|
-
#
|
34
|
-
# TODO - there are very few methods that Vcard has that DirectoryInfo
|
35
|
-
# doesn't. I could probably just do away with it entirely, but I suspect
|
36
|
-
# that there are methods that could be usefully added to Vcard, perhaps to
|
37
|
-
# get the email addresses, or the name, or perhaps to set fields, like
|
38
|
-
# email=. What would be useful?
|
39
|
-
#
|
40
55
|
# = Examples
|
41
56
|
#
|
42
57
|
# - link:ex_mkvcard.txt: example of creating a vCard
|
@@ -51,18 +66,566 @@ module Vpim
|
|
51
66
|
# (small but) complete application contributed by Dane G. Avilla, thanks!
|
52
67
|
# - link:vcf-to-ics.txt: example of how to create calendars of birthdays from vCards
|
53
68
|
# - link:vcf-dump.txt: utility for dumping contents of .vcf files
|
54
|
-
#
|
55
|
-
# Here's an example of encoding a simple vCard using the low-level API:
|
56
|
-
#
|
57
|
-
# card = Vpim::Vcard.create
|
58
|
-
# card << Vpim::DirectoryInfo::Field.create('EMAIL', 'user.name@example.com', 'TYPE' => 'INTERNET' )
|
59
|
-
# card << Vpim::DirectoryInfo::Field.create('URL', 'http://www.example.com/user' )
|
60
|
-
# card << Vpim::DirectoryInfo::Field.create('FN', 'User Name' )
|
61
|
-
# puts card.to_s
|
62
69
|
class Vcard < DirectoryInfo
|
63
70
|
|
71
|
+
# Represents the value of an ADR field.
|
72
|
+
#
|
73
|
+
# #location, #preferred, and #delivery indicate information about how the
|
74
|
+
# address is to be used, the other attributes are parts of the address.
|
75
|
+
#
|
76
|
+
# Using values other than those defined for #location or #delivery is
|
77
|
+
# unlikely to be portable, or even conformant.
|
78
|
+
#
|
79
|
+
# All attributes are optional. #location and #delivery can be set to arrays
|
80
|
+
# of strings.
|
81
|
+
class Address
|
82
|
+
# post office box (String)
|
83
|
+
attr_accessor :pobox
|
84
|
+
# seldom used, its not clear what it is for (String)
|
85
|
+
attr_accessor :extended
|
86
|
+
# street address (String)
|
87
|
+
attr_accessor :street
|
88
|
+
# usually the city (String)
|
89
|
+
attr_accessor :locality
|
90
|
+
# usually the province or state (String)
|
91
|
+
attr_accessor :region
|
92
|
+
# postal code (String)
|
93
|
+
attr_accessor :postalcode
|
94
|
+
# country name (String)
|
95
|
+
attr_accessor :country
|
96
|
+
# home, work (Array of String): the location referred to by the address
|
97
|
+
attr_accessor :location
|
98
|
+
# true, false (boolean): where this is the preferred address (for this location)
|
99
|
+
attr_accessor :preferred
|
100
|
+
# postal, parcel, dom (domestic), intl (international) (Array of String): delivery
|
101
|
+
# type of this address
|
102
|
+
attr_accessor :delivery
|
103
|
+
|
104
|
+
# nonstandard types, their meaning is undefined (Array of String). These
|
105
|
+
# might be found during decoding, but shouldn't be set during encoding.
|
106
|
+
attr_reader :nonstandard
|
107
|
+
|
108
|
+
# Used to simplify some long and tedious code. These symbols are in the
|
109
|
+
# order required for the ADR field structured TEXT value, the order
|
110
|
+
# cannot be changed.
|
111
|
+
@@adr_parts = [
|
112
|
+
:@pobox,
|
113
|
+
:@extended,
|
114
|
+
:@street,
|
115
|
+
:@locality,
|
116
|
+
:@region,
|
117
|
+
:@postalcode,
|
118
|
+
:@country,
|
119
|
+
]
|
120
|
+
|
121
|
+
# TODO
|
122
|
+
# - #location?
|
123
|
+
# - #delivery?
|
124
|
+
def initialize #:nodoc:
|
125
|
+
# TODO - Add #label to support LABEL. Try to find LABEL
|
126
|
+
# in either same group, or with sam params.
|
127
|
+
@@adr_parts.each do |part|
|
128
|
+
instance_variable_set(part, '')
|
129
|
+
end
|
130
|
+
|
131
|
+
@location = []
|
132
|
+
@preferred = false
|
133
|
+
@delivery = []
|
134
|
+
@nonstandard = []
|
135
|
+
end
|
136
|
+
|
137
|
+
def encode #:nodoc:
|
138
|
+
parts = @@adr_parts.map do |part|
|
139
|
+
instance_variable_get(part)
|
140
|
+
end
|
141
|
+
|
142
|
+
value = Vpim.encode_text_list(parts, ";")
|
143
|
+
|
144
|
+
params = [ @location, @delivery, @nonstandard ]
|
145
|
+
params << 'pref' if @preferred
|
146
|
+
params = params.flatten.compact.map { |s| s.to_str.downcase }.uniq
|
147
|
+
|
148
|
+
paramshash = {}
|
149
|
+
|
150
|
+
paramshash['TYPE'] = params if params.first
|
151
|
+
|
152
|
+
Vpim::DirectoryInfo::Field.create( 'ADR', value, paramshash)
|
153
|
+
end
|
154
|
+
|
155
|
+
def Address.decode(card, field) #:nodoc:
|
156
|
+
adr = new
|
157
|
+
|
158
|
+
parts = Vpim.decode_text_list(field.value_raw, ';')
|
159
|
+
|
160
|
+
@@adr_parts.each_with_index do |part,i|
|
161
|
+
adr.instance_variable_set(part, parts[i] || '')
|
162
|
+
end
|
163
|
+
|
164
|
+
params = field.pvalues('TYPE')
|
165
|
+
|
166
|
+
if params
|
167
|
+
params.each do |p|
|
168
|
+
p.downcase!
|
169
|
+
case p
|
170
|
+
when 'home', 'work'
|
171
|
+
adr.location << p
|
172
|
+
when 'postal', 'parcel', 'dom', 'intl'
|
173
|
+
adr.delivery << p
|
174
|
+
when 'pref'
|
175
|
+
adr.preferred = true
|
176
|
+
else
|
177
|
+
adr.nonstandard << p
|
178
|
+
end
|
179
|
+
end
|
180
|
+
# Strip duplicates
|
181
|
+
[ adr.location, adr.delivery, adr.nonstandard ].each do |a|
|
182
|
+
a.uniq!
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
adr
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# Represents the value of an EMAIL field.
|
191
|
+
class Email < String
|
192
|
+
# true, false (boolean): whether this is the preferred email address
|
193
|
+
attr_accessor :preferred
|
194
|
+
# internet, x400 (String): the email address format, rarely specified
|
195
|
+
# since the default is 'internet'
|
196
|
+
attr_accessor :format
|
197
|
+
# home, work (Array of String): the location referred to by the address. The
|
198
|
+
# inclusion of location parameters in a vCard seems to be non-conformant,
|
199
|
+
# strictly speaking, but also seems to be widespread.
|
200
|
+
attr_accessor :location
|
201
|
+
# nonstandard types, their meaning is undefined (Array of String). These
|
202
|
+
# might be found during decoding, but shouldn't be set during encoding.
|
203
|
+
attr_reader :nonstandard
|
204
|
+
|
205
|
+
def initialize(email='') #:nodoc:
|
206
|
+
@preferred = false
|
207
|
+
@format = 'internet'
|
208
|
+
@location = []
|
209
|
+
@nonstandard = []
|
210
|
+
super(email)
|
211
|
+
end
|
212
|
+
|
213
|
+
def inspect #:nodoc:
|
214
|
+
s = "#<#{self.class.to_s}: #{to_str.inspect}"
|
215
|
+
s << ", pref" if preferred
|
216
|
+
s << ", #{format}" if format != 'internet'
|
217
|
+
s << ", " << @location.join(", ") if @location.first
|
218
|
+
s << ", #{@nonstandard.join(", ")}" if @nonstandard.first
|
219
|
+
s
|
220
|
+
end
|
221
|
+
|
222
|
+
def encode #:nodoc:
|
223
|
+
value = to_str.strip
|
224
|
+
|
225
|
+
if value.length < 1
|
226
|
+
raise InvalidEncodingError, "EMAIL must have a value"
|
227
|
+
end
|
228
|
+
|
229
|
+
params = [ @location, @nonstandard ]
|
230
|
+
params << @format if @format != 'internet'
|
231
|
+
params << 'pref' if @preferred
|
232
|
+
|
233
|
+
params = params.flatten.compact.map { |s| s.to_str.downcase }.uniq
|
234
|
+
|
235
|
+
paramshash = {}
|
236
|
+
|
237
|
+
paramshash['TYPE'] = params if params.first
|
238
|
+
|
239
|
+
Vpim::DirectoryInfo::Field.create( 'EMAIL', value, paramshash)
|
240
|
+
end
|
241
|
+
|
242
|
+
def Email.decode(field) #:nodoc:
|
243
|
+
value = field.to_text.strip
|
244
|
+
|
245
|
+
if value.length < 1
|
246
|
+
raise InvalidEncodingError, "EMAIL must have a value"
|
247
|
+
end
|
248
|
+
|
249
|
+
eml = Email.new(value)
|
250
|
+
|
251
|
+
params = field.pvalues('TYPE')
|
252
|
+
|
253
|
+
if params
|
254
|
+
params.each do |p|
|
255
|
+
p.downcase!
|
256
|
+
case p
|
257
|
+
when 'home', 'work'
|
258
|
+
eml.location << p
|
259
|
+
when 'pref'
|
260
|
+
eml.preferred = true
|
261
|
+
when 'x400', 'internet'
|
262
|
+
eml.format = p
|
263
|
+
else
|
264
|
+
eml.nonstandard << p
|
265
|
+
end
|
266
|
+
end
|
267
|
+
# Strip duplicates
|
268
|
+
[ eml.location, eml.nonstandard ].each do |a|
|
269
|
+
a.uniq!
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
eml
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
# Represents the value of a TEL field.
|
278
|
+
#
|
279
|
+
# The value is supposed to be a "X.500 Telephone Number" according to RFC
|
280
|
+
# 2426, but that standard is not freely available. Otherwise, anything that
|
281
|
+
# looks like a phone number should be OK.
|
282
|
+
class Telephone < String
|
283
|
+
# true, false (boolean): whether this is the preferred email address
|
284
|
+
attr_accessor :preferred
|
285
|
+
# home, work, cell, car, pager (Array of String): the location
|
286
|
+
# of the device
|
287
|
+
attr_accessor :location
|
288
|
+
# voice, fax, video, msg, bbs, modem, isdn, pcs (Array of String): the
|
289
|
+
# capabilities of the device
|
290
|
+
attr_accessor :capability
|
291
|
+
# nonstandard types, their meaning is undefined (Array of String). These
|
292
|
+
# might be found during decoding, but shouldn't be set during encoding.
|
293
|
+
attr_reader :nonstandard
|
294
|
+
|
295
|
+
def initialize(telephone='') #:nodoc:
|
296
|
+
@preferred = false
|
297
|
+
@location = []
|
298
|
+
@capability = []
|
299
|
+
@nonstandard = []
|
300
|
+
super(telephone)
|
301
|
+
end
|
302
|
+
|
303
|
+
def inspect #:nodoc:
|
304
|
+
s = "#<#{self.class.to_s}: #{to_str.inspect}"
|
305
|
+
s << ", pref" if preferred
|
306
|
+
s << ", " << @location.join(", ") if @location.first
|
307
|
+
s << ", " << @capability.join(", ") if @capability.first
|
308
|
+
s << ", #{@nonstandard.join(", ")}" if @nonstandard.first
|
309
|
+
s
|
310
|
+
end
|
311
|
+
|
312
|
+
def encode #:nodoc:
|
313
|
+
value = to_str.strip
|
314
|
+
|
315
|
+
if value.length < 1
|
316
|
+
raise InvalidEncodingError, "TEL must have a value"
|
317
|
+
end
|
318
|
+
|
319
|
+
params = [ @location, @capability, @nonstandard ]
|
320
|
+
params << 'pref' if @preferred
|
321
|
+
|
322
|
+
params = params.flatten.compact.map { |s| s.to_str.downcase }.uniq
|
323
|
+
|
324
|
+
paramshash = {}
|
325
|
+
|
326
|
+
paramshash['TYPE'] = params if params.first
|
327
|
+
|
328
|
+
Vpim::DirectoryInfo::Field.create( 'TEL', value, paramshash)
|
329
|
+
end
|
330
|
+
|
331
|
+
def Telephone.decode(field) #:nodoc:
|
332
|
+
value = field.to_text.strip
|
333
|
+
|
334
|
+
if value.length < 1
|
335
|
+
raise InvalidEncodingError, "TEL must have a value"
|
336
|
+
end
|
337
|
+
|
338
|
+
tel = Telephone.new(value)
|
339
|
+
|
340
|
+
params = field.pvalues('TYPE')
|
341
|
+
|
342
|
+
if params
|
343
|
+
params.each do |p|
|
344
|
+
p.downcase!
|
345
|
+
case p
|
346
|
+
when 'home', 'work', 'cell', 'car', 'pager'
|
347
|
+
tel.location << p
|
348
|
+
when 'voice', 'fax', 'video', 'msg', 'bbs', 'modem', 'isdn', 'pcs'
|
349
|
+
tel.capability << p
|
350
|
+
when 'pref'
|
351
|
+
tel.preferred = true
|
352
|
+
else
|
353
|
+
tel.nonstandard << p
|
354
|
+
end
|
355
|
+
end
|
356
|
+
# Strip duplicates
|
357
|
+
[ tel.location, tel.capability, tel.nonstandard ].each do |a|
|
358
|
+
a.uniq!
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
tel
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
# The name from a vCard, including all the components of the N: and FN:
|
367
|
+
# fields.
|
368
|
+
class Name
|
369
|
+
# family name, from N
|
370
|
+
attr_accessor :family
|
371
|
+
# given name, from N
|
372
|
+
attr_accessor :given
|
373
|
+
# additional names, from N
|
374
|
+
attr_accessor :additional
|
375
|
+
# such as "Ms." or "Dr.", from N
|
376
|
+
attr_accessor :prefix
|
377
|
+
# such as "BFA", from N
|
378
|
+
attr_accessor :suffix
|
379
|
+
# full name, the FN field. FN is a formatted version of the N field,
|
380
|
+
# intended to be in a form more aligned with the cultural conventions of
|
381
|
+
# the vCard owner than +formatted+ is.
|
382
|
+
attr_accessor :fullname
|
383
|
+
# all the components of N formtted as "#{prefix} #{given} #{additional} #{family}, #{suffix}"
|
384
|
+
attr_reader :formatted
|
385
|
+
|
386
|
+
# Override the attr reader to make it dynamic
|
387
|
+
remove_method :formatted
|
388
|
+
def formatted #:nodoc:
|
389
|
+
f = [ @prefix, @given, @additional, @family ].map{|i| i == '' ? nil : i.strip}.compact.join(' ')
|
390
|
+
if @suffix != ''
|
391
|
+
f << ', ' << @suffix
|
392
|
+
end
|
393
|
+
f
|
394
|
+
end
|
395
|
+
|
396
|
+
def initialize(n='', fn='') #:nodoc:
|
397
|
+
n = Vpim.decode_text_list(n, ';') do |item|
|
398
|
+
item.strip
|
399
|
+
end
|
400
|
+
|
401
|
+
@family = n[0] || ""
|
402
|
+
@given = n[1] || ""
|
403
|
+
@additional = n[2] || ""
|
404
|
+
@prefix = n[3] || ""
|
405
|
+
@suffix = n[4] || ""
|
406
|
+
|
407
|
+
# FIXME - make calls to #fullname fail if fn is nil
|
408
|
+
@fullname = (fn || "").strip
|
409
|
+
end
|
410
|
+
|
411
|
+
def encode #:nodoc:
|
412
|
+
Vpim::DirectoryInfo::Field.create('N',
|
413
|
+
Vpim.encode_text_list([ @family, @given, @additional, @prefix, @suffix ].map{|n| n.strip}, ';')
|
414
|
+
)
|
415
|
+
end
|
416
|
+
def encode_fn #:nodoc:
|
417
|
+
fn = @fullname.strip
|
418
|
+
if @fullname.length == 0
|
419
|
+
fn = formatted
|
420
|
+
end
|
421
|
+
Vpim::DirectoryInfo::Field.create('FN', fn)
|
422
|
+
end
|
423
|
+
|
424
|
+
end
|
425
|
+
|
426
|
+
def decode_invisible(field) #:nodoc:
|
427
|
+
nil
|
428
|
+
end
|
429
|
+
|
430
|
+
def decode_default(field) #:nodoc:
|
431
|
+
Line.new( field.group, field.name, field.value )
|
432
|
+
end
|
433
|
+
|
434
|
+
def decode_version(field) #:nodoc:
|
435
|
+
Line.new( field.group, field.name, (field.value.to_f * 10).to_i )
|
436
|
+
end
|
437
|
+
|
438
|
+
def decode_text(field) #:nodoc:
|
439
|
+
Line.new( field.group, field.name, Vpim.decode_text(field.value_raw) )
|
440
|
+
end
|
441
|
+
|
442
|
+
def decode_n(field) #:nodoc:
|
443
|
+
Line.new( field.group, field.name, Name.new(field.value, self['FN']).freeze )
|
444
|
+
end
|
445
|
+
|
446
|
+
def decode_date_or_datetime(field) #:nodoc:
|
447
|
+
date = nil
|
448
|
+
begin
|
449
|
+
date = Vpim.decode_date(field.value_raw)
|
450
|
+
date = Date.new(*date)
|
451
|
+
rescue Vpim::InvalidEncodingError
|
452
|
+
# FIXME - try and decode as DATE-TIME
|
453
|
+
raise
|
454
|
+
end
|
455
|
+
Line.new( field.group, field.name, date )
|
456
|
+
end
|
457
|
+
|
458
|
+
def decode_bday(field) #:nodoc:
|
459
|
+
begin
|
460
|
+
return decode_date_or_datetime(field)
|
461
|
+
|
462
|
+
rescue Vpim::InvalidEncodingError
|
463
|
+
if field.value =~ /(\d+)-(\d+)-(\d+)/
|
464
|
+
y = $1.to_i
|
465
|
+
m = $2.to_i
|
466
|
+
d = $3.to_i
|
467
|
+
if(y < 1900)
|
468
|
+
y = Time.now.year
|
469
|
+
end
|
470
|
+
Line.new( field.group, field.name, Date.new(y, m, d) )
|
471
|
+
else
|
472
|
+
raise
|
473
|
+
end
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
def decode_geo(field) #:nodoc:
|
478
|
+
geo = Vpim.decode_list(field.value_raw, ';') do |item| item.to_f end
|
479
|
+
Line.new( field.group, field.name, geo )
|
480
|
+
end
|
481
|
+
|
482
|
+
def decode_address(field) #:nodoc:
|
483
|
+
Line.new( field.group, field.name, Address.decode(self, field) )
|
484
|
+
end
|
485
|
+
|
486
|
+
def decode_email(field) #:nodoc:
|
487
|
+
Line.new( field.group, field.name, Email.decode(field) )
|
488
|
+
end
|
489
|
+
|
490
|
+
def decode_telephone(field) #:nodoc:
|
491
|
+
Line.new( field.group, field.name, Telephone.decode(field) )
|
492
|
+
end
|
493
|
+
|
494
|
+
def decode_list_of_text(field) #:nodoc:
|
495
|
+
Line.new( field.group, field.name,
|
496
|
+
Vpim.decode_text_list(field.value_raw).select{|t| t.length > 0}.uniq
|
497
|
+
)
|
498
|
+
end
|
499
|
+
|
500
|
+
def decode_structured_text(field) #:nodoc:
|
501
|
+
Line.new( field.group, field.name, Vpim.decode_text_list(field.value_raw, ';') )
|
502
|
+
end
|
503
|
+
|
504
|
+
def decode_uri(field) #:nodoc:
|
505
|
+
Line.new( field.group, field.name, Uri.new(field.value) )
|
506
|
+
end
|
507
|
+
|
508
|
+
def decode_agent(field) #:nodoc:
|
509
|
+
case field.kind
|
510
|
+
when 'text'
|
511
|
+
decode_text(field)
|
512
|
+
when 'uri'
|
513
|
+
decode_uri(field)
|
514
|
+
when 'vcard', nil
|
515
|
+
Line.new( field.group, field.name, Vcard.decode(Vpim.decode_text(field.value_raw)).first )
|
516
|
+
else
|
517
|
+
raise InvalidEncodingError, "AGENT type #{field.kind} is not allowed"
|
518
|
+
end
|
519
|
+
end
|
520
|
+
|
521
|
+
def decode_attachment(field) #:nodoc:
|
522
|
+
Line.new( field.group, field.name, Attachment.decode(field, 'binary', 'TYPE') )
|
523
|
+
end
|
524
|
+
|
525
|
+
@@decode = {
|
526
|
+
'BEGIN' => :decode_invisible, # Don't return delimiter
|
527
|
+
'END' => :decode_invisible, # Don't return delimiter
|
528
|
+
'FN' => :decode_invisible, # Returned as part of N.
|
529
|
+
|
530
|
+
'ADR' => :decode_address,
|
531
|
+
'AGENT' => :decode_agent,
|
532
|
+
'BDAY' => :decode_bday,
|
533
|
+
'CATEGORIES' => :decode_list_of_text,
|
534
|
+
'EMAIL' => :decode_email,
|
535
|
+
'GEO' => :decode_geo,
|
536
|
+
'KEY' => :decode_attachment,
|
537
|
+
'LOGO' => :decode_attachment,
|
538
|
+
'MAILER' => :decode_text,
|
539
|
+
'N' => :decode_n,
|
540
|
+
'NAME' => :decode_text,
|
541
|
+
'NICKNAME' => :decode_list_of_text,
|
542
|
+
'NOTE' => :decode_text,
|
543
|
+
'ORG' => :decode_structured_text,
|
544
|
+
'PHOTO' => :decode_attachment,
|
545
|
+
'PRODID' => :decode_text,
|
546
|
+
'PROFILE' => :decode_text,
|
547
|
+
'REV' => :decode_date_or_datetime,
|
548
|
+
'ROLE' => :decode_text,
|
549
|
+
'SOUND' => :decode_attachment,
|
550
|
+
'SOURCE' => :decode_text,
|
551
|
+
'TEL' => :decode_telephone,
|
552
|
+
'TITLE' => :decode_text,
|
553
|
+
'UID' => :decode_text,
|
554
|
+
'URL' => :decode_uri,
|
555
|
+
'VERSION' => :decode_version,
|
556
|
+
}
|
557
|
+
|
558
|
+
@@decode.default = :decode_default
|
559
|
+
|
560
|
+
# Cache of decoded lines/fields, so we don't have to decode a field more than once.
|
561
|
+
attr_reader :cache #:nodoc:
|
562
|
+
|
563
|
+
# An entry in a vCard. The #value object's type varies with the kind of
|
564
|
+
# line (the #name), and on how the line was encoded. The objects returned
|
565
|
+
# for a specific kind of line are often extended so that they support a
|
566
|
+
# common set of methods. The goal is to allow all types of objects for a
|
567
|
+
# kind of line to be treated with some uniformity, but still allow specific
|
568
|
+
# handling for the various value types if desired.
|
569
|
+
#
|
570
|
+
# See the specific methods for details.
|
571
|
+
class Line
|
572
|
+
attr_reader :group
|
573
|
+
attr_reader :name
|
574
|
+
attr_reader :value
|
575
|
+
|
576
|
+
def initialize(group, name, value) #:nodoc:
|
577
|
+
@group, @name, @value = (group||''), name.to_str, value
|
578
|
+
end
|
579
|
+
|
580
|
+
def self.decode(decode, card, field) #:nodoc:
|
581
|
+
card.cache[field] || (card.cache[field] = card.send(decode[field.name], field))
|
582
|
+
end
|
583
|
+
end
|
584
|
+
|
585
|
+
#@lines = {} FIXME - dead code
|
586
|
+
|
587
|
+
# Return line for a field
|
588
|
+
def f2l(field) #:nodoc:
|
589
|
+
Line.decode(@@decode, self, field)
|
590
|
+
end
|
591
|
+
|
592
|
+
# With no block, returns an Array of Line. If +name+ is specified, the
|
593
|
+
# Array will only contain the +Line+s with that +name+. The Array may be
|
594
|
+
# empty.
|
595
|
+
#
|
596
|
+
# If a block is given, each Line will be yielded instead of being returned
|
597
|
+
# in an Array.
|
598
|
+
def lines(name=nil) #:yield: Line
|
599
|
+
# FIXME - this would be much easier if #lines was #each, and there was a
|
600
|
+
# different #lines that returned an Enumerator that used #each
|
601
|
+
unless block_given?
|
602
|
+
map do |f|
|
603
|
+
if( !name || f.name?(name) )
|
604
|
+
f2l(f)
|
605
|
+
else
|
606
|
+
nil
|
607
|
+
end
|
608
|
+
end.compact
|
609
|
+
else
|
610
|
+
each do |f|
|
611
|
+
if( !name || f.name?(name) )
|
612
|
+
line = f2l(f)
|
613
|
+
if line
|
614
|
+
yield line
|
615
|
+
end
|
616
|
+
end
|
617
|
+
end
|
618
|
+
self
|
619
|
+
end
|
620
|
+
end
|
621
|
+
|
64
622
|
private_class_method :new
|
65
623
|
|
624
|
+
def initialize(fields, profile) #:nodoc:
|
625
|
+
@cache = {}
|
626
|
+
super(fields, profile)
|
627
|
+
end
|
628
|
+
|
66
629
|
# Create a vCard 3.0 object with the minimum required fields, plus any
|
67
630
|
# +fields+ you want in the card (they can also be added later).
|
68
631
|
def Vcard.create(fields = [] )
|
@@ -139,23 +702,12 @@ module Vpim
|
|
139
702
|
vcards
|
140
703
|
end
|
141
704
|
|
142
|
-
# The vCard version multiplied by 10 as an Integer. If no VERSION field
|
143
|
-
# is present (which is non-conformant), nil is returned. For example, a
|
144
|
-
# version 2.1 vCard would have a version of 21, and a version 3.0 vCard
|
145
|
-
# would have a version of 30.
|
146
|
-
def version
|
147
|
-
v = self["version"]
|
148
|
-
unless v
|
149
|
-
raise Vpim::InvalidEncodingError, 'Invalid vCard - it has no version field!'
|
150
|
-
end
|
151
|
-
v = v.to_f * 10
|
152
|
-
v = v.to_i
|
153
|
-
end
|
154
|
-
|
155
705
|
# The value of the field named +name+, optionally limited to fields of
|
156
706
|
# type +type+. If no match is found, nil is returned, if multiple matches
|
157
707
|
# are found, the first match to have one of its type values be 'PREF'
|
158
708
|
# (preferred) is returned, otherwise the first match is returned.
|
709
|
+
#
|
710
|
+
# FIXME - this will become an alias for #value.
|
159
711
|
def [](name, type=nil)
|
160
712
|
fields = enum_by_name(name).find_all { |f| type == nil || f.type?(type) }
|
161
713
|
|
@@ -174,103 +726,675 @@ module Vpim
|
|
174
726
|
fields.first ? fields.first.value : nil
|
175
727
|
end
|
176
728
|
|
177
|
-
#
|
178
|
-
#
|
729
|
+
# Return the Line#value for a specific +name+, and optionally for a
|
730
|
+
# specific +type+.
|
731
|
+
#
|
732
|
+
# If no line with the +name+ (and, optionally, +type+) exists, nil is
|
733
|
+
# returned.
|
734
|
+
#
|
735
|
+
# If multiple lines exist, the order of preference is:
|
736
|
+
# - lines with values over lines without
|
737
|
+
# - lines with a type of 'pref' over lines without
|
738
|
+
# If multiple lines are equally preferred, then the first line will be
|
739
|
+
# returned.
|
740
|
+
#
|
741
|
+
# This is most useful when looking for a line that can not occur multiple
|
742
|
+
# times, or when the line can occur multiple times, and you want to pick
|
743
|
+
# the first preferred line of a specific type. See #values if you need to
|
744
|
+
# access all the lines.
|
745
|
+
#
|
746
|
+
# Note that the +type+ field parameter is used for different purposes by
|
747
|
+
# the various kinds of vCard lines, but for the addressing lines (ADR,
|
748
|
+
# LABEL, TEL, EMAIL) it is has a reasonably consistent usage. Each
|
749
|
+
# addressing line can occur multiple times, and a +type+ of 'pref'
|
750
|
+
# indicates that a particular line is the preferred line. Other +type+
|
751
|
+
# values tend to indicate some information about the location ('home',
|
752
|
+
# 'work', ...) or some detail about the address ('cell', 'fax', 'voice',
|
753
|
+
# ...). See the methods for the specific types of line for information
|
754
|
+
# about supported types and their meaning.
|
755
|
+
def value(name, type = nil)
|
756
|
+
v = nil
|
179
757
|
|
180
|
-
|
181
|
-
# family name from N:
|
182
|
-
attr_reader :family
|
183
|
-
# given name from N:
|
184
|
-
attr_reader :given
|
185
|
-
# additional names from N:
|
186
|
-
attr_reader :additional
|
187
|
-
# such as "Ms." or "Dr.", from N:
|
188
|
-
attr_reader :prefix
|
189
|
-
# such as "BFA", from N:
|
190
|
-
attr_reader :suffix
|
191
|
-
# all the components of N: formtted as "#{prefix} #{given} #{additional} #{family}, #{suffix}"
|
192
|
-
attr_reader :formatted
|
193
|
-
# full name, the FN: field, a formatted version of the N: field, probably
|
194
|
-
# in a form more align with the cultural conventions of the vCard owner
|
195
|
-
# than +formatted+ is
|
196
|
-
attr_reader :fullname
|
197
|
-
|
198
|
-
def initialize(n, fn) #:nodoc:
|
199
|
-
n = Vpim.decode_list(n, ';') do |item|
|
200
|
-
item.strip
|
201
|
-
end
|
758
|
+
fields = enum_by_name(name).find_all { |f| type == nil || f.type?(type) }
|
202
759
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
@suffix = n[4] || ""
|
208
|
-
@formatted = [ @prefix, @given, @additional, @family ].map{|i| i == '' ? nil : i}.compact.join(' ')
|
209
|
-
if @suffix != ''
|
210
|
-
@formatted << ', ' << @suffix
|
211
|
-
end
|
760
|
+
valued = fields.select { |f| f.value != '' }
|
761
|
+
if valued.first
|
762
|
+
fields = valued
|
763
|
+
end
|
212
764
|
|
213
|
-
|
214
|
-
|
765
|
+
pref = fields.select { |f| f.pref? }
|
766
|
+
|
767
|
+
if pref.first
|
768
|
+
fields = pref
|
769
|
+
end
|
770
|
+
|
771
|
+
if fields.first
|
772
|
+
line = Line.decode(@@decode, self, fields.first)
|
773
|
+
|
774
|
+
if line
|
775
|
+
return line.value
|
776
|
+
end
|
215
777
|
end
|
216
778
|
|
779
|
+
nil
|
780
|
+
end
|
781
|
+
|
782
|
+
# A variant of #lines that only iterates over specific Line names. Since
|
783
|
+
# the name is known, only the Line#value is returned or yielded.
|
784
|
+
def values(name)
|
785
|
+
unless block_given?
|
786
|
+
lines(name).map { |line| line.value }
|
787
|
+
else
|
788
|
+
lines(name) { |line| yield line.value }
|
789
|
+
end
|
790
|
+
end
|
791
|
+
|
792
|
+
# The first ADR value of type +type+, a Address. Any of the location or
|
793
|
+
# delivery attributes of Address can be used as +type+. A wrapper around
|
794
|
+
# #value('ADR', +type+).
|
795
|
+
def address(type=nil)
|
796
|
+
value('ADR', type)
|
797
|
+
end
|
798
|
+
|
799
|
+
# The ADR values, an array of Address. If a block is given, the values are
|
800
|
+
# yielded. A wrapper around #values('ADR').
|
801
|
+
def addresses #:yield:address
|
802
|
+
values('ADR')
|
803
|
+
end
|
804
|
+
|
805
|
+
# The AGENT values. Each AGENT value is either a String, a Uri, or a Vcard.
|
806
|
+
# If a block is given, the values are yielded. A wrapper around
|
807
|
+
# #values('AGENT').
|
808
|
+
def agents #:yield:agent
|
809
|
+
values('AGENT')
|
810
|
+
end
|
811
|
+
|
812
|
+
# The BDAY value as either a Date or a DateTime, or nil if there is none.
|
813
|
+
#
|
814
|
+
# If the BDAY value is invalidly formatted, a feeble heuristic is applied
|
815
|
+
# to find the month and year, and return a Date in the current year.
|
816
|
+
def birthday
|
817
|
+
value('BDAY')
|
818
|
+
end
|
819
|
+
|
820
|
+
# The CATEGORIES values, an array of String. A wrapper around
|
821
|
+
# #value('CATEGORIES').
|
822
|
+
def categories
|
823
|
+
value('CATEGORIES')
|
824
|
+
end
|
825
|
+
|
826
|
+
# The first EMAIL value of type +type+, a Email. Any of the location
|
827
|
+
# attributes of Email can be used as +type+. A wrapper around
|
828
|
+
# #value('EMAIL', +type+).
|
829
|
+
def email(type=nil)
|
830
|
+
value('EMAIL', type)
|
831
|
+
end
|
832
|
+
|
833
|
+
# The EMAIL values, an array of Email. If a block is given, the values are
|
834
|
+
# yielded. A wrapper around #values('EMAIL').
|
835
|
+
def emails #:yield:email
|
836
|
+
values('EMAIL')
|
837
|
+
end
|
838
|
+
|
839
|
+
# The GEO value, an Array of two Floats, +[ latitude, longitude]+. North
|
840
|
+
# of the equator is positive latitude, east of the meridian is positive
|
841
|
+
# longitude. See RFC2445 for more info, there are lots of special cases
|
842
|
+
# and RFC2445's description is more complete thant RFC2426.
|
843
|
+
def geo
|
844
|
+
value('GEO')
|
845
|
+
end
|
846
|
+
|
847
|
+
# Return an Array of KEY Line#value, or yield each Line#value if a block
|
848
|
+
# is given. A wrapper around #values('KEY').
|
849
|
+
#
|
850
|
+
# KEY is a public key or authentication certificate associated with the
|
851
|
+
# object that the vCard represents. It is not commonly used, but could
|
852
|
+
# contain a X.509 or PGP certificate.
|
853
|
+
#
|
854
|
+
# See Attachment for a description of the value.
|
855
|
+
def keys(&proc) #:yield: Line.value
|
856
|
+
values('KEY', &proc)
|
857
|
+
end
|
858
|
+
|
859
|
+
# Return an Array of LOGO Line#value, or yield each Line#value if a block
|
860
|
+
# is given. A wrapper around #values('LOGO').
|
861
|
+
#
|
862
|
+
# LOGO is a graphic image of a logo associated with the object the vCard
|
863
|
+
# represents. Its not common, but would probably be equivalent to the logo
|
864
|
+
# on printed card.
|
865
|
+
#
|
866
|
+
# See Attachment for a description of the value.
|
867
|
+
def logos(&proc) #:yield: Line.value
|
868
|
+
values('LOGO', &proc)
|
217
869
|
end
|
218
870
|
|
219
|
-
|
871
|
+
## MAILER
|
872
|
+
|
873
|
+
# The N and FN as a Name object.
|
874
|
+
#
|
875
|
+
# N is required for a vCards, this raises InvalidEncodingError if
|
876
|
+
# there is no N so it cannot return nil.
|
220
877
|
def name
|
221
|
-
|
222
|
-
|
878
|
+
value('N') || raise(Vpim::InvalidEncodingError, "Missing mandatory N field")
|
879
|
+
end
|
880
|
+
|
881
|
+
# The first NICKNAME value, nil if there are none.
|
882
|
+
def nickname
|
883
|
+
v = value('NICKNAME')
|
884
|
+
v = v.first if v
|
885
|
+
v
|
886
|
+
end
|
887
|
+
|
888
|
+
# The NICKNAME values, an array of String. The array may be empty.
|
889
|
+
def nicknames
|
890
|
+
values('NICKNAME').flatten.uniq
|
891
|
+
end
|
892
|
+
|
893
|
+
# The NOTE value, a String. A wrapper around #value('NOTE').
|
894
|
+
def note
|
895
|
+
value('NOTE')
|
896
|
+
end
|
897
|
+
|
898
|
+
# The ORG value, an Array of String. The first string is the organization,
|
899
|
+
# subsequent strings are departments within the organization. A wrapper
|
900
|
+
# around #value('ORG').
|
901
|
+
def org
|
902
|
+
value('ORG')
|
903
|
+
end
|
904
|
+
|
905
|
+
# Return an Array of PHOTO Line#value, or yield each Line#value if a block
|
906
|
+
# is given. A wrapper around #values('PHOTO').
|
907
|
+
#
|
908
|
+
# PHOTO is an image or photograph information that annotates some aspect of
|
909
|
+
# the object the vCard represents. Commonly there is one PHOTO, and it is a
|
910
|
+
# photo of the person identified by the vCard.
|
911
|
+
#
|
912
|
+
# See Attachment for a description of the value.
|
913
|
+
def photos(&proc) #:yield: Line.value
|
914
|
+
values('PHOTO', &proc)
|
915
|
+
end
|
916
|
+
|
917
|
+
## PRODID
|
918
|
+
|
919
|
+
## PROFILE
|
920
|
+
|
921
|
+
## REV
|
922
|
+
|
923
|
+
## ROLE
|
924
|
+
|
925
|
+
# Return an Array of SOUND Line#value, or yield each Line#value if a block
|
926
|
+
# is given. A wrapper around #values('SOUND').
|
927
|
+
#
|
928
|
+
# SOUND is digital sound content information that annotates some aspect of
|
929
|
+
# the vCard. By default this type is used to specify the proper
|
930
|
+
# pronunciation of the name associated with the vCard. It is not commonly
|
931
|
+
# used. Also, note that there is no mechanism available to specify that the
|
932
|
+
# SOUND is being used for anything other than the default.
|
933
|
+
#
|
934
|
+
# See Attachment for a description of the value.
|
935
|
+
def sounds(&proc) #:yield: Line.value
|
936
|
+
values('SOUND', &proc)
|
937
|
+
end
|
938
|
+
|
939
|
+
## SOURCE
|
940
|
+
|
941
|
+
# The first TEL value of type +type+, a Telephone. Any of the location or
|
942
|
+
# capability attributes of Telephone can be used as +type+. A wrapper around
|
943
|
+
# #value('TEL', +type+).
|
944
|
+
def telephone(type=nil)
|
945
|
+
value('TEL', type)
|
946
|
+
end
|
947
|
+
|
948
|
+
# The TEL values, an array of Telephone. If a block is given, the values are
|
949
|
+
# yielded. A wrapper around #values('TEL').
|
950
|
+
def telephones #:yield:tel
|
951
|
+
values('TEL')
|
952
|
+
end
|
953
|
+
|
954
|
+
## TITLE
|
955
|
+
|
956
|
+
## UID
|
957
|
+
|
958
|
+
# The URL value, a Uri. A wrapper around #value('NOTE').
|
959
|
+
def url
|
960
|
+
value('URL')
|
961
|
+
end
|
962
|
+
|
963
|
+
# The VERSION multiplied by 10 as an Integer. For example, a VERSION:2.1
|
964
|
+
# vCard would have a version of 21, and a VERSION:3.0 vCard would have a
|
965
|
+
# version of 30.
|
966
|
+
#
|
967
|
+
# VERSION is required for a vCard, this raises InvalidEncodingError if
|
968
|
+
# there is no VERSION so it cannot return nil.
|
969
|
+
def version
|
970
|
+
v = value('VERSION')
|
971
|
+
unless v
|
972
|
+
raise Vpim::InvalidEncodingError, 'Invalid vCard - it has no version field!'
|
223
973
|
end
|
224
|
-
|
974
|
+
v
|
225
975
|
end
|
226
976
|
|
227
|
-
#
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
977
|
+
# Make changes to a vCard.
|
978
|
+
#
|
979
|
+
# Yields a Vpim::Vcard::Maker that can be used to modify this vCard.
|
980
|
+
def make #:yield: maker
|
981
|
+
Vpim::Vcard::Maker.make2(self) do |maker|
|
982
|
+
yield maker
|
232
983
|
end
|
233
|
-
nn
|
234
984
|
end
|
235
985
|
|
236
|
-
#
|
237
|
-
def
|
238
|
-
|
986
|
+
# Delete +line+ if block yields true.
|
987
|
+
def delete_if #:nodoc: :yield: line
|
988
|
+
# Do in two steps to not mess up progress through the enumerator.
|
989
|
+
rm = []
|
990
|
+
|
991
|
+
each do |f|
|
992
|
+
line = f2l(f)
|
993
|
+
if line && yield(line)
|
994
|
+
rm << f
|
995
|
+
|
996
|
+
# Hack - because we treat N and FN as one field
|
997
|
+
if f.name? 'N'
|
998
|
+
rm << field('FN')
|
999
|
+
end
|
1000
|
+
end
|
1001
|
+
end
|
1002
|
+
|
1003
|
+
rm.each do |f|
|
1004
|
+
@fields.delete( f )
|
1005
|
+
@cache.delete( f )
|
1006
|
+
end
|
1007
|
+
|
239
1008
|
end
|
240
1009
|
|
241
|
-
#
|
242
|
-
# field, and raises an error if the birthday field could not be expressed
|
243
|
-
# as a recurring event.
|
1010
|
+
# A class to make and make changes to vCards.
|
244
1011
|
#
|
245
|
-
#
|
246
|
-
#
|
247
|
-
#
|
248
|
-
#
|
249
|
-
|
250
|
-
|
1012
|
+
# It can be used to create completely new vCards using Vcard#make2.
|
1013
|
+
#
|
1014
|
+
# Its is also yielded from Vpim::Vcard#make, in which case it allows a kind
|
1015
|
+
# of transactional approach to changing vCards, so their values can be
|
1016
|
+
# validated after any changes have been made.
|
1017
|
+
#
|
1018
|
+
# Examples:
|
1019
|
+
# - link:ex_mkvcard.txt: example of creating a vCard
|
1020
|
+
# - link:ex_cpvcard.txt: example of copying and them modifying a vCard
|
1021
|
+
# - link:ex_mkv21vcard.txt: example of creating version 2.1 vCard
|
1022
|
+
# - link:ex_mkyourown.txt: example of adding support for new fields to Vcard::Maker
|
1023
|
+
class Maker
|
1024
|
+
# Make a vCard.
|
1025
|
+
#
|
1026
|
+
# Yields +maker+, a Vpim::Vcard::Maker which allows fields to be added to
|
1027
|
+
# +card+, and returns +card+, a Vpim::Vcard.
|
1028
|
+
#
|
1029
|
+
# If +card+ is nil or not provided a new Vpim::Vcard is created and the
|
1030
|
+
# fields are added to it.
|
1031
|
+
#
|
1032
|
+
# Defaults:
|
1033
|
+
# - vCards must have both an N and an FN field, #make2 will fail if there
|
1034
|
+
# is no N field in the +card+ when your block is finished adding fields.
|
1035
|
+
# - If there is an N field, but no FN field, FN will be set from the
|
1036
|
+
# information in N, see Vcard::Name#preformatted for more information.
|
1037
|
+
# - vCards must have a VERSION field. If one does not exist when your block is
|
1038
|
+
# is finished it will be set to 3.0.
|
1039
|
+
def self.make2(card = Vpim::Vcard.create, &block) # :yields: maker
|
1040
|
+
new(nil, card).make(&block)
|
1041
|
+
end
|
251
1042
|
|
252
|
-
|
1043
|
+
# Deprecated, use #make2.
|
1044
|
+
#
|
1045
|
+
# If set, the FN field will be set to +full_name+. Otherwise, FN will
|
1046
|
+
# be set from the values in #name.
|
1047
|
+
def self.make(full_name = nil, &block) # :yields: maker
|
1048
|
+
new(full_name, Vpim::Vcard.create).make(&block)
|
1049
|
+
end
|
253
1050
|
|
254
|
-
|
255
|
-
|
1051
|
+
def make # :nodoc:
|
1052
|
+
yield self
|
1053
|
+
unless @card['N']
|
1054
|
+
raise Unencodeable, 'N field is mandatory'
|
1055
|
+
end
|
1056
|
+
fn = @card.field('FN')
|
1057
|
+
if fn && fn.value.strip.length == 0
|
1058
|
+
@card.delete(fn)
|
1059
|
+
fn = nil
|
1060
|
+
end
|
1061
|
+
unless fn
|
1062
|
+
@card << Vpim::DirectoryInfo::Field.create('FN', Vpim::Vcard::Name.new(@card['N'], '').formatted)
|
1063
|
+
end
|
1064
|
+
unless @card['VERSION']
|
1065
|
+
@card << Vpim::DirectoryInfo::Field.create('VERSION', "3.0")
|
1066
|
+
end
|
1067
|
+
@card
|
1068
|
+
end
|
256
1069
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
1070
|
+
private
|
1071
|
+
|
1072
|
+
def initialize(full_name, card) # :nodoc:
|
1073
|
+
@card = card || Vpim::Vcard::create
|
1074
|
+
if full_name
|
1075
|
+
@card << Vpim::DirectoryInfo::Field.create('FN', full_name.strip )
|
1076
|
+
end
|
1077
|
+
end
|
1078
|
+
|
1079
|
+
public
|
1080
|
+
|
1081
|
+
# def add_name # :yield: n
|
1082
|
+
# # FIXME: Each attribute can currently only have a single String value.
|
1083
|
+
# # FIXME: Need to escape specials in the String.
|
1084
|
+
# x = Struct.new(:family, :given, :additional, :prefix, :suffix).new
|
1085
|
+
# yield x
|
1086
|
+
# @card << Vpim::DirectoryInfo::Field.create(
|
1087
|
+
# 'N',
|
1088
|
+
# x.map { |s| s ? s.to_str : '' }
|
1089
|
+
# )
|
1090
|
+
# self
|
1091
|
+
# end
|
1092
|
+
# Set with #name now.
|
1093
|
+
# Use m.name do |n| n.fullname = foo end
|
1094
|
+
def fullname=(fullname) #:nodoc: bacwards compat
|
1095
|
+
if @card.field('FN')
|
1096
|
+
raise Vpim::InvalidEncodingError, "Not allowed to add more than one FN field to a vCard."
|
1097
|
+
end
|
1098
|
+
@card << Vpim::DirectoryInfo::Field.create( 'FN', fullname );
|
1099
|
+
end
|
1100
|
+
|
1101
|
+
# Set the name fields, N and FN.
|
1102
|
+
#
|
1103
|
+
# Attributes of +name+ are:
|
1104
|
+
# - family: family name
|
1105
|
+
# - given: given name
|
1106
|
+
# - additional: additional names
|
1107
|
+
# - prefix: such as "Ms." or "Dr."
|
1108
|
+
# - suffix: such as "BFA", or "Sensei"
|
1109
|
+
#
|
1110
|
+
# +name+ is a Vcard::Name.
|
1111
|
+
#
|
1112
|
+
# All attributes are optional, though have all names be zero-length
|
1113
|
+
# strings isn't really in the spirit of things. FN's value will be set
|
1114
|
+
# to Vcard::Name#formatted if Vcard::Name#fullname isn't given a specific
|
1115
|
+
# value.
|
1116
|
+
#
|
1117
|
+
# Warning: This is the only mandatory field.
|
1118
|
+
def name #:yield:name
|
1119
|
+
x = begin
|
1120
|
+
@card.name.dup
|
1121
|
+
rescue
|
1122
|
+
Vpim::Vcard::Name.new
|
1123
|
+
end
|
1124
|
+
|
1125
|
+
fn = x.fullname
|
1126
|
+
|
1127
|
+
yield x
|
1128
|
+
|
1129
|
+
x.fullname.strip!
|
1130
|
+
|
1131
|
+
delete_if do |line|
|
1132
|
+
line.name == 'N'
|
1133
|
+
end
|
1134
|
+
|
1135
|
+
@card << x.encode
|
1136
|
+
@card << x.encode_fn
|
1137
|
+
|
1138
|
+
self
|
1139
|
+
end
|
1140
|
+
|
1141
|
+
alias :add_name :name #:nodoc: backwards compatibility
|
1142
|
+
|
1143
|
+
# Add an address field, ADR. +address+ is a Vpim::Vcard::Address.
|
1144
|
+
def add_addr # :yield: address
|
1145
|
+
x = Vpim::Vcard::Address.new
|
1146
|
+
yield x
|
1147
|
+
@card << x.encode
|
1148
|
+
self
|
1149
|
+
end
|
1150
|
+
|
1151
|
+
# Add a telephone field, TEL. +tel+ is a Vpim::Vcard::Telephone.
|
1152
|
+
#
|
1153
|
+
# The block is optional, its only necessary if you want to specify
|
1154
|
+
# the optional attributes.
|
1155
|
+
def add_tel(number) # :yield: tel
|
1156
|
+
x = Vpim::Vcard::Telephone.new(number)
|
1157
|
+
if block_given?
|
1158
|
+
yield x
|
1159
|
+
end
|
1160
|
+
@card << x.encode
|
1161
|
+
self
|
1162
|
+
end
|
1163
|
+
|
1164
|
+
# Add an email field, EMAIL. +email+ is a Vpim::Vcard::Email.
|
1165
|
+
#
|
1166
|
+
# The block is optional, its only necessary if you want to specify
|
1167
|
+
# the optional attributes.
|
1168
|
+
def add_email(email) # :yield: email
|
1169
|
+
x = Vpim::Vcard::Email.new(email)
|
1170
|
+
if block_given?
|
1171
|
+
yield x
|
1172
|
+
end
|
1173
|
+
@card << x.encode
|
1174
|
+
self
|
1175
|
+
end
|
1176
|
+
|
1177
|
+
# Set the nickname field, NICKNAME.
|
1178
|
+
#
|
1179
|
+
# It can be set to a single String or an Array of String.
|
1180
|
+
def nickname=(nickname)
|
1181
|
+
delete_if { |l| l.name == 'NICKNAME' }
|
1182
|
+
|
1183
|
+
@card << Vpim::DirectoryInfo::Field.create( 'NICKNAME', nickname );
|
1184
|
+
end
|
1185
|
+
|
1186
|
+
# Add a birthday field, BDAY.
|
1187
|
+
#
|
1188
|
+
# +birthday+ must be a time or date object.
|
1189
|
+
#
|
1190
|
+
# Warning: It may confuse both humans and software if you add multiple
|
1191
|
+
# birthdays.
|
1192
|
+
def birthday=(birthday)
|
1193
|
+
if !birthday.respond_to? :month
|
1194
|
+
raise ArgumentError, 'birthday must be a date or time object.'
|
1195
|
+
end
|
1196
|
+
delete_if { |l| l.name == 'BDAY' }
|
1197
|
+
@card << Vpim::DirectoryInfo::Field.create( 'BDAY', birthday );
|
1198
|
+
end
|
1199
|
+
|
1200
|
+
# Add a note field, NOTE. The +note+ String can contain newlines, they
|
1201
|
+
# will be escaped.
|
1202
|
+
def add_note(note)
|
1203
|
+
@card << Vpim::DirectoryInfo::Field.create( 'NOTE', Vpim.encode_text(note) );
|
1204
|
+
end
|
1205
|
+
|
1206
|
+
# Add an instant-messaging/point of presence address field, IMPP. The address
|
1207
|
+
# is a URL, with the syntax depending on the protocol.
|
1208
|
+
#
|
1209
|
+
# Attributes of IMPP are:
|
1210
|
+
# - preferred: true - set if this is the preferred address
|
1211
|
+
# - location: home, work, mobile - location of address
|
1212
|
+
# - purpose: personal,business - purpose of communications
|
1213
|
+
#
|
1214
|
+
# All attributes are optional, and so is the block.
|
1215
|
+
#
|
1216
|
+
# The URL syntaxes for the messaging schemes is fairly complicated, so I
|
1217
|
+
# don't try and build the URLs here, maybe in the future. This forces
|
1218
|
+
# the user to know the URL for their own address, hopefully not too much
|
1219
|
+
# of a burden.
|
1220
|
+
#
|
1221
|
+
# IMPP is defined in draft-jennings-impp-vcard-04.txt. It refers to the
|
1222
|
+
# URI scheme of a number of messaging protocols, but doesn't give
|
1223
|
+
# references to all of them:
|
1224
|
+
# - "xmpp" indicates to use XMPP, draft-saintandre-xmpp-uri-06.txt
|
1225
|
+
# - "irc" or "ircs" indicates to use IRC, draft-butcher-irc-url-04.txt
|
1226
|
+
# - "sip" indicates to use SIP/SIMPLE, RFC 3261
|
1227
|
+
# - "im" or "pres" indicates to use a CPIM or CPP gateway, RFC 3860 and RFC 3859
|
1228
|
+
# - "ymsgr" indicates to use yahoo
|
1229
|
+
# - "msn" might indicate to use Microsoft messenger
|
1230
|
+
# - "aim" indicates to use AOL
|
1231
|
+
#
|
1232
|
+
def add_impp(url) # :yield: impp
|
1233
|
+
params = {}
|
1234
|
+
|
1235
|
+
if block_given?
|
1236
|
+
x = Struct.new( :location, :preferred, :purpose ).new
|
1237
|
+
|
1238
|
+
yield x
|
1239
|
+
|
1240
|
+
x[:preferred] = 'PREF' if x[:preferred]
|
1241
|
+
|
1242
|
+
types = x.to_a.flatten.compact.map { |s| s.downcase }.uniq
|
1243
|
+
|
1244
|
+
params['TYPE'] = types if types.first
|
1245
|
+
end
|
1246
|
+
|
1247
|
+
@card << Vpim::DirectoryInfo::Field.create( 'IMPP', url, params)
|
1248
|
+
self
|
1249
|
+
end
|
1250
|
+
|
1251
|
+
# Add an X-AIM account name where +xaim+ is an AIM screen name.
|
1252
|
+
#
|
1253
|
+
# I don't know if this is conventional, or supported by anything other
|
1254
|
+
# than AddressBook.app, but an example is:
|
1255
|
+
# X-AIM;type=HOME;type=pref:exampleaccount
|
1256
|
+
#
|
1257
|
+
# Attributes of X-AIM are:
|
1258
|
+
# - preferred: true - set if this is the preferred address
|
1259
|
+
# - location: home, work, mobile - location of address
|
1260
|
+
#
|
1261
|
+
# All attributes are optional, and so is the block.
|
1262
|
+
def add_x_aim(xaim) # :yield: xaim
|
1263
|
+
params = {}
|
1264
|
+
|
1265
|
+
if block_given?
|
1266
|
+
x = Struct.new( :location, :preferred ).new
|
1267
|
+
|
1268
|
+
yield x
|
1269
|
+
|
1270
|
+
x[:preferred] = 'PREF' if x[:preferred]
|
1271
|
+
|
1272
|
+
types = x.to_a.flatten.compact.map { |s| s.downcase }.uniq
|
1273
|
+
|
1274
|
+
params['TYPE'] = types if types.first
|
1275
|
+
end
|
1276
|
+
|
1277
|
+
@card << Vpim::DirectoryInfo::Field.create( 'X-AIM', xaim, params)
|
1278
|
+
self
|
1279
|
+
end
|
1280
|
+
|
1281
|
+
|
1282
|
+
# Add a photo field, PHOTO.
|
1283
|
+
#
|
1284
|
+
# Attributes of PHOTO are:
|
1285
|
+
# - image: set to image data to include inline
|
1286
|
+
# - link: set to the URL of the image data
|
1287
|
+
# - type: string identifying the image type, supposed to be an "IANA registered image format",
|
1288
|
+
# or a non-registered image format (usually these start with an x-)
|
1289
|
+
#
|
1290
|
+
# An error will be raised if neither image or link is set, or if both image
|
1291
|
+
# and link is set.
|
1292
|
+
#
|
1293
|
+
# Setting type is optional for a link image, because either the URL, the
|
1294
|
+
# image file extension, or a HTTP Content-Type may specify the type. If
|
1295
|
+
# it's not a link, setting type is mandatory, though it can be set to an
|
1296
|
+
# empty string, <code>''</code>, if the type is unknown.
|
1297
|
+
#
|
1298
|
+
# TODO - I'm not sure about this API. I'm thinking maybe it should be
|
1299
|
+
# #add_photo(image, type), and that I should detect when the image is a
|
1300
|
+
# URL, and make type mandatory if it wasn't a URL.
|
1301
|
+
def add_photo # :yield: photo
|
1302
|
+
x = Struct.new(:image, :link, :type).new
|
1303
|
+
yield x
|
1304
|
+
if x[:image] && x[:link]
|
1305
|
+
raise Vpim::InvalidEncodingError, 'Image is not allowed to be both inline and a link.'
|
1306
|
+
end
|
1307
|
+
|
1308
|
+
value = x[:image] || x[:link]
|
1309
|
+
|
1310
|
+
if !value
|
1311
|
+
raise Vpim::InvalidEncodingError, 'A image link or inline data must be provided.'
|
1312
|
+
end
|
1313
|
+
|
1314
|
+
params = {}
|
1315
|
+
|
1316
|
+
# Don't set type to the empty string.
|
1317
|
+
params['TYPE'] = x[:type] if( x[:type] && x[:type].length > 0 )
|
1318
|
+
|
1319
|
+
if x[:link]
|
1320
|
+
params['VALUE'] = 'URI'
|
1321
|
+
else # it's inline, base-64 encode it
|
1322
|
+
params['ENCODING'] = :b64
|
1323
|
+
if !x[:type]
|
1324
|
+
raise Vpim::InvalidEncodingError, 'Inline image data must have it\'s type set.'
|
264
1325
|
end
|
265
|
-
|
1326
|
+
end
|
1327
|
+
|
1328
|
+
@card << Vpim::DirectoryInfo::Field.create( 'PHOTO', value, params )
|
1329
|
+
self
|
1330
|
+
end
|
1331
|
+
|
1332
|
+
# Add a URL field, URL.
|
1333
|
+
def add_url(url)
|
1334
|
+
@card << Vpim::DirectoryInfo::Field.create( 'URL', url.to_str );
|
1335
|
+
end
|
1336
|
+
|
1337
|
+
# Add a Field, +field+.
|
1338
|
+
def add_field(field)
|
1339
|
+
fieldname = field.name.upcase
|
1340
|
+
case
|
1341
|
+
when [ 'BEGIN', 'END' ].include?(fieldname)
|
1342
|
+
raise Vpim::InvalidEncodingError, "Not allowed to manually add #{field.name} to a vCard."
|
1343
|
+
|
1344
|
+
when [ 'VERSION', 'N', 'FN' ].include?(fieldname)
|
1345
|
+
if @card.field(fieldname)
|
1346
|
+
raise Vpim::InvalidEncodingError, "Not allowed to add more than one #{fieldname} to a vCard."
|
1347
|
+
end
|
1348
|
+
@card << field
|
1349
|
+
|
266
1350
|
else
|
267
|
-
|
1351
|
+
@card << field
|
268
1352
|
end
|
269
1353
|
end
|
270
1354
|
|
271
|
-
|
272
|
-
|
1355
|
+
# Copy the fields from +card+ into self using #add_field. If a block is
|
1356
|
+
# provided, each Field from +card+ is yielded. The block should return a
|
1357
|
+
# Field to add, or nil. The Field doesn't have to be the one yielded,
|
1358
|
+
# allowing the field to be copied and modified (see Field#copy) before adding, or
|
1359
|
+
# not added at all if the block yields nil.
|
1360
|
+
#
|
1361
|
+
# The vCard fields BEGIN and END aren't copied, and VERSION, N, and FN are copied
|
1362
|
+
# only if the card doesn't have them already.
|
1363
|
+
def copy(card) # :yields: Field
|
1364
|
+
card.each do |field|
|
1365
|
+
fieldname = field.name.upcase
|
1366
|
+
case
|
1367
|
+
when [ 'BEGIN', 'END' ].include?(fieldname)
|
1368
|
+
# Never copy these
|
1369
|
+
|
1370
|
+
when [ 'VERSION', 'N', 'FN' ].include?(fieldname) && @card.field(fieldname)
|
1371
|
+
# Copy these only if they don't already exist.
|
1372
|
+
|
1373
|
+
else
|
1374
|
+
if block_given?
|
1375
|
+
field = yield field
|
1376
|
+
end
|
1377
|
+
|
1378
|
+
if field
|
1379
|
+
add_field(field)
|
1380
|
+
end
|
1381
|
+
end
|
1382
|
+
end
|
1383
|
+
end
|
1384
|
+
|
1385
|
+
# Delete +line+ if block yields true.
|
1386
|
+
def delete_if #:yield: line
|
1387
|
+
begin
|
1388
|
+
@card.delete_if do |line|
|
1389
|
+
yield line
|
1390
|
+
end
|
1391
|
+
rescue NoMethodError
|
1392
|
+
# FIXME - this is a hideous hack, allowing a DirectoryInfo to
|
1393
|
+
# be passed instead of a Vcard, and for it to almost work. Yuck.
|
1394
|
+
end
|
1395
|
+
end
|
273
1396
|
|
1397
|
+
end
|
274
1398
|
end
|
275
1399
|
end
|
276
1400
|
|