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/repo.rb ADDED
@@ -0,0 +1,261 @@
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 'enumerator'
10
+ require "net/http"
11
+
12
+ require 'plist'
13
+
14
+ require 'vpim/icalendar'
15
+ require 'vpim/duration'
16
+
17
+ module Vpim
18
+ # A Repo is a representation of a calendar repository.
19
+ #
20
+ # Currently supported repository types are:
21
+ # - Repo::Apple3, an Apple iCal3 repository.
22
+ # - Repo::Directory, a directory hierarchy containing .ics files
23
+ # - Repo::Uri, a URI that identifies a single iCalendar
24
+ #
25
+ # All repository types support at least the methods of Repo, and all
26
+ # repositories return calendars that support at least the methods of
27
+ # Repo::Calendar.
28
+ class Repo
29
+ include Enumerable
30
+
31
+ # Open a repository at location +where+.
32
+ def initialize(where)
33
+ end
34
+
35
+ # Enumerate the calendars in the repository.
36
+ def each #:yield: calendar
37
+ end
38
+
39
+ # A calendar abstraction. It models a calendar in a calendar repository
40
+ # that may not be an iCalendar.
41
+ #
42
+ # It has methods that behave identically to Icalendar, but it also has
43
+ # methods like name and displayed that are not present in an iCalendar.
44
+ class Calendar
45
+ include Enumerable
46
+
47
+ # The calendar name.
48
+ def name
49
+ end
50
+
51
+ # Whether a calendar should be displayed.
52
+ #
53
+ # TODO - should be #displayed?
54
+ def displayed
55
+ end
56
+
57
+ # Encode into iCalendar format.
58
+ def encode
59
+ end
60
+
61
+ # Enumerate the components in the calendar, both todos and events, or
62
+ # the specified klass. Like Icalendar#each()
63
+ def each(klass=nil, &block) #:yield: component
64
+ end
65
+
66
+ # Enumerate the events in the calendar.
67
+ def events(&block) #:yield: Vevent
68
+ each(Vpim::Icalendar::Vevent, &block)
69
+ end
70
+
71
+ # Enumerate the todos in the calendar.
72
+ def todos(&block) #:yield: Vtodo
73
+ each(Vpim::Icalendar::Vtodo, &block)
74
+ end
75
+
76
+ # The method definitions are just to fool rdoc, not to be used.
77
+ %w{each name displayed encode}.each{|m| remove_method m}
78
+
79
+ def file_each(file, klass, &block) #:nodoc:
80
+ unless iterator?
81
+ return Enumerable::Enumerator.new(self, :each, klass)
82
+ end
83
+
84
+ cals = open(file) do |io|
85
+ Vpim::Icalendar.decode(io)
86
+ end
87
+
88
+ cals.each do |cal|
89
+ cal.each(klass, &block)
90
+ end
91
+ self
92
+ end
93
+ end
94
+ end
95
+
96
+ class Repo
97
+ include Enumerable
98
+
99
+ # An Apple iCal version 3 repository.
100
+ class Apple3 < Repo
101
+ def initialize(where = "~/Library/Calendars")
102
+ @where = where.to_str
103
+ end
104
+
105
+ def each #:nodoc:
106
+ Dir[ File.expand_path(@where + "/**/*.calendar") ].each do |dir|
107
+ yield Calendar.new(dir)
108
+ end
109
+ self
110
+ end
111
+
112
+ class Calendar < Repo::Calendar
113
+ def initialize(dir) # :nodoc:
114
+ @dir = dir
115
+ end
116
+
117
+ def plist(key) #:nodoc:
118
+ Plist::parse_xml( @dir + "/Info.plist")[key]
119
+ end
120
+
121
+ def name #:nodoc:
122
+ plist "Title"
123
+ end
124
+
125
+ def displayed #:nodoc:
126
+ 1 == plist("Checked")
127
+ end
128
+
129
+ def each(klass=nil, &block) #:nodoc:
130
+ unless iterator?
131
+ return Enumerable::Enumerator.new(self, :each, klass)
132
+ end
133
+ Dir[ @dir + "/Events/*.ics" ].map do |ics|
134
+ file_each(ics, klass, &block)
135
+ end
136
+ self
137
+ end
138
+
139
+ def encode #:nodoc:
140
+ Icalendar.create2 do |cal|
141
+ each{|c| cal << c}
142
+ end.encode
143
+ end
144
+ end
145
+
146
+ end
147
+
148
+ class Directory < Repo
149
+ class Calendar < Repo::Calendar
150
+ def initialize(file) #:nodoc:
151
+ @file = file
152
+ end
153
+
154
+ def name #:nodoc:
155
+ File.basename(@file)
156
+ end
157
+
158
+ def displayed #:nodoc:
159
+ true
160
+ end
161
+
162
+ def each(klass, &block) #:nodoc:
163
+ file_each(@file, klass, &block)
164
+ end
165
+
166
+ def encode #:nodoc:
167
+ open(@file, "r"){|f| f.read}
168
+ end
169
+
170
+ end
171
+
172
+ def initialize(where = ".")
173
+ @where = where.to_str
174
+ end
175
+
176
+ def each #:nodoc:
177
+ Dir[ File.expand_path(@where + "/**/*.ics") ].each do |file|
178
+ yield Calendar.new(file)
179
+ end
180
+ self
181
+ end
182
+ end
183
+
184
+ class Uri < Repo
185
+ def self.uri_check(uri)
186
+ uri = case uri
187
+ when URI
188
+ uri
189
+ else
190
+ begin
191
+ URI.parse(uri.sub(/^webcal:/, "http:"))
192
+ rescue URI::InvalidURIError => e
193
+ raise ArgumentError, "Invalid URI for #{uri.inspect} - #{e.to_s}"
194
+ end
195
+ end
196
+ unless uri.scheme == "http"
197
+ raise ArgumentError, "Unsupported URI scheme for #{uri.inspect}"
198
+ end
199
+ uri
200
+ end
201
+
202
+ class Calendar < Repo::Calendar
203
+ def body
204
+ end
205
+
206
+ def initialize(uri) #:nodoc:
207
+ @uri = Uri.uri_check(uri)
208
+ end
209
+
210
+ def name #:nodoc:
211
+ @uri.to_s
212
+ end
213
+
214
+ def displayed #:nodoc:
215
+ true
216
+ end
217
+
218
+ def each(klass, &block) #:nodoc:
219
+ unless iterator?
220
+ return Enumerable::Enumerator.new(self, :each, klass)
221
+ end
222
+
223
+ cals = Vpim::Icalendar.decode(encode)
224
+
225
+ cals.each do |cal|
226
+ cal.each(klass, &block)
227
+ end
228
+ self
229
+ end
230
+
231
+ def encode #:nodoc:
232
+ Net::HTTP.get_response(@uri) do |result|
233
+ accum = ""
234
+ =begin
235
+ better to let this pass up as an invalid encoding error
236
+ if result.code != "200"
237
+ raise StandardError,
238
+ "HTTP GET of #{@uri.to_s.inspect} failed with #{result.code} #{result.error_type}"
239
+ end
240
+ =end
241
+ result.read_body do |chunk|
242
+ accum << chunk
243
+ end
244
+ return accum
245
+ end
246
+ end
247
+
248
+ end
249
+
250
+ def initialize(where)
251
+ @where = Uri.uri_check(where)
252
+ end
253
+
254
+ def each #:nodoc:
255
+ yield Calendar.new(@where)
256
+ self
257
+ end
258
+ end
259
+ end
260
+ end
261
+
@@ -0,0 +1,371 @@
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/vpim'
10
+
11
+ module Vpim
12
+ # Contains regular expression strings for the EBNF of RFC 2425.
13
+ module Bnf #:nodoc:
14
+
15
+ # 1*(ALPHA / DIGIT / "-")
16
+ # Note: I think I can add A-Z here, and get rid of the "i" matches elsewhere.
17
+ # Note: added '_' to allowed because its produced by Notes (X-LOTUS-CHILD_UID:)
18
+ # Note: added '/' to allowed because its produced by KAddressBook (X-messaging/xmpp-All:)
19
+ # Note: added ' ' to allowed because its produced by highrisehq.com (X-GOOGLE TALK:)
20
+ NAME = '[-a-z0-9_/][-a-z0-9_/ ]*'
21
+
22
+ # <"> <Any character except CTLs, DQUOTE> <">
23
+ QSTR = '"([^"]*)"'
24
+
25
+ # *<Any character except CTLs, DQUOTE, ";", ":", ",">
26
+ PTEXT = '([^";:,]+)'
27
+
28
+ # param-value = ptext / quoted-string
29
+ PVALUE = "(?:#{QSTR}|#{PTEXT})"
30
+
31
+ # param = name "=" param-value *("," param-value)
32
+ # Note: v2.1 allows a type or encoding param-value to appear without the type=
33
+ # or the encoding=. This is hideous, but we try and support it, if there
34
+ # is no "=", then $2 will be "", and we will treat it as a v2.1 param.
35
+ PARAM = ";(#{NAME})(=?)((?:#{PVALUE})?(?:,#{PVALUE})*)"
36
+
37
+ # V3.0: contentline = [group "."] name *(";" param) ":" value
38
+ # V2.1: contentline = *( group "." ) name *(";" param) ":" value
39
+ #
40
+ # We accept the V2.1 syntax for backwards compatibility.
41
+ #LINE = "((?:#{NAME}\\.)*)?(#{NAME})([^:]*)\:(.*)"
42
+ LINE = "^((?:#{NAME}\\.)*)?(#{NAME})((?:#{PARAM})*):(.*)$"
43
+
44
+ # date = date-fullyear ["-"] date-month ["-"] date-mday
45
+ # date-fullyear = 4 DIGIT
46
+ # date-month = 2 DIGIT
47
+ # date-mday = 2 DIGIT
48
+ DATE = '(\d\d\d\d)-?(\d\d)-?(\d\d)'
49
+
50
+ # time = time-hour [":"] time-minute [":"] time-second [time-secfrac] [time-zone]
51
+ # time-hour = 2 DIGIT
52
+ # time-minute = 2 DIGIT
53
+ # time-second = 2 DIGIT
54
+ # time-secfrac = "," 1*DIGIT
55
+ # time-zone = "Z" / time-numzone
56
+ # time-numzone = sign time-hour [":"] time-minute
57
+ TIME = '(\d\d):?(\d\d):?(\d\d)(\.\d+)?(Z|[-+]\d\d:?\d\d)?'
58
+
59
+ # integer = (["+"] / "-") 1*DIGIT
60
+ INTEGER = '[-+]?\d+'
61
+
62
+ # QSAFE-CHAR = WSP / %x21 / %x23-7E / NON-US-ASCII
63
+ # ; Any character except CTLs and DQUOTE
64
+ QSAFECHAR = '[ \t\x21\x23-\x7e\x80-\xff]'
65
+
66
+ # SAFE-CHAR = WSP / %x21 / %x23-2B / %x2D-39 / %x3C-7E / NON-US-ASCII
67
+ # ; Any character except CTLs, DQUOTE, ";", ":", ","
68
+ SAFECHAR = '[ \t\x21\x23-\x2b\x2d-\x39\x3c-\x7e\x80-\xff]'
69
+ end
70
+ end
71
+
72
+ module Vpim
73
+ # Split on \r\n or \n to get the lines, unfold continued lines (they
74
+ # start with ' ' or \t), and return the array of unfolded lines.
75
+ #
76
+ # This also supports the (invalid) encoding convention of allowing empty
77
+ # lines to be inserted for readability - it does this by dropping zero-length
78
+ # lines.
79
+ #
80
+ # Also supports an the QUOTED-PRINTABLE soft line-break as described here:
81
+ # http://en.wikipedia.org/wiki/Quoted-printable
82
+ #
83
+ def Vpim.unfold(card) # :nodoc:
84
+ unfolded = []
85
+ card.each do |line|
86
+ line.chomp!
87
+ # If it's a continuation line, add it to the last.
88
+ # If it's an empty line, drop it from the input.
89
+ if( line =~ /^[ \t]/ )
90
+ unfolded[-1] << line[1, line.size-1]
91
+ elsif (unfolded.last && unfolded.last =~ /;ENCODING=QUOTED-PRINTABLE:.*?=$/)
92
+ unfolded.last << line
93
+ elsif( line =~ /^$/ )
94
+ else
95
+ unfolded << line
96
+ end
97
+ end
98
+ unfolded
99
+ end
100
+
101
+ # Convert a +sep+-seperated list of values into an array of values.
102
+ def Vpim.decode_list(value, sep = ',') # :nodoc:
103
+ list = []
104
+
105
+ value.each_line(sep) do |item|
106
+ item.chomp!(sep)
107
+ list << yield(item)
108
+ end
109
+ list
110
+ end
111
+
112
+ # Convert a RFC 2425 date into an array of [year, month, day].
113
+ def Vpim.decode_date(v) # :nodoc:
114
+ unless v =~ %r{^\s*#{Bnf::DATE}\s*$}
115
+ raise Vpim::InvalidEncodingError, "date not valid (#{v})"
116
+ end
117
+ [$1.to_i, $2.to_i, $3.to_i]
118
+ end
119
+
120
+ # Convert a RFC 2425 date into a Date object.
121
+ def self.decode_date_to_date(v)
122
+ Date.new(*decode_date(v))
123
+ end
124
+
125
+ # Note in the following the RFC2425 allows yyyy-mm-ddThh:mm:ss, but RFC2445
126
+ # does not. I choose to encode to the subset that is valid for both.
127
+
128
+ # Encode a Date object as "yyyymmdd".
129
+ def Vpim.encode_date(d) # :nodoc:
130
+ "%0.4d%0.2d%0.2d" % [ d.year, d.mon, d.day ]
131
+ end
132
+
133
+ # Encode a Date object as "yyyymmdd".
134
+ def Vpim.encode_time(d) # :nodoc:
135
+ "%0.4d%0.2d%0.2d" % [ d.year, d.mon, d.day ]
136
+ end
137
+
138
+ # Encode a Time or DateTime object as "yyyymmddThhmmss"
139
+ def Vpim.encode_date_time(d) # :nodoc:
140
+ "%0.4d%0.2d%0.2dT%0.2d%0.2d%0.2d" % [ d.year, d.mon, d.day, d.hour, d.min, d.sec ]
141
+ end
142
+
143
+ # Convert a RFC 2425 time into an array of [hour,min,sec,secfrac,timezone]
144
+ def Vpim.decode_time(v) # :nodoc:
145
+ unless match = %r{^\s*#{Bnf::TIME}\s*$}.match(v)
146
+ raise Vpim::InvalidEncodingError, "time '#{v}' not valid"
147
+ end
148
+ hour, min, sec, secfrac, tz = match.to_a[1..5]
149
+
150
+ [hour.to_i, min.to_i, sec.to_i, secfrac ? secfrac.to_f : 0, tz]
151
+ end
152
+
153
+ def self.array_datetime_to_time(dtarray) #:nodoc:
154
+ # We get [ year, month, day, hour, min, sec, usec, tz ]
155
+ begin
156
+ tz = (dtarray.pop == "Z") ? :gm : :local
157
+ Time.send(tz, *dtarray)
158
+ rescue ArgumentError => e
159
+ raise Vpim::InvalidEncodingError, "#{tz} #{e} (#{dtarray.join(', ')})"
160
+ end
161
+ end
162
+
163
+ # Convert a RFC 2425 time into an array of Time objects.
164
+ def Vpim.decode_time_to_time(v) # :nodoc:
165
+ array_datetime_to_time(decode_date_time(v))
166
+ end
167
+
168
+ # Convert a RFC 2425 date-time into an array of [year,mon,day,hour,min,sec,secfrac,timezone]
169
+ def Vpim.decode_date_time(v) # :nodoc:
170
+ unless match = %r{^\s*#{Bnf::DATE}T#{Bnf::TIME}\s*$}.match(v)
171
+ raise Vpim::InvalidEncodingError, "date-time '#{v}' not valid"
172
+ end
173
+ year, month, day, hour, min, sec, secfrac, tz = match.to_a[1..8]
174
+
175
+ [
176
+ # date
177
+ year.to_i, month.to_i, day.to_i,
178
+ # time
179
+ hour.to_i, min.to_i, sec.to_i, secfrac ? secfrac.to_f : 0, tz
180
+ ]
181
+ end
182
+
183
+ def Vpim.decode_date_time_to_datetime(v) #:nodoc:
184
+ year, month, day, hour, min, sec, secfrac, tz = Vpim.decode_date_time(v)
185
+ # TODO - DateTime understands timezones, so we could decode tz and use it.
186
+ DateTime.civil(year, month, day, hour, min, sec, 0)
187
+ end
188
+
189
+ # Vpim.decode_boolean
190
+ #
191
+ # float
192
+ #
193
+ # float_list
194
+ =begin
195
+ =end
196
+
197
+ # Convert an RFC2425 INTEGER value into an Integer
198
+ def Vpim.decode_integer(v) # :nodoc:
199
+ unless match = %r{\s*#{Bnf::INTEGER}\s*}.match(v)
200
+ raise Vpim::InvalidEncodingError, "integer not valid (#{v})"
201
+ end
202
+ v.to_i
203
+ end
204
+
205
+ #
206
+ # integer_list
207
+
208
+ # Convert a RFC2425 date-list into an array of dates.
209
+ def Vpim.decode_date_list(v) # :nodoc:
210
+ Vpim.decode_list(v) do |date|
211
+ date.strip!
212
+ if date.length > 0
213
+ Vpim.decode_date(date)
214
+ end
215
+ end.compact
216
+ end
217
+
218
+ # Convert a RFC 2425 time-list into an array of times.
219
+ def Vpim.decode_time_list(v) # :nodoc:
220
+ Vpim.decode_list(v) do |time|
221
+ time.strip!
222
+ if time.length > 0
223
+ Vpim.decode_time(time)
224
+ end
225
+ end.compact
226
+ end
227
+
228
+ # Convert a RFC 2425 date-time-list into an array of date-times.
229
+ def Vpim.decode_date_time_list(v) # :nodoc:
230
+ Vpim.decode_list(v) do |datetime|
231
+ datetime.strip!
232
+ if datetime.length > 0
233
+ Vpim.decode_date_time(datetime)
234
+ end
235
+ end.compact
236
+ end
237
+
238
+ # Convert RFC 2425 text into a String.
239
+ # \\ -> \
240
+ # \n -> NL
241
+ # \N -> NL
242
+ # \, -> ,
243
+ # \; -> ;
244
+ #
245
+ # I've seen double-quote escaped by iCal.app. Hmm. Ok, if you aren't supposed
246
+ # to escape anything but the above, everything else is ambiguous, so I'll
247
+ # just support it.
248
+ def Vpim.decode_text(v) # :nodoc:
249
+ # FIXME - I think this should trim leading and trailing space
250
+ v.gsub(/\\(.)/) do
251
+ case $1
252
+ when 'n', 'N'
253
+ "\n"
254
+ else
255
+ $1
256
+ end
257
+ end
258
+ end
259
+
260
+ def Vpim.encode_text(v) #:nodoc:
261
+ v.to_str.gsub(/([\\,;\n])/) { $1 == "\n" ? "\\n" : "\\"+$1 }
262
+ end
263
+
264
+ # v is an Array of String, or just a single String
265
+ def Vpim.encode_text_list(v, sep = ",") #:nodoc:
266
+ begin
267
+ v.to_ary.map{ |t| Vpim.encode_text(t) }.join(sep)
268
+ rescue
269
+ Vpim.encode_text(v)
270
+ end
271
+ end
272
+
273
+ # Convert a +sep+-seperated list of TEXT values into an array of values.
274
+ def Vpim.decode_text_list(value, sep = ',') # :nodoc:
275
+ # Need to do in two stages, as best I can find.
276
+ list = value.scan(/([^#{sep}\\]*(?:\\.[^#{sep}\\]*)*)#{sep}/).map do |v|
277
+ Vpim.decode_text(v.first)
278
+ end
279
+ if value.match(/([^#{sep}\\]*(?:\\.[^#{sep}\\]*)*)$/)
280
+ list << $1
281
+ end
282
+ list
283
+ end
284
+
285
+ # param-value = paramtext / quoted-string
286
+ # paramtext = *SAFE-CHAR
287
+ # quoted-string = DQUOTE *QSAFE-CHAR DQUOTE
288
+ def Vpim.encode_paramtext(value)
289
+ case value
290
+ when %r{\A#{Bnf::SAFECHAR}*\z}
291
+ value
292
+ else
293
+ raise Vpim::Unencodable, "paramtext #{value.inspect}"
294
+ end
295
+ end
296
+
297
+ def Vpim.encode_paramvalue(value)
298
+ case value
299
+ when %r{\A#{Bnf::SAFECHAR}*\z}
300
+ value
301
+ when %r{\A#{Bnf::QSAFECHAR}*\z}
302
+ '"' + value + '"'
303
+ else
304
+ raise Vpim::Unencodable, "param-value #{value.inspect}"
305
+ end
306
+ end
307
+
308
+
309
+ # Unfold the lines in +card+, then return an array of one Field object per
310
+ # line.
311
+ def Vpim.decode(card) #:nodoc:
312
+ content = Vpim.unfold(card).collect { |line| DirectoryInfo::Field.decode(line) }
313
+ end
314
+
315
+
316
+ # Expand an array of fields into its syntactic entities. Each entity is a sequence
317
+ # of fields where the sequences is delimited by a BEGIN/END field. Since
318
+ # BEGIN/END delimited entities can be nested, we build a tree. Each entry in
319
+ # the array is either a Field or an array of entries (where each entry is
320
+ # either a Field, or an array of entries...).
321
+ def Vpim.expand(src) #:nodoc:
322
+ # output array to expand the src to
323
+ dst = []
324
+ # stack used to track our nesting level, as we see begin/end we start a
325
+ # new/finish the current entity, and push/pop that entity from the stack
326
+ current = [ dst ]
327
+
328
+ for f in src
329
+ if f.name? 'BEGIN'
330
+ e = [ f ]
331
+
332
+ current.last.push(e)
333
+ current.push(e)
334
+
335
+ elsif f.name? 'END'
336
+ current.last.push(f)
337
+
338
+ unless current.last.first.value? current.last.last.value
339
+ raise "BEGIN/END mismatch (#{current.last.first.value} != #{current.last.last.value})"
340
+ end
341
+
342
+ current.pop
343
+
344
+ else
345
+ current.last.push(f)
346
+ end
347
+ end
348
+
349
+ dst
350
+ end
351
+
352
+ # Split an array into an array of all the fields at the outer level, and
353
+ # an array of all the inner arrays of fields. Return the array [outer,
354
+ # inner].
355
+ def Vpim.outer_inner(fields) #:nodoc:
356
+ # TODO - use Enumerable#partition
357
+ # seperate into the outer-level fields, and the arrays of component
358
+ # fields
359
+ outer = []
360
+ inner = []
361
+ fields.each do |line|
362
+ case line
363
+ when Array; inner << line
364
+ else; outer << line
365
+ end
366
+ end
367
+ return outer, inner
368
+ end
369
+
370
+ end
371
+