vobject 0.1.0 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/macos.yml +38 -0
  3. data/.github/workflows/ubuntu.yml +56 -0
  4. data/.github/workflows/windows.yml +40 -0
  5. data/.hound.yml +3 -0
  6. data/.rubocop.tb.yml +650 -0
  7. data/.rubocop.yml +1077 -0
  8. data/Gemfile +1 -1
  9. data/LICENSE.txt +21 -17
  10. data/README.adoc +151 -0
  11. data/Rakefile +1 -1
  12. data/lib/c.rb +173 -0
  13. data/lib/error.rb +19 -0
  14. data/lib/vcalendar.rb +77 -0
  15. data/lib/vcard.rb +67 -0
  16. data/lib/vobject.rb +13 -170
  17. data/lib/vobject/component.rb +87 -36
  18. data/lib/vobject/parameter.rb +116 -0
  19. data/lib/vobject/parametervalue.rb +26 -0
  20. data/lib/vobject/property.rb +134 -55
  21. data/lib/vobject/propertyvalue.rb +46 -0
  22. data/lib/vobject/vcalendar/component.rb +106 -0
  23. data/lib/vobject/vcalendar/grammar.rb +595 -0
  24. data/lib/vobject/vcalendar/paramcheck.rb +259 -0
  25. data/lib/vobject/vcalendar/propertyparent.rb +98 -0
  26. data/lib/vobject/vcalendar/propertyvalue.rb +606 -0
  27. data/lib/vobject/vcalendar/typegrammars.rb +605 -0
  28. data/lib/vobject/vcard/v3_0/component.rb +40 -0
  29. data/lib/vobject/vcard/v3_0/grammar.rb +175 -0
  30. data/lib/vobject/vcard/v3_0/paramcheck.rb +110 -0
  31. data/lib/vobject/vcard/v3_0/parameter.rb +17 -0
  32. data/lib/vobject/vcard/v3_0/property.rb +18 -0
  33. data/lib/vobject/vcard/v3_0/propertyvalue.rb +401 -0
  34. data/lib/vobject/vcard/v3_0/typegrammars.rb +425 -0
  35. data/lib/vobject/vcard/v4_0/component.rb +40 -0
  36. data/lib/vobject/vcard/v4_0/grammar.rb +224 -0
  37. data/lib/vobject/vcard/v4_0/paramcheck.rb +269 -0
  38. data/lib/vobject/vcard/v4_0/parameter.rb +18 -0
  39. data/lib/vobject/vcard/v4_0/property.rb +63 -0
  40. data/lib/vobject/vcard/v4_0/propertyvalue.rb +404 -0
  41. data/lib/vobject/vcard/v4_0/typegrammars.rb +539 -0
  42. data/lib/vobject/version.rb +1 -1
  43. data/vobject.gemspec +19 -16
  44. metadata +81 -26
  45. data/.travis.yml +0 -5
  46. data/README.md +0 -94
