scashin133-vpim 9.4.0

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