vcard 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.gitignore +7 -0
- data/LICENSE +58 -0
- data/README.rdoc +7 -0
- data/Rakefile +58 -0
- data/VERSION +1 -0
- data/lib/vcard.rb +34 -0
- data/lib/vcard/attachment.rb +100 -0
- data/lib/vcard/dirinfo.rb +272 -0
- data/lib/vcard/enumerator.rb +30 -0
- data/lib/vcard/field.rb +610 -0
- data/lib/vcard/rfc2425.rb +367 -0
- data/lib/vcard/vcard.rb +1423 -0
- data/test/field_test.rb +152 -0
- data/test/test_helper.rb +10 -0
- data/test/vcard_test.rb +967 -0
- metadata +73 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (C) 2008 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
|
+
# This is a way for an object to have multiple ways of being enumerated via
|
11
|
+
# argument to it's #each() method. An Enumerator mixes in Enumerable, so the
|
12
|
+
# standard APIs such as Enumerable#map(), Enumerable#to_a(), and
|
13
|
+
# Enumerable#find_all() can be used on it.
|
14
|
+
#
|
15
|
+
# TODO since 1.8, this is part of the standard library, I should rewrite vPim
|
16
|
+
# so this can be removed.
|
17
|
+
class Enumerator
|
18
|
+
include Enumerable
|
19
|
+
|
20
|
+
def initialize(obj, *args)
|
21
|
+
@obj = obj
|
22
|
+
@args = args
|
23
|
+
end
|
24
|
+
|
25
|
+
def each(&block)
|
26
|
+
@obj.each(*@args, &block)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
data/lib/vcard/field.rb
ADDED
@@ -0,0 +1,610 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (C) 2008 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
|
+
|
11
|
+
class DirectoryInfo
|
12
|
+
|
13
|
+
# A field in a directory info object.
|
14
|
+
class Field
|
15
|
+
# TODO
|
16
|
+
# - Field should know which param values and field values are
|
17
|
+
# case-insensitive, configurably, so it can down case them
|
18
|
+
# - perhaps should have pvalue_set/del/add, perhaps case-insensitive, or
|
19
|
+
# pvalue_iset/idel/iadd, where set sets them all, add adds if not present,
|
20
|
+
# and del deletes any that are present
|
21
|
+
# - I really, really, need a case-insensitive string...
|
22
|
+
# - should allow nil as a field value, its not the same as '', if there is
|
23
|
+
# more than one pvalue, the empty string will show up. This isn't strictly
|
24
|
+
# disallowed, but its odd. Should also strip empty strings on decoding, if
|
25
|
+
# I don't already.
|
26
|
+
private_class_method :new
|
27
|
+
|
28
|
+
def Field.create_array(fields)
|
29
|
+
case fields
|
30
|
+
when Hash
|
31
|
+
fields.map do |name,value|
|
32
|
+
DirectoryInfo::Field.create( name, value )
|
33
|
+
end
|
34
|
+
else
|
35
|
+
fields.to_ary
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Encode a field.
|
40
|
+
def Field.encode0(group, name, params={}, value='') # :nodoc:
|
41
|
+
line = ""
|
42
|
+
|
43
|
+
# A reminder of the line format:
|
44
|
+
# [<group>.]<name>;<pname>=<pvalue>,<pvalue>:<value>
|
45
|
+
|
46
|
+
if group
|
47
|
+
line << group << '.'
|
48
|
+
end
|
49
|
+
|
50
|
+
line << name
|
51
|
+
|
52
|
+
params.each do |pname, pvalues|
|
53
|
+
|
54
|
+
unless pvalues.respond_to? :to_ary
|
55
|
+
pvalues = [ pvalues ]
|
56
|
+
end
|
57
|
+
|
58
|
+
line << ';' << pname << '='
|
59
|
+
|
60
|
+
sep = "" # set to ',' after one pvalue has been appended
|
61
|
+
|
62
|
+
pvalues.each do |pvalue|
|
63
|
+
# check if we need to do any encoding
|
64
|
+
if pname.casecmp('ENCODING') == 0 && pvalue == :b64
|
65
|
+
pvalue = 'B' # the RFC definition of the base64 param value
|
66
|
+
value = [ value.to_str ].pack('m').gsub("\n", '')
|
67
|
+
end
|
68
|
+
|
69
|
+
line << sep << pvalue
|
70
|
+
sep =",";
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
line << ':'
|
75
|
+
|
76
|
+
line << Field.value_str(value)
|
77
|
+
|
78
|
+
line
|
79
|
+
end
|
80
|
+
|
81
|
+
def Field.value_str(value) # :nodoc:
|
82
|
+
line = ''
|
83
|
+
case value
|
84
|
+
when Date
|
85
|
+
line << Vpim.encode_date(value)
|
86
|
+
|
87
|
+
when Time #, DateTime
|
88
|
+
line << Vpim.encode_date_time(value)
|
89
|
+
|
90
|
+
when Array
|
91
|
+
line << value.map { |v| Field.value_str(v) }.join(';')
|
92
|
+
|
93
|
+
when Symbol
|
94
|
+
line << value
|
95
|
+
|
96
|
+
else
|
97
|
+
# FIXME - somewhere along here, values with special chars need escaping...
|
98
|
+
line << value.to_str
|
99
|
+
end
|
100
|
+
line
|
101
|
+
end
|
102
|
+
|
103
|
+
# Decode a field.
|
104
|
+
def Field.decode0(atline) # :nodoc:
|
105
|
+
unless atline =~ %r{#{Bnf::LINE}}i
|
106
|
+
raise Vpim::InvalidEncodingError, atline
|
107
|
+
end
|
108
|
+
|
109
|
+
atgroup = $1.upcase
|
110
|
+
atname = $2.upcase
|
111
|
+
paramslist = $3
|
112
|
+
atvalue = $~[-1]
|
113
|
+
|
114
|
+
# I've seen space that shouldn't be there, as in "BEGIN:VCARD ", so
|
115
|
+
# strip it. I'm not absolutely sure this is allowed... it certainly
|
116
|
+
# breaks round-trip encoding.
|
117
|
+
atvalue.strip!
|
118
|
+
|
119
|
+
if atgroup.length > 0
|
120
|
+
atgroup.chomp!('.')
|
121
|
+
else
|
122
|
+
atgroup = nil
|
123
|
+
end
|
124
|
+
|
125
|
+
atparams = {}
|
126
|
+
|
127
|
+
# Collect the params, if any.
|
128
|
+
if paramslist.size > 1
|
129
|
+
|
130
|
+
# v3.0 and v2.1 params
|
131
|
+
paramslist.scan( %r{#{Bnf::PARAM}}i ) do
|
132
|
+
|
133
|
+
# param names are case-insensitive, and multi-valued
|
134
|
+
name = $1.upcase
|
135
|
+
params = $3
|
136
|
+
|
137
|
+
# v2.1 params have no '=' sign, figure out what kind of param it
|
138
|
+
# is (either its a known encoding, or we treat it as a 'TYPE'
|
139
|
+
# param).
|
140
|
+
|
141
|
+
if $2 == ""
|
142
|
+
params = $1
|
143
|
+
case $1
|
144
|
+
when /quoted-printable/i
|
145
|
+
name = 'ENCODING'
|
146
|
+
|
147
|
+
when /base64/i
|
148
|
+
name = 'ENCODING'
|
149
|
+
|
150
|
+
else
|
151
|
+
name = 'TYPE'
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# TODO - In ruby1.8 I can give an initial value to the atparams
|
156
|
+
# hash values instead of this.
|
157
|
+
unless atparams.key? name
|
158
|
+
atparams[name] = []
|
159
|
+
end
|
160
|
+
|
161
|
+
params.scan( %r{#{Bnf::PVALUE}} ) do
|
162
|
+
atparams[name] << ($1 || $2)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
[ atgroup, atname, atparams, atvalue ]
|
168
|
+
end
|
169
|
+
|
170
|
+
def initialize(line) # :nodoc:
|
171
|
+
@line = line.to_str
|
172
|
+
@group, @name, @params, @value = Field.decode0(@line)
|
173
|
+
|
174
|
+
@params.each do |pname,pvalues|
|
175
|
+
pvalues.freeze
|
176
|
+
end
|
177
|
+
self
|
178
|
+
end
|
179
|
+
|
180
|
+
# Create a field by decoding +line+, a String which must already be
|
181
|
+
# unfolded. Decoded fields are frozen, but see #copy().
|
182
|
+
def Field.decode(line)
|
183
|
+
new(line).freeze
|
184
|
+
end
|
185
|
+
|
186
|
+
# Create a field with name +name+ (a String), value +value+ (see below),
|
187
|
+
# and optional parameters, +params+. +params+ is a hash of the parameter
|
188
|
+
# name (a String) to either a single string or symbol, or an array of
|
189
|
+
# strings and symbols (parameters can be multi-valued).
|
190
|
+
#
|
191
|
+
# If 'ENCODING' => :b64 is specified as a parameter, the value will be
|
192
|
+
# base-64 encoded. If it's already base-64 encoded, then use String
|
193
|
+
# values ('ENCODING' => 'B'), and no further encoding will be done by
|
194
|
+
# this routine.
|
195
|
+
#
|
196
|
+
# Currently handled value types are:
|
197
|
+
# - Time, encoded as a date-time value
|
198
|
+
# - Date, encoded as a date value
|
199
|
+
# - String, encoded directly
|
200
|
+
# - Array of String, concatentated with ';' between them.
|
201
|
+
#
|
202
|
+
# TODO - need a way to encode String values as TEXT, at least optionally,
|
203
|
+
# so as to escape special chars, etc.
|
204
|
+
def Field.create(name, value="", params={})
|
205
|
+
line = Field.encode0(nil, name, params, value)
|
206
|
+
|
207
|
+
begin
|
208
|
+
new(line)
|
209
|
+
rescue Vpim::InvalidEncodingError => e
|
210
|
+
raise ArgumentError, e.to_s
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# Create a copy of Field. If the original Field was frozen, this one
|
215
|
+
# won't be.
|
216
|
+
def copy
|
217
|
+
Marshal.load(Marshal.dump(self))
|
218
|
+
end
|
219
|
+
|
220
|
+
# The String encoding of the Field. The String will be wrapped to a
|
221
|
+
# maximum line width of +width+, where +0+ means no wrapping, and nil is
|
222
|
+
# to accept the default wrapping (75, recommended by RFC2425).
|
223
|
+
#
|
224
|
+
# Note: AddressBook.app 3.0.3 neither understands to unwrap lines when it
|
225
|
+
# imports vCards (it treats them as raw new-line characters), nor wraps
|
226
|
+
# long lines on export. This is mostly a cosmetic problem, but wrapping
|
227
|
+
# can be disabled by setting width to +0+, if desired.
|
228
|
+
#
|
229
|
+
# FIXME - breaks round-trip encoding, need to change this to not wrap
|
230
|
+
# fields that are already wrapped.
|
231
|
+
def encode(width=nil)
|
232
|
+
width = 75 unless width
|
233
|
+
l = @line
|
234
|
+
# Wrap to width, unless width is zero.
|
235
|
+
if width > 0
|
236
|
+
l = l.gsub(/.{#{width},#{width}}/) { |m| m + "\n " }
|
237
|
+
end
|
238
|
+
# Make sure it's terminated with no more than a single NL.
|
239
|
+
l.gsub(/\s*\z/, '') + "\n"
|
240
|
+
end
|
241
|
+
|
242
|
+
alias to_s encode
|
243
|
+
|
244
|
+
# The name.
|
245
|
+
def name
|
246
|
+
@name
|
247
|
+
end
|
248
|
+
|
249
|
+
# The group, if present, or nil if not present.
|
250
|
+
def group
|
251
|
+
@group
|
252
|
+
end
|
253
|
+
|
254
|
+
# An Array of all the param names.
|
255
|
+
def pnames
|
256
|
+
@params.keys
|
257
|
+
end
|
258
|
+
|
259
|
+
# FIXME - remove my own uses of #params
|
260
|
+
alias params pnames # :nodoc:
|
261
|
+
|
262
|
+
# The first value of the param +name+, nil if there is no such param,
|
263
|
+
# the param has no value, or the first param value is zero-length.
|
264
|
+
def pvalue(name)
|
265
|
+
v = pvalues( name )
|
266
|
+
if v
|
267
|
+
v = v.first
|
268
|
+
end
|
269
|
+
if v
|
270
|
+
v = nil unless v.length > 0
|
271
|
+
end
|
272
|
+
v
|
273
|
+
end
|
274
|
+
|
275
|
+
# The Array of all values of the param +name+, nil if there is no such
|
276
|
+
# param, [] if the param has no values. If the Field isn't frozen, the
|
277
|
+
# Array is mutable.
|
278
|
+
def pvalues(name)
|
279
|
+
@params[name.upcase]
|
280
|
+
end
|
281
|
+
|
282
|
+
# FIXME - remove my own uses of #param
|
283
|
+
alias param pvalues # :nodoc:
|
284
|
+
|
285
|
+
alias [] pvalues
|
286
|
+
|
287
|
+
# Yield once for each param, +name+ is the parameter name, +value+ is an
|
288
|
+
# array of the parameter values.
|
289
|
+
def each_param(&block) #:yield: name, value
|
290
|
+
if @params
|
291
|
+
@params.each(&block)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
# The decoded value.
|
296
|
+
#
|
297
|
+
# The encoding specified by the #encoding, if any, is stripped.
|
298
|
+
#
|
299
|
+
# Note: Both the RFC 2425 encoding param ("b", meaning base-64) and the
|
300
|
+
# vCard 2.1 encoding params ("base64", "quoted-printable", "8bit", and
|
301
|
+
# "7bit") are supported.
|
302
|
+
#
|
303
|
+
# FIXME:
|
304
|
+
# - should use the VALUE parameter
|
305
|
+
# - should also take a default value type, so it can be converted
|
306
|
+
# if VALUE parameter is not present.
|
307
|
+
def value
|
308
|
+
case encoding
|
309
|
+
when nil, '8BIT', '7BIT' then @value
|
310
|
+
|
311
|
+
# Hack - if the base64 lines started with 2 SPC chars, which is invalid,
|
312
|
+
# there will be extra spaces in @value. Since no SPC chars show up in
|
313
|
+
# b64 encodings, they can be safely stripped out before unpacking.
|
314
|
+
when 'B', 'BASE64' then @value.gsub(' ', '').unpack('m*').first
|
315
|
+
|
316
|
+
when 'QUOTED-PRINTABLE' then @value.unpack('M*').first
|
317
|
+
|
318
|
+
else
|
319
|
+
raise Vpim::InvalidEncodingError, "unrecognized encoding (#{encoding})"
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
# Is the #name of this Field +name+? Names are case insensitive.
|
324
|
+
def name?(name)
|
325
|
+
@name.casecmp(name) == 0
|
326
|
+
end
|
327
|
+
|
328
|
+
# Is the #group of this field +group+? Group names are case insensitive.
|
329
|
+
# A +group+ of nil matches if the field has no group.
|
330
|
+
def group?(group)
|
331
|
+
@group.casecmp(group) == 0
|
332
|
+
end
|
333
|
+
|
334
|
+
# Is the value of this field of type +kind+? RFC2425 allows the type of
|
335
|
+
# a fields value to be encoded in the VALUE parameter. Don't rely on its
|
336
|
+
# presence, they aren't required, and usually aren't bothered with. In
|
337
|
+
# cases where the kind of value might vary (an iCalendar DTSTART can be
|
338
|
+
# either a date or a date-time, for example), you are more likely to see
|
339
|
+
# the kind of value specified explicitly.
|
340
|
+
#
|
341
|
+
# The value types defined by RFC 2425 are:
|
342
|
+
# - uri:
|
343
|
+
# - text:
|
344
|
+
# - date: a list of 1 or more dates
|
345
|
+
# - time: a list of 1 or more times
|
346
|
+
# - date-time: a list of 1 or more date-times
|
347
|
+
# - integer:
|
348
|
+
# - boolean:
|
349
|
+
# - float:
|
350
|
+
def kind?(kind)
|
351
|
+
self.kind.casecmp(kind) == 0
|
352
|
+
end
|
353
|
+
|
354
|
+
# Is one of the values of the TYPE parameter of this field +type+? The
|
355
|
+
# type parameter values are case insensitive. False if there is no TYPE
|
356
|
+
# parameter.
|
357
|
+
#
|
358
|
+
# TYPE parameters are used for general categories, such as
|
359
|
+
# distinguishing between an email address used at home or at work.
|
360
|
+
def type?(type)
|
361
|
+
type = type.to_str
|
362
|
+
|
363
|
+
types = param('TYPE')
|
364
|
+
|
365
|
+
if types
|
366
|
+
types = types.detect { |t| t.casecmp(type) == 0 }
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
# Is this field marked as preferred? A vCard field is preferred if
|
371
|
+
# #type?('PREF'). This method is not necessarily meaningful for
|
372
|
+
# non-vCard profiles.
|
373
|
+
def pref?
|
374
|
+
type? 'PREF'
|
375
|
+
end
|
376
|
+
|
377
|
+
# Set whether a field is marked as preferred. See #pref?
|
378
|
+
def pref=(ispref)
|
379
|
+
if ispref
|
380
|
+
pvalue_iadd('TYPE', 'PREF')
|
381
|
+
else
|
382
|
+
pvalue_idel('TYPE', 'PREF')
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
# Is the value of this field +value+? The check is case insensitive.
|
387
|
+
# FIXME - it shouldn't be insensitive, make a #casevalue? method.
|
388
|
+
def value?(value)
|
389
|
+
@value.casecmp(value) == 0
|
390
|
+
end
|
391
|
+
|
392
|
+
# The value of the ENCODING parameter, if present, or nil if not
|
393
|
+
# present.
|
394
|
+
def encoding
|
395
|
+
e = param('ENCODING')
|
396
|
+
|
397
|
+
if e
|
398
|
+
if e.length > 1
|
399
|
+
raise Vpim::InvalidEncodingError, "multi-valued param 'ENCODING' (#{e})"
|
400
|
+
end
|
401
|
+
e = e.first.upcase
|
402
|
+
end
|
403
|
+
e
|
404
|
+
end
|
405
|
+
|
406
|
+
# The type of the value, as specified by the VALUE parameter, nil if
|
407
|
+
# unspecified.
|
408
|
+
def kind
|
409
|
+
v = param('VALUE')
|
410
|
+
if v
|
411
|
+
if v.size > 1
|
412
|
+
raise InvalidEncodingError, "multi-valued param 'VALUE' (#{values})"
|
413
|
+
end
|
414
|
+
v = v.first.downcase
|
415
|
+
end
|
416
|
+
v
|
417
|
+
end
|
418
|
+
|
419
|
+
# The value as an array of Time objects (all times and dates in
|
420
|
+
# RFC2425 are lists, even where it might not make sense, such as a
|
421
|
+
# birthday). The time will be UTC if marked as so (with a timezone of
|
422
|
+
# "Z"), and in localtime otherwise.
|
423
|
+
#
|
424
|
+
# TODO - support timezone offsets
|
425
|
+
#
|
426
|
+
# TODO - if year is before 1970, this won't work... but some people
|
427
|
+
# are generating calendars saying Canada Day started in 1753!
|
428
|
+
# That's just wrong! So, what to do? I add a message
|
429
|
+
# saying what the year is that breaks, so they at least know that
|
430
|
+
# its ridiculous! I think I need my own DateTime variant.
|
431
|
+
def to_time
|
432
|
+
begin
|
433
|
+
Vpim.decode_date_time_list(value).collect do |d|
|
434
|
+
# We get [ year, month, day, hour, min, sec, usec, tz ]
|
435
|
+
begin
|
436
|
+
if(d.pop == "Z")
|
437
|
+
Time.gm(*d)
|
438
|
+
else
|
439
|
+
Time.local(*d)
|
440
|
+
end
|
441
|
+
rescue ArgumentError => e
|
442
|
+
raise Vpim::InvalidEncodingError, "Time.gm(#{d.join(', ')}) failed with #{e.message}"
|
443
|
+
end
|
444
|
+
end
|
445
|
+
rescue Vpim::InvalidEncodingError
|
446
|
+
Vpim.decode_date_list(value).collect do |d|
|
447
|
+
# We get [ year, month, day ]
|
448
|
+
begin
|
449
|
+
Time.gm(*d)
|
450
|
+
rescue ArgumentError => e
|
451
|
+
raise Vpim::InvalidEncodingError, "Time.gm(#{d.join(', ')}) failed with #{e.message}"
|
452
|
+
end
|
453
|
+
end
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
# The value as an array of Date objects (all times and dates in
|
458
|
+
# RFC2425 are lists, even where it might not make sense, such as a
|
459
|
+
# birthday).
|
460
|
+
#
|
461
|
+
# The field value may be a list of either DATE or DATE-TIME values,
|
462
|
+
# decoding is tried first as a DATE-TIME, then as a DATE, if neither
|
463
|
+
# works an InvalidEncodingError will be raised.
|
464
|
+
def to_date
|
465
|
+
begin
|
466
|
+
Vpim.decode_date_time_list(value).collect do |d|
|
467
|
+
# We get [ year, month, day, hour, min, sec, usec, tz ]
|
468
|
+
Date.new(d[0], d[1], d[2])
|
469
|
+
end
|
470
|
+
rescue Vpim::InvalidEncodingError
|
471
|
+
Vpim.decode_date_list(value).collect do |d|
|
472
|
+
# We get [ year, month, day ]
|
473
|
+
Date.new(*d)
|
474
|
+
end
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
# The value as text. Text can have escaped newlines, commas, and escape
|
479
|
+
# characters, this method will strip them, if present.
|
480
|
+
#
|
481
|
+
# In theory, #value could also do this, but it would need to know that
|
482
|
+
# the value is of type 'TEXT', and often for text values the 'VALUE'
|
483
|
+
# parameter is not present, so knowledge of the expected type of the
|
484
|
+
# field is required from the decoder.
|
485
|
+
def to_text
|
486
|
+
Vpim.decode_text(value)
|
487
|
+
end
|
488
|
+
|
489
|
+
# The undecoded value, see +value+.
|
490
|
+
def value_raw
|
491
|
+
@value
|
492
|
+
end
|
493
|
+
|
494
|
+
# TODO def pretty_print() ...
|
495
|
+
|
496
|
+
# Set the group of this field to +group+.
|
497
|
+
def group=(group)
|
498
|
+
mutate(group, @name, @params, @value)
|
499
|
+
group
|
500
|
+
end
|
501
|
+
|
502
|
+
# Set the value of this field to +value+. Valid values are as in
|
503
|
+
# Field.create().
|
504
|
+
def value=(value)
|
505
|
+
mutate(@group, @name, @params, value)
|
506
|
+
value
|
507
|
+
end
|
508
|
+
|
509
|
+
# Convert +value+ to text, then assign.
|
510
|
+
#
|
511
|
+
# TODO - unimplemented
|
512
|
+
def text=(text)
|
513
|
+
end
|
514
|
+
|
515
|
+
# Set a the param +pname+'s value to +pvalue+, replacing any value it
|
516
|
+
# currently has. See Field.create() for a description of +pvalue+.
|
517
|
+
#
|
518
|
+
# Example:
|
519
|
+
# if field['TYPE']
|
520
|
+
# field['TYPE'] << 'HOME'
|
521
|
+
# else
|
522
|
+
# field['TYPE'] = [ 'HOME' ]
|
523
|
+
# end
|
524
|
+
#
|
525
|
+
# TODO - this could be an alias to #pvalue_set
|
526
|
+
def []=(pname,pvalue)
|
527
|
+
unless pvalue.respond_to?(:to_ary)
|
528
|
+
pvalue = [ pvalue ]
|
529
|
+
end
|
530
|
+
|
531
|
+
h = @params.dup
|
532
|
+
|
533
|
+
h[pname.upcase] = pvalue
|
534
|
+
|
535
|
+
mutate(@group, @name, h, @value)
|
536
|
+
pvalue
|
537
|
+
end
|
538
|
+
|
539
|
+
# Add +pvalue+ to the param +pname+'s value. The values are treated as a
|
540
|
+
# set so duplicate values won't occur, and String values are case
|
541
|
+
# insensitive. See Field.create() for a description of +pvalue+.
|
542
|
+
def pvalue_iadd(pname, pvalue)
|
543
|
+
pname = pname.upcase
|
544
|
+
|
545
|
+
# Get a uniq set, where strings are compared case-insensitively.
|
546
|
+
values = [ pvalue, @params[pname] ].flatten.compact
|
547
|
+
values = values.collect do |v|
|
548
|
+
if v.respond_to? :to_str
|
549
|
+
v = v.to_str.upcase
|
550
|
+
end
|
551
|
+
v
|
552
|
+
end
|
553
|
+
values.uniq!
|
554
|
+
|
555
|
+
h = @params.dup
|
556
|
+
|
557
|
+
h[pname] = values
|
558
|
+
|
559
|
+
mutate(@group, @name, h, @value)
|
560
|
+
values
|
561
|
+
end
|
562
|
+
|
563
|
+
# Delete +pvalue+ from the param +pname+'s value. The values are treated
|
564
|
+
# as a set so duplicate values won't occur, and String values are case
|
565
|
+
# insensitive. +pvalue+ must be a single String or Symbol.
|
566
|
+
def pvalue_idel(pname, pvalue)
|
567
|
+
pname = pname.upcase
|
568
|
+
if pvalue.respond_to? :to_str
|
569
|
+
pvalue = pvalue.to_str.downcase
|
570
|
+
end
|
571
|
+
|
572
|
+
# Get a uniq set, where strings are compared case-insensitively.
|
573
|
+
values = [ nil, @params[pname] ].flatten.compact
|
574
|
+
values = values.collect do |v|
|
575
|
+
if v.respond_to? :to_str
|
576
|
+
v = v.to_str.downcase
|
577
|
+
end
|
578
|
+
v
|
579
|
+
end
|
580
|
+
values.uniq!
|
581
|
+
values.delete pvalue
|
582
|
+
|
583
|
+
h = @params.dup
|
584
|
+
|
585
|
+
h[pname] = values
|
586
|
+
|
587
|
+
mutate(@group, @name, h, @value)
|
588
|
+
values
|
589
|
+
end
|
590
|
+
|
591
|
+
# FIXME - should change this so it doesn't assign to @line here, so @line
|
592
|
+
# is used to preserve original encoding. That way, #encode can only wrap
|
593
|
+
# new fields, not old fields.
|
594
|
+
def mutate(g, n, p, v) #:nodoc:
|
595
|
+
line = Field.encode0(g, n, p, v)
|
596
|
+
|
597
|
+
begin
|
598
|
+
@group, @name, @params, @value = Field.decode0(line)
|
599
|
+
@line = line
|
600
|
+
rescue Vpim::InvalidEncodingError => e
|
601
|
+
raise ArgumentError, e.to_s
|
602
|
+
end
|
603
|
+
self
|
604
|
+
end
|
605
|
+
|
606
|
+
private :mutate
|
607
|
+
end
|
608
|
+
end
|
609
|
+
end
|
610
|
+
|