@@ -0,0 +1,605 @@
1
+ require "rsec"
2
+ require "set"
3
+ require "uri"
4
+ require "date"
5
+ require "tzinfo"
6
+ include Rsec::Helpers
7
+ require_relative "../../c"
8
+ require_relative "../../error"
9
+ require_relative "./propertyparent"
10
+ require "vobject"
11
+ require_relative "./propertyvalue"
12
+
13
+ module Vobject::Vcalendar
14
+ class Typegrammars
15
+ class << self
16
+ # property value types, each defining their own parser
17
+ def recur
18
+ freq = /SECONDLY/i.r | /MINUTELY/i.r | /HOURLY/i.r | /DAILY/i.r |
19
+ /WEEKLY/i.r | /MONTHLY/i.r | /YEARLY/i.r
20
+ enddate = C::DATE_TIME | C::DATE
21
+ seconds = /[0-9]{1,2}/.r
22
+ byseclist = seq(seconds << ",".r, lazy { byseclist }) do |s, l|
23
+ [s, l].flatten
24
+ end | seconds.map { |s| [s] }
25
+ minutes = /[0-9]{1,2}/.r
26
+ byminlist = seq(minutes << ",".r, lazy { byminlist }) do |m, l|
27
+ [m, l].flatten
28
+ end | minutes.map { |m| [m] }
29
+ hours = /[0-9]{1,2}/.r
30
+ byhrlist = seq(hours << ",".r, lazy { byhrlist }) do |h, l|
31
+ [h, l].flatten
32
+ end | hours.map { |h| [h] }
33
+ ordwk = /[0-9]{1,2}/.r
34
+ weekday = /SU/i.r | /MO/i.r | /TU/i.r | /WE/i.r | /TH/i.r | /FR/i.r | /SA/i.r
35
+ weekdaynum1 = seq(C::SIGN._?, ordwk) do |s, o|
36
+ h = { ordwk: o }
37
+ h[:sign] = s[0] unless s.empty?
38
+ h
39
+ end
40
+ weekdaynum = seq(weekdaynum1._?, weekday) do |a, b|
41
+ h = { weekday: b }
42
+ h = h.merge a[0] unless a.empty?
43
+ h
44
+ end
45
+ bywdaylist = seq(weekdaynum << ",".r, lazy { bywdaylist }) do |w, l|
46
+ [w, l].flatten
47
+ end | weekdaynum.map { |w| [w] }
48
+ ordmoday = /[0-9]{1,2}/.r
49
+ monthdaynum = seq(C::SIGN._?, ordmoday) do |s, o|
50
+ h = { ordmoday: o }
51
+ h[:sign] = s[0] unless s.empty?
52
+ h
53
+ end
54
+ bymodaylist = seq(monthdaynum << ",".r, lazy { bymodaylist }) do |m, l|
55
+ [m, l].flatten
56
+ end | monthdaynum.map { |m| [m] }
57
+ ordyrday = /[0-9]{1,3}/.r
58
+ yeardaynum = seq(C::SIGN._?, ordyrday) do |s, o|
59
+ h = { ordyrday: o }
60
+ h[:sign] = s[0] unless s.empty?
61
+ h
62
+ end
63
+ byyrdaylist = seq(yeardaynum << ",".r, lazy { byyrdaylist }) do |y, l|
64
+ [y, l].flatten
65
+ end | yeardaynum.map { |y| [y] }
66
+ weeknum = seq(C::SIGN._?, ordwk) do |s, o|
67
+ h = { ordwk: o }
68
+ h[:sign] = s[0] unless s.empty?
69
+ h
70
+ end
71
+ bywknolist = seq(weeknum << ",".r, lazy { bywknolist }) do |w, l|
72
+ [w, l].flatten
73
+ end | weeknum.map { |w| [w] }
74
+ # monthnum = /[0-9]{1,2}/.r
75
+ # RFC 7529 add leap month indicator
76
+ monthnum = /[0-9]{1,2}L?/i.r
77
+ bymolist = seq(monthnum << ",".r, lazy { bymolist }) do |m, l|
78
+ [m, l].flatten
79
+ end | monthnum.map { |m| [m] }
80
+ setposday = yeardaynum
81
+ bysplist = seq(setposday << ",".r, lazy { bysplist }) do |s, l|
82
+ [s, l].flatten
83
+ end | setposday.map { |s| [s] }
84
+ # http://www.unicode.org/repos/cldr/tags/latest/common/bcp47/calendar.xml
85
+ rscale = C::XNAME_VCAL | /buddhist/i.r | /chinese/i.r | /coptic/i.r | /dangi/i.r |
86
+ /ethioaa/i.r | /ethiopic-amete-alem/i.r | /ethiopic/i.r |
87
+ /gregory/i.r | /hebrew/i.r | /indian/i.r | /islamic/i.r |
88
+ /islamic-umalqura/i.r | /islamic-tbla/i.r | /islamic-civil/i.r |
89
+ /islamic-rgsa/i.r | /iso8601/i.r | /japanese/i.r | /persian/i.r |
90
+ /roc/i.r | /islamicc/i.r | /gregorian/i.r
91
+ skip = /OMIT/i.r | /BACKWARD/i.r | /FORWARD/i.r
92
+ recur_rule_part = seq(/FREQ/i.r << "=".r, freq) { |_k, v| { freq: v } } |
93
+ seq(/UNTIL/i.r << "=".r, enddate) { |_k, v| { until: v } } |
94
+ seq(/COUNT/i.r << "=".r, /[0-9]+/i.r) { |_k, v| { count: v } } |
95
+ seq(/INTERVAL/i.r << "=".r, /[0-9]+/i.r) { |_k, v| { interval: v } } |
96
+ seq(/BYSECOND/i.r << "=".r, byseclist) { |_k, v| { bysecond: v } } |
97
+ seq(/BYMINUTE/i.r << "=".r, byminlist) { |_k, v| { byminute: v } } |
98
+ seq(/BYHOUR/i.r << "=".r, byhrlist) { |_k, v| { byhour: v } } |
99
+ seq(/BYDAY/i.r << "=".r, bywdaylist) { |_k, v| { byday: v } } |
100
+ seq(/BYMONTHDAY/i.r << "=".r, bymodaylist) { |_k, v| { bymonthday: v } } |
101
+ seq(/BYYEARDAY/i.r << "=".r, byyrdaylist) { |_k, v| { byyearday: v } } |
102
+ seq(/BYWEEKNO/i.r << "=".r, bywknolist) { |_k, v| { byweekno: v } } |
103
+ seq(/BYMONTH/i.r << "=".r, bymolist) { |_k, v| { bymonth: v } } |
104
+ seq(/BYSETPOS/i.r << "=".r, bysplist) { |_k, v| { bysetpos: v } } |
105
+ seq(/WKST/i.r << "=".r, weekday) { |_k, v| { wkst: v } } |
106
+ # RFC 7529
107
+ seq(/RSCALE/i.r << "=".r, rscale) { |_k, v| { rscale: v } } |
108
+ seq(/SKIP/i.r << "=".r, skip) { |_k, v| { skip: v } }
109
+ recur1 = seq(recur_rule_part, ";", lazy { recur1 }) { |h, _, r| h.merge r } |
110
+ recur_rule_part
111
+ recur = recur1.map { |r| PropertyValue::Recur.new r }
112
+ recur.eof
113
+ end
114
+
115
+ def integer
116
+ integer = prim(:int32).map { |i| PropertyValue::Integer.new i }
117
+ integer.eof
118
+ end
119
+
120
+ def percent_complete
121
+ integer = prim(:int32).map do |a|
122
+ if a >= 0 && a <= 100
123
+ PropertyValue::PercentComplete.new a
124
+ else
125
+ { error: "Percentage outside of range 0..100" }
126
+ end
127
+ end
128
+ integer.eof
129
+ end
130
+
131
+ def priority
132
+ integer = prim(:int32).map do |a|
133
+ if a >= 0 && a <= 9
134
+ PropertyValue::Priority.new a
135
+ else
136
+ { error: "Percentage outside of range 0..100" }
137
+ end
138
+ end
139
+ integer.eof
140
+ end
141
+
142
+ def float_t
143
+ float_t = prim(:double).map { |f| PropertyValue::Float.new f }
144
+ float_t.eof
145
+ end
146
+
147
+ def time_t
148
+ time_t = C::TIME.map { |t| PropertyValue::Time.new t }
149
+ time_t.eof
150
+ end
151
+
152
+ def geovalue
153
+ float = prim(:double)
154
+ geovalue = seq(float << ";".r, float) do |a, b|
155
+ if a <= 180.0 && a >= -180.0 && b <= 180 && b > -180
156
+ PropertyValue::Geovalue.new(lat: a, long: b)
157
+ else
158
+ { error: "Latitude/Longitude outside of range -180..180" }
159
+ end
160
+ end
161
+ geovalue.eof
162
+ end
163
+
164
+ def calscalevalue
165
+ calscalevalue = /GREGORIAN/i.r.map { PropertyValue::Calscale.new "GREGORIAN" }
166
+ calscalevalue.eof
167
+ end
168
+
169
+ def iana_token
170
+ iana_token = C::IANATOKEN.map { |x| PropertyValue::Ianatoken.new x }
171
+ iana_token.eof
172
+ end
173
+
174
+ def versionvalue
175
+ versionvalue = seq(prim(:double) << ";".r,
176
+ prim(:double)) do |x, y|
177
+ PropertyValue::Version.new [x, y]
178
+ end | "2.0".r.map do
179
+ PropertyValue::Version.new ["2.0"]
180
+ end | prim(:double).map do |v|
181
+ PropertyValue::Version.new v
182
+ end
183
+ versionvalue.eof
184
+ end
185
+
186
+ def binary
187
+ binary = seq(/[a-zA-Z0-9+\/]*/.r, /={0,2}/.r) do |b, q|
188
+ if (b.length + q.length) % 4 == 0
189
+ PropertyValue::Binary.new(b + q)
190
+ else
191
+ { error: "Malformed binary coding" }
192
+ end
193
+ end
194
+ binary.eof
195
+ end
196
+
197
+ def uri
198
+ uri = /\S+/.r.map do |s|
199
+ if s =~ URI::DEFAULT_PARSER.make_regexp
200
+ PropertyValue::Uri.new(s)
201
+ else
202
+ { error: "Invalid URI" }
203
+ end
204
+ end
205
+ uri.eof
206
+ end
207
+
208
+ def text_t
209
+ text_t = C::TEXT.map { |t| PropertyValue::Text.new(unescape(t)) }
210
+ text_t.eof
211
+ end
212
+
213
+ def textlist
214
+ textlist1 =
215
+ seq(C::TEXT << ",".r, lazy { textlist1 }) { |a, b| [unescape(a), b].flatten } |
216
+ C::TEXT.map { |t| [unescape(t)] }
217
+ textlist = textlist1.map { |m| PropertyValue::Textlist.new m }
218
+ textlist.eof
219
+ end
220
+
221
+ def request_statusvalue
222
+ @req_status = Set.new %w{2.0 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 2.10 2.11 3.0 3.1 3.2 3.3 3.4 3.5 3.6 3.7 3.8 3.9 3.10 3.11 3.12 3.13 3.14 4.0 5.0 5.1 5.2 5.3}
223
+ extdata = seq(";".r, C::TEXT) { |_, t| t }
224
+ request_statusvalue = seq(/[0-9](\.[0-9]){1,2}/.r << ";".r, C::TEXT, extdata._?) do |n, t1, t2|
225
+ return { error: "Invalid request status #{n}" } unless @req_status.include?(n) # RFC 5546
226
+ hash = { statcode: n, statdesc: t1 }
227
+ hash[:extdata] = t2[0] unless t2.empty?
228
+ Vobject::Vcalendar::PropertyValue::Requeststatusvalue.new hash
229
+ end
230
+ request_statusvalue.eof
231
+ end
232
+
233
+ def classvalue
234
+ classvalue = (/PUBLIC/i.r | /PRIVATE/i.r | /CONFIDENTIAL/i.r | C::XNAME_VCAL | C::IANATOKEN).map do |m|
235
+ PropertyValue::ClassValue.new m
236
+ end
237
+ classvalue.eof
238
+ end
239
+
240
+ def eventstatus
241
+ eventstatus = (/TENTATIVE/i.r | /CONFIRMED/i.r | /CANCELLED/i.r).map do |m|
242
+ PropertyValue::EventStatus.new m
243
+ end
244
+ eventstatus.eof
245
+ end
246
+
247
+ def todostatus
248
+ todostatus = (/NEEDS-ACTION/i.r | /COMPLETED/i.r | /IN-PROCESS/i.r | /CANCELLED/i.r).map do |m|
249
+ PropertyValue::Todostatus.new m
250
+ end
251
+ todostatus.eof
252
+ end
253
+
254
+ def journalstatus
255
+ journalstatus = (/DRAFT/i.r | /FINAL/i.r | /CANCELLED/i.r).map do |m|
256
+ PropertyValue::Journalstatus.new m
257
+ end
258
+ journalstatus.eof
259
+ end
260
+
261
+ def date_t
262
+ date_t = C::DATE
263
+ date_t.eof
264
+ end
265
+
266
+ def datelist
267
+ datelist1 = seq(C::DATE << ",".r, lazy { datelist1 }) do |d, l|
268
+ [d, l].flatten
269
+ end | C::DATE.map { |d| [d] }
270
+ datelist = datelist1.map { |m| PropertyValue::Datelist.new m }
271
+ datelist.eof
272
+ end
273
+
274
+ def date_time_t
275
+ C::DATE_TIME.eof
276
+ end
277
+
278
+ def date_timelist
279
+ date_timelist1 = seq(C::DATE_TIME << ",".r,
280
+ lazy { date_timelist1 }) do |d, l|
281
+ [d, l].flatten
282
+ end | C::DATE_TIME.map { |d| [d] }
283
+ date_timelist = date_timelist1.map do |m|
284
+ PropertyValue::Datetimelist.new m
285
+ end
286
+ date_timelist.eof
287
+ end
288
+
289
+ def date_time_utc_t
290
+ date_time_utc_t = C::DATE_TIME_UTC
291
+ date_time_utc_t.eof
292
+ end
293
+
294
+ def date_time_utclist
295
+ date_time_utclist1 = seq(C::DATE_TIME_UTC << ",".r, lazy { date_time_utclist1 }) do |d, l|
296
+ [d, l].flatten
297
+ end | C::DATE_TIME_UTC.map { |d| [d] }
298
+ date_time_utclist = date_time_utclist1.map do |m|
299
+ PropertyValue::Datetimeutclist.new m
300
+ end
301
+ date_time_utclist.eof
302
+ end
303
+
304
+ def duration_t
305
+ duration = C::DURATION.map { |d| PropertyValue::Duration.new d }
306
+ duration.eof
307
+ end
308
+
309
+ def periodlist
310
+ period_explicit = seq(C::DATE_TIME << "/".r, C::DATE_TIME) do |s, e|
311
+ { start: s, end: e }
312
+ end
313
+ period_start = seq(C::DATE_TIME << "/".r, C::DURATION) do |s, d|
314
+ { start: s, duration: PropertyValue::Duration.new(d) }
315
+ end
316
+ period = period_explicit | period_start
317
+ periodlist1 = seq(period << ",".r, lazy { periodlist1 }) do |p, l|
318
+ [p, l].flatten
319
+ end | period.map { |p| [p] }
320
+ periodlist = periodlist1.map { |m| PropertyValue::Periodlist.new m }
321
+ periodlist.eof
322
+ end
323
+
324
+ def transpvalue
325
+ transpvalue = (/OPAQUE/i.r | /TRANSPARENT/i.r).map do |m|
326
+ PropertyValue::TranspValue.new m
327
+ end
328
+ transpvalue.eof
329
+ end
330
+
331
+ def utc_offset
332
+ utc_offset = seq(C::SIGN, /[0-9]{2}/.r, /[0-9]{2}/.r,
333
+ /[0-9]{2}/.r._?) do |sign, h, m, sec|
334
+ hash = { sign: sign, hr: h, min: m }
335
+ hash[:sec] = sec[0] unless sec.empty?
336
+ PropertyValue::Utcoffset.new hash
337
+ end
338
+ utc_offset.eof
339
+ end
340
+
341
+ def actionvalue
342
+ actionvalue = (/AUDIO/i.r | /DISPLAY/i.r | /EMAIL/i.r | C::IANATOKEN |
343
+ C::XNAME_VCAL).map { |m| PropertyValue::ActionValue.new m }
344
+ actionvalue.eof
345
+ end
346
+
347
+ def boolean
348
+ boolean = C::BOOLEAN.map { |b| PropertyValue::Boolean.new b }
349
+ boolean.eof
350
+ end
351
+
352
+ # RFC 5546
353
+ def methodvalue
354
+ methodvalue = (/PUBLISH/i.r | /REQUEST/i.r | /REPLY/i.r | /ADD/i.r |
355
+ /CANCEL/i.r | /REFRESH/i.r | /COUNTER/i.r |
356
+ /DECLINECOUNTER/i.r).map { |m| PropertyValue::MethodValue.new m }
357
+ methodvalue.eof
358
+ end
359
+
360
+ # RFC 7953
361
+ def busytype
362
+ busytype = (/BUSY-UNAVAILABLE/i.r | /BUSY-TENTATIVE/i.r | /BUSY/i.r |
363
+ C::IANATOKEN |
364
+ C::XNAME_VCAL).map { |m| PropertyValue::BusyType.new m }
365
+ busytype.eof
366
+ end
367
+
368
+ # https://www.w3.org/TR/2011/REC-css3-color-20110607/#svg-color
369
+ def color
370
+ color = C::COLOR.map { |m| PropertyValue::Color.new m }
371
+ color.eof
372
+ end
373
+
374
+ # text escapes: \\ \; \, \N \n
375
+ def unescape(x)
376
+ # temporarily escape \\ as \007f, which is disallowed in any text
377
+ x.gsub(/\\\\/, "\u007f").gsub(/\\;/, ";").gsub(/\\,/, ",").
378
+ gsub(/\\[Nn]/, "\n").tr("\u007f", "\\")
379
+ end
380
+
381
+ def registered_propname
382
+ registered_propname = C::NAME_VCAL
383
+ registered_propname.eof
384
+ end
385
+
386
+ def registered_propname?(x)
387
+ p = registered_propname.parse(x)
388
+ not(Rsec::INVALID[p])
389
+ end
390
+
391
+ # Enforce type restrictions on values of particular properties.
392
+ # If successful, return typed interpretation of string
393
+ def typematch(strict, key, params, component, value, ctx)
394
+ errors = []
395
+ errors << property_parent(strict, key, component, value, ctx)
396
+ ctx1 = Rsec::ParseContext.new value, "source"
397
+ case key
398
+ when :CALSCALE
399
+ ret = calscalevalue._parse ctx1
400
+ when :METHOD
401
+ ret = methodvalue._parse ctx1
402
+ when :VERSION
403
+ ret = versionvalue._parse ctx1
404
+ when :ATTACH
405
+ ret = if params[:VALUE] == "BINARY"
406
+ binary._parse ctx1
407
+ else
408
+ uri._parse ctx1
409
+ end
410
+ when :IMAGE
411
+ parse_err(strict, errors, "No VALUE parameter specified for property #{key}", ctx1) if params.empty?
412
+ parse_err(strict, errors, "No VALUE parameter specified for property #{key}", ctx1) unless params[:VALUE]
413
+ if params[:VALUE] == "BINARY"
414
+ parse_err(strict, errors, "No ENCODING parameter specified for property #{key}", ctx1) unless params[:ENCODING]
415
+ parse_err(strict, errors, "Incorrect ENCODING parameter specified for property #{key}", ctx1) unless params[:ENCODING] == "BASE64"
416
+ ret = binary._parse ctx1
417
+ elsif params[:VALUE] == "URI"
418
+ ret = uri._parse ctx1
419
+ else
420
+ parse_err(strict, errors, "Incorrect VALUE parameter specified for property #{key}", ctx1)
421
+ end
422
+ when :CATEGORIES, :RESOURCES
423
+ ret = textlist._parse ctx1
424
+ when :CLASS
425
+ ret = classvalue._parse ctx1
426
+ when :COMMENT, :DESCRIPTION, :LOCATION, :SUMMARY, :TZID, :TZNAME,
427
+ :CONTACT, :RELATED_TO, :UID, :PRODID, :NAME
428
+ ret = text_t._parse ctx1
429
+ when :GEO
430
+ ret = geovalue._parse ctx1
431
+ when :PERCENT_COMPLETE
432
+ ret = percent_complete._parse ctx1
433
+ when :PRIORITY
434
+ ret = priority._parse ctx1
435
+ when :STATUS
436
+ ret = case component
437
+ when :EVENT
438
+ eventstatus._parse ctx1
439
+ when :TODO
440
+ todostatus._parse ctx1
441
+ when :JOURNAL
442
+ journalstatus._parse ctx1
443
+ else
444
+ text_t._parse ctx1
445
+ end
446
+ when :COMPLETED, :CREATED, :DTSTAMP, :LAST_MODIFIED
447
+ ret = date_time_utc_t._parse ctx1
448
+ when :DTEND, :DTSTART, :DUE, :RECURRENCE_ID
449
+ if params && params[:VALUE] == "DATE"
450
+ ret = date_t._parse ctx1
451
+ elsif component == :FREEBUSY
452
+ ret = date_time_utc_t._parse ctx1
453
+ elsif params && params[:TZID]
454
+ if [:STANDARD || :DAYLIGHT].include? component
455
+ parse_err(strict, errors, "Specified TZID within property #{key} in #{component}", ctx1)
456
+ end
457
+ begin
458
+ tz = TZInfo::Timezone.get(params[:TZID])
459
+ ret = date_time_t._parse ctx1
460
+ # note that we use the registered tz information to map to UTC, rather than look up the values witin the VTIMEZONE component
461
+ ret.value[:time] = tz.local_to_utc(ret.value[:time])
462
+ ret.value[:zone] = params[:TZID]
463
+ rescue
464
+ # undefined timezone: default to floating local
465
+ ret = date_time_t._parse ctx1
466
+ end
467
+ else
468
+ ret = date_time_t._parse ctx1
469
+ end
470
+ when :EXDATE
471
+ if params && params[:VALUE] == "DATE"
472
+ ret = datelist._parse ctx1
473
+ elsif params && params[:TZID]
474
+ if [:STANDARD || :DAYLIGHT].include? component
475
+ parse_err(strict, errors, "Specified TZID within property #{key} in #{component}", ctx1)
476
+ end
477
+ tz = TZInfo::Timezone.get(params[:TZID])
478
+ ret = date_timelist._parse ctx1
479
+ ret.value.each do |x|
480
+ x.value[:time] = tz.local_to_utc(x.value[:time])
481
+ x.value[:zone] = params[:TZID]
482
+ end
483
+ else
484
+ ret = date_timelist._parse ctx1
485
+ end
486
+ when :RDATE
487
+ if params && params[:VALUE] == "DATE"
488
+ ret = datelist._parse ctx1
489
+ elsif params && params[:VALUE] == "PERIOD"
490
+ ret = periodlist._parse ctx1
491
+ elsif params && params[:TZID]
492
+ if [:STANDARD || :DAYLIGHT].include? component
493
+ parse_err(strict, errors, "Specified TZID within property #{key} in #{component}", ctx1)
494
+ end
495
+ tz = TZInfo::Timezone.get(params[:TZID])
496
+ ret = date_timelist._parse ctx1
497
+ ret.value.each do |x|
498
+ x.value[:time] = tz.local_to_utc(x.value[:time])
499
+ x.value[:zone] = params[:TZID]
500
+ end
501
+ else
502
+ ret = date_timelist._parse ctx1
503
+ end
504
+ when :TRIGGER
505
+ if params && params[:VALUE] == "DATE-TIME" || /^\d{8}T/.match(value)
506
+ if params && params[:RELATED]
507
+ parse_err(strict, errors, "Specified RELATED within property #{key} as date-time", ctx1)
508
+ end
509
+ ret = date_time_utc_t._parse ctx1
510
+ else
511
+ ret = duration_t._parse ctx1
512
+ end
513
+ when :FREEBUSY
514
+ ret = periodlist._parse ctx1
515
+ when :TRANSP
516
+ ret = transpvalue._parse ctx1
517
+ when :TZOFFSETFROM, :TZOFFSETTO
518
+ ret = utc_offset._parse ctx1
519
+ when :TZURI, :URL, :SOURCE, :CONFERENCE
520
+ if key == :CONFERENCE
521
+ parse_err(strict, errors, "Missing URI VALUE parameter", ctx1) if params.empty?
522
+ parse_err(strict, errors, "Missing URI VALUE parameter", ctx1) if !params[:VALUE]
523
+ parse_err(strict, errors, "report_error Type mismatch of VALUE parameter #{params[:VALUE]} for property #{key}", ctx1) if params[:VALUE] != "URI"
524
+ end
525
+ ret = uri._parse ctx1
526
+ when :ATTENDEE, :ORGANIZER
527
+ ret = uri._parse ctx1
528
+ when :RRULE
529
+ ret = recur._parse ctx1
530
+ when :ACTION
531
+ ret = actionvalue._parse ctx1
532
+ when :REPEAT, :SEQUENCE
533
+ ret = integer._parse ctx1
534
+ when :REQUEST_STATUS
535
+ ret = request_statusvalue._parse ctx1
536
+ # RFC 7953
537
+ when :BUSYTYPE
538
+ ret = busytype._parse ctx1
539
+ # RFC 7986
540
+ when :REFRESH_INTERVAL
541
+ parse_err(strict, errors, "Missing VALUE parameter for property #{key}", ctx1) if params.empty?
542
+ parse_err(strict, errors, "Missing VALUE parameter for property #{key}", ctx1) if !params[:VALUE]
543
+ parse_err(strict, errors, "Type mismatch of VALUE parameter #{params[:VALUE]} for property #{key}", ctx1) if params[:VALUE] != "DURATION"
544
+ ret = duration_t._parse ctx1
545
+ # RFC 7986
546
+ when :COLOR
547
+ ret = color._parse ctx1
548
+ else
549
+ if params && params[:VALUE]
550
+ case params[:VALUE]
551
+ when "BOOLEAN"
552
+ ret = boolean._parse ctx1
553
+ when "BINARY"
554
+ ret = binary._parse ctx1
555
+ when "CAL-ADDRESS"
556
+ ret = uri._parse ctx1
557
+ when "DATE-TIME"
558
+ ret = date_time_t._parse ctx1
559
+ when "DATE"
560
+ ret = date_t._parse ctx1
561
+ when "DURATION"
562
+ ret = duration_t._parse ctx1
563
+ when "FLOAT"
564
+ ret = float_t._parse ctx1
565
+ when "INTEGER"
566
+ ret = integer._parse ctx1
567
+ when "PERIOD"
568
+ ret = period._parse ctx1
569
+ when "RECUR"
570
+ ret = recur._parse ctx1
571
+ when "TEXT"
572
+ ret = text_t._parse ctx1
573
+ when "TIME"
574
+ ret = time_t._parse ctx1
575
+ when "URI"
576
+ ret = uri._parse ctx1
577
+ when "UTC-OFFSET"
578
+ ret = utc_offset._parse ctx1
579
+ end
580
+ else
581
+ ret = text_t._parse ctx1
582
+ end
583
+ end
584
+ if ret.is_a?(Hash) && ret[:error]
585
+ parse_err(strict, errors, "#{ret[:error]} for property #{key}, value #{value}", ctx)
586
+ end
587
+ if Rsec::INVALID[ret]
588
+ parse_err(strict, errors, "Type mismatch for property #{key}, value #{value}", ctx)
589
+ end
590
+ Rsec::Fail.reset
591
+ [ret, errors]
592
+ end
593
+
594
+ private
595
+
596
+ def parse_err(strict, errors, msg, ctx)
597
+ if strict
598
+ raise ctx.report_error msg, "source"
599
+ else
600
+ errors << ctx.report_error(msg, "source")
601
+ end
602
+ end
603
+ end
604
+ end
605
+ end