vobject 0.1.0 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/macos.yml +38 -0
- data/.github/workflows/ubuntu.yml +56 -0
- data/.github/workflows/windows.yml +40 -0
- data/.hound.yml +3 -0
- data/.rubocop.tb.yml +650 -0
- data/.rubocop.yml +1077 -0
- data/Gemfile +1 -1
- data/LICENSE.txt +21 -17
- data/README.adoc +151 -0
- data/Rakefile +1 -1
- data/lib/c.rb +173 -0
- data/lib/error.rb +19 -0
- data/lib/vcalendar.rb +77 -0
- data/lib/vcard.rb +67 -0
- data/lib/vobject.rb +13 -170
- data/lib/vobject/component.rb +87 -36
- data/lib/vobject/parameter.rb +116 -0
- data/lib/vobject/parametervalue.rb +26 -0
- data/lib/vobject/property.rb +134 -55
- data/lib/vobject/propertyvalue.rb +46 -0
- data/lib/vobject/vcalendar/component.rb +106 -0
- data/lib/vobject/vcalendar/grammar.rb +595 -0
- data/lib/vobject/vcalendar/paramcheck.rb +259 -0
- data/lib/vobject/vcalendar/propertyparent.rb +98 -0
- data/lib/vobject/vcalendar/propertyvalue.rb +606 -0
- data/lib/vobject/vcalendar/typegrammars.rb +605 -0
- data/lib/vobject/vcard/v3_0/component.rb +40 -0
- data/lib/vobject/vcard/v3_0/grammar.rb +175 -0
- data/lib/vobject/vcard/v3_0/paramcheck.rb +110 -0
- data/lib/vobject/vcard/v3_0/parameter.rb +17 -0
- data/lib/vobject/vcard/v3_0/property.rb +18 -0
- data/lib/vobject/vcard/v3_0/propertyvalue.rb +401 -0
- data/lib/vobject/vcard/v3_0/typegrammars.rb +425 -0
- data/lib/vobject/vcard/v4_0/component.rb +40 -0
- data/lib/vobject/vcard/v4_0/grammar.rb +224 -0
- data/lib/vobject/vcard/v4_0/paramcheck.rb +269 -0
- data/lib/vobject/vcard/v4_0/parameter.rb +18 -0
- data/lib/vobject/vcard/v4_0/property.rb +63 -0
- data/lib/vobject/vcard/v4_0/propertyvalue.rb +404 -0
- data/lib/vobject/vcard/v4_0/typegrammars.rb +539 -0
- data/lib/vobject/version.rb +1 -1
- data/vobject.gemspec +19 -16
- metadata +81 -26
- data/.travis.yml +0 -5
- data/README.md +0 -94
@@ -0,0 +1,26 @@
|
|
1
|
+
module Vobject
|
2
|
+
class ParameterValue
|
3
|
+
def initialize(val)
|
4
|
+
self.value = val
|
5
|
+
end
|
6
|
+
# raise_invalid_initialization if key != name
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_s
|
10
|
+
value
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def name
|
16
|
+
prop_name
|
17
|
+
end
|
18
|
+
|
19
|
+
def default_value_type
|
20
|
+
"text"
|
21
|
+
end
|
22
|
+
|
23
|
+
def raise_invalid_initialization
|
24
|
+
raise "vObject property initialization failed"
|
25
|
+
end
|
26
|
+
end
|
data/lib/vobject/property.rb
CHANGED
@@ -1,83 +1,162 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
1
|
+
require "vobject"
|
2
|
+
require "vobject/parameter"
|
3
|
+
|
4
|
+
class Vobject::Property
|
5
|
+
attr_accessor :group, :prop_name, :params, :value, :multiple, :norm
|
6
|
+
|
7
|
+
def <=>(another)
|
8
|
+
if self.prop_name =~ /^VERSION$/i
|
9
|
+
-1
|
10
|
+
elsif another.prop_name =~ /^VERSION$/i
|
11
|
+
1
|
12
|
+
else
|
13
|
+
self.to_norm <=> another.to_norm
|
14
|
+
end
|
15
|
+
end
|
8
16
|
|
9
|
-
|
10
|
-
|
17
|
+
def initialize(key, options)
|
18
|
+
if options.class == Array
|
19
|
+
self.multiple = []
|
20
|
+
options.each do |v|
|
21
|
+
multiple << property_base_class.new(key, v)
|
22
|
+
self.prop_name = key
|
23
|
+
end
|
24
|
+
else
|
11
25
|
self.prop_name = key
|
12
|
-
|
13
|
-
|
26
|
+
if options.nil? || options.empty?
|
27
|
+
self.group = nil
|
28
|
+
self.params = []
|
29
|
+
self.value = nil
|
30
|
+
else
|
31
|
+
self.group = options[:group]
|
32
|
+
self.prop_name = key
|
33
|
+
unless options[:params].nil? || options[:params].empty?
|
34
|
+
self.params = []
|
35
|
+
options[:params].each do |k, v|
|
36
|
+
params << parameter_base_class.new(k, v)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
# self.value = parse_value(options[:value])
|
40
|
+
self.value = options[:value]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
self.norm = nil
|
44
|
+
raise_invalid_initialization if key != name
|
45
|
+
end
|
14
46
|
|
15
|
-
|
47
|
+
def to_s
|
48
|
+
if multiple.nil? || multiple.empty?
|
49
|
+
ret = to_s_line
|
50
|
+
else
|
51
|
+
arr = []
|
52
|
+
multiple.each do |x|
|
53
|
+
arr << x.to_s_line
|
54
|
+
end
|
55
|
+
ret = arr.join("")
|
16
56
|
end
|
57
|
+
ret
|
58
|
+
end
|
17
59
|
|
18
|
-
|
19
|
-
|
20
|
-
|
60
|
+
def to_s_line
|
61
|
+
line = group ? "#{group}." : ""
|
62
|
+
line << name.to_s.tr("_", "-")
|
21
63
|
|
22
|
-
|
23
|
-
|
64
|
+
(params || {}).each do |p|
|
65
|
+
line << ";#{p}"
|
66
|
+
end
|
24
67
|
|
25
|
-
|
26
|
-
end
|
68
|
+
line << ":#{value}"
|
27
69
|
|
28
|
-
|
70
|
+
line = Vobject::fold_line(line) << "\n"
|
29
71
|
|
30
|
-
|
72
|
+
line
|
73
|
+
end
|
31
74
|
|
32
|
-
|
75
|
+
def to_norm
|
76
|
+
if @norm.nil?
|
77
|
+
if multiple.nil? || multiple.empty?
|
78
|
+
ret = to_norm_line
|
79
|
+
else
|
80
|
+
arr = []
|
81
|
+
multiple.sort.each do |x|
|
82
|
+
arr << x.to_norm_line
|
83
|
+
end
|
84
|
+
ret = arr.join("")
|
85
|
+
end
|
86
|
+
@norm = ret
|
33
87
|
end
|
88
|
+
@norm
|
89
|
+
end
|
34
90
|
|
35
|
-
|
91
|
+
def to_norm_line
|
92
|
+
line = group ? "#{group}." : ""
|
93
|
+
line << name.to_s.tr("_", "-").upcase
|
36
94
|
|
37
|
-
|
38
|
-
|
95
|
+
(params || {}).sort.each do |p|
|
96
|
+
line << ";#{p.to_norm}"
|
39
97
|
end
|
40
98
|
|
41
|
-
|
42
|
-
parse_method = :"parse_#{value_type}_value"
|
43
|
-
parse_method = respond_to?(parse_method, true) ? parse_method : :parse_text_value
|
44
|
-
send(parse_method, value)
|
45
|
-
end
|
99
|
+
line << ":#{value.to_norm}"
|
46
100
|
|
47
|
-
|
48
|
-
value
|
49
|
-
end
|
101
|
+
line = Vobject::fold_line(line) << "\n"
|
50
102
|
|
51
|
-
|
52
|
-
|
53
|
-
end
|
103
|
+
line
|
104
|
+
end
|
54
105
|
|
55
|
-
def default_value_type
|
56
|
-
"text"
|
57
|
-
end
|
58
106
|
|
59
|
-
|
60
|
-
|
107
|
+
def to_hash
|
108
|
+
ret = {}
|
109
|
+
if multiple
|
110
|
+
ret[prop_name] = []
|
111
|
+
multiple.each do |c|
|
112
|
+
ret[prop_name] = ret[prop_name] << c.to_hash[prop_name]
|
113
|
+
end
|
114
|
+
else
|
115
|
+
ret = {prop_name => { value: value.to_hash } }
|
116
|
+
ret[prop_name][:group] = group unless group.nil?
|
117
|
+
if params
|
118
|
+
ret[prop_name][:params] = {}
|
119
|
+
params.each do |p|
|
120
|
+
ret[prop_name][:params] = ret[prop_name][:params].merge p.to_hash
|
121
|
+
end
|
122
|
+
end
|
61
123
|
end
|
124
|
+
ret
|
125
|
+
end
|
62
126
|
|
63
|
-
|
64
|
-
# http://tools.ietf.org/html/rfc6350#section-3.2
|
65
|
-
#
|
66
|
-
# NOTE: the "line" here is not including the trailing \n
|
67
|
-
def fold_line(line)
|
68
|
-
folded_line = line[0, MAX_LINE_WIDTH]
|
69
|
-
remainder_line = line[MAX_LINE_WIDTH, line.length - MAX_LINE_WIDTH] || ''
|
127
|
+
private
|
70
128
|
|
71
|
-
|
129
|
+
def name
|
130
|
+
prop_name
|
131
|
+
end
|
72
132
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
133
|
+
def parse_value(value)
|
134
|
+
parse_method = :"parse_#{value_type}_value"
|
135
|
+
parse_method = respond_to?(parse_method, true) ? parse_method : :parse_text_value
|
136
|
+
send(parse_method, value)
|
137
|
+
end
|
77
138
|
|
78
|
-
|
79
|
-
|
139
|
+
def parse_text_value(value)
|
140
|
+
value
|
141
|
+
end
|
80
142
|
|
143
|
+
def value_type
|
144
|
+
params ? params[0].value : default_value_type
|
81
145
|
end
|
82
146
|
|
147
|
+
def default_value_type
|
148
|
+
"text"
|
149
|
+
end
|
150
|
+
|
151
|
+
def property_base_class
|
152
|
+
Vobject::Property
|
153
|
+
end
|
154
|
+
|
155
|
+
def parameter_base_class
|
156
|
+
Vobject::Parameter
|
157
|
+
end
|
158
|
+
|
159
|
+
def raise_invalid_initialization
|
160
|
+
raise "vObject property initialization failed"
|
161
|
+
end
|
83
162
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Vobject
|
2
|
+
class PropertyValue
|
3
|
+
attr_accessor :value, :type, :errors, :norm
|
4
|
+
|
5
|
+
def <=>(another)
|
6
|
+
self.value <=> another.value
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(val)
|
10
|
+
self.value = val
|
11
|
+
self.type = "text" # safe default
|
12
|
+
self.norm = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
# raise_invalid_initialization if key != name
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
value
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_norm
|
22
|
+
if norm.nil?
|
23
|
+
norm = to_s
|
24
|
+
end
|
25
|
+
norm
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_hash
|
29
|
+
value
|
30
|
+
end
|
31
|
+
|
32
|
+
def name
|
33
|
+
type
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def default_value_type
|
39
|
+
"text"
|
40
|
+
end
|
41
|
+
|
42
|
+
def raise_invalid_initialization
|
43
|
+
raise "vObject property initialization failed"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require "vobject"
|
2
|
+
require "vobject/property"
|
3
|
+
require "vobject/vcalendar/grammar"
|
4
|
+
require "json"
|
5
|
+
|
6
|
+
class Vobject::Component::Vcalendar < Vobject::Component
|
7
|
+
attr_accessor :comp_name, :children
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def parse(vcf, strict)
|
11
|
+
hash = Vobject::Vcalendar::Grammar.new(strict).parse(vcf)
|
12
|
+
comp_name = hash.keys.first
|
13
|
+
|
14
|
+
new comp_name, hash[comp_name], hash[:errors]
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(key, cs)
|
18
|
+
# super key, cs
|
19
|
+
self.comp_name = key
|
20
|
+
raise_invalid_initialization if key != name
|
21
|
+
|
22
|
+
self.children = []
|
23
|
+
if cs.is_a?(Array)
|
24
|
+
cs.each do |component|
|
25
|
+
c = []
|
26
|
+
component.each_key do |k|
|
27
|
+
val = component[k]
|
28
|
+
# iteration of array || hash values is making the value a key!
|
29
|
+
next if k.class == Array
|
30
|
+
next if k.class == Hash
|
31
|
+
cc = child_class(k, val)
|
32
|
+
c << cc.new(k, val)
|
33
|
+
end
|
34
|
+
children << c
|
35
|
+
end
|
36
|
+
else
|
37
|
+
cs.each_key do |k|
|
38
|
+
val = cs[k]
|
39
|
+
# iteration of array || hash values is making the value a key!
|
40
|
+
next if k.class == Array
|
41
|
+
next if k.class == Hash
|
42
|
+
cc = child_class(k, val)
|
43
|
+
children << cc.new(k, val)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def child_class(key, val)
|
49
|
+
base_class = if key == :VTODO
|
50
|
+
Vobject::Component::Vcalendar::ToDo
|
51
|
+
elsif key == :VFREEBUSY
|
52
|
+
Vobject::Component::Vcalendar::FreeBusy
|
53
|
+
elsif key == :JOURNAL
|
54
|
+
Vobject::Component::Vcalendar::Journal
|
55
|
+
elsif key == :STANDARD
|
56
|
+
Vobject::Component::Vcalendar::Timezone::Standard
|
57
|
+
elsif key == :DAYLIGHT
|
58
|
+
Vobject::Component::Vcalendar::Timezone::Daylight
|
59
|
+
elsif key == :VTIMEZONE
|
60
|
+
Vobject::Component::Vcalendar::Timezone
|
61
|
+
elsif key == :VEVENT
|
62
|
+
Vobject::Component::Vcalendar::Event
|
63
|
+
elsif key == :VALARM
|
64
|
+
Vobject::Component::Vcalendar::Alarm
|
65
|
+
elsif key == :VAVAILABILITY
|
66
|
+
Vobject::Component::Vcalendar::Vavailability
|
67
|
+
elsif key == :AVAILABLE
|
68
|
+
Vobject::Component::Vcalendar::Vavailability::Available
|
69
|
+
elsif !(val.is_a?(Hash) && !val.has_key?(:value))
|
70
|
+
property_base_class
|
71
|
+
else
|
72
|
+
Vobject::Component::Vcalendar
|
73
|
+
end
|
74
|
+
return base_class if [:CLASS, :OBJECT, :METHOD].include? key
|
75
|
+
camelized_key = key.to_s.downcase.split("_").map(&:capitalize).join("")
|
76
|
+
base_class.const_get(camelized_key) rescue base_class
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def raise_invalid_parsing
|
82
|
+
raise "Vobject component parse failed"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class Vobject::Component::Vcalendar::ToDo < Vobject::Component::Vcalendar
|
88
|
+
end
|
89
|
+
class Vobject::Component::Vcalendar::Freebusy < Vobject::Component::Vcalendar
|
90
|
+
end
|
91
|
+
class Vobject::Component::Vcalendar::Journal < Vobject::Component::Vcalendar
|
92
|
+
end
|
93
|
+
class Vobject::Component::Vcalendar::Timezone < Vobject::Component::Vcalendar
|
94
|
+
end
|
95
|
+
class Vobject::Component::Vcalendar::Timezone::Standard < Vobject::Component::Vcalendar::Timezone
|
96
|
+
end
|
97
|
+
class Vobject::Component::Vcalendar::Timezone::Daylight < Vobject::Component::Vcalendar::Timezone
|
98
|
+
end
|
99
|
+
class Vobject::Component::Vcalendar::Event < Vobject::Component::Vcalendar
|
100
|
+
end
|
101
|
+
class Vobject::Component::Vcalendar::Alarm < Vobject::Component::Vcalendar
|
102
|
+
end
|
103
|
+
class Vobject::Component::Vcalendar::Vavailability < Vobject::Component::Vcalendar
|
104
|
+
end
|
105
|
+
class Vobject::Component::Vcalendar::Vavailability::Available < Vobject::Component::Vcalendar
|
106
|
+
end
|
@@ -0,0 +1,595 @@
|
|
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 "vobject"
|
10
|
+
require "vobject/vcalendar/typegrammars"
|
11
|
+
require "vobject/vcalendar/paramcheck"
|
12
|
+
|
13
|
+
module Vobject::Vcalendar
|
14
|
+
class Grammar
|
15
|
+
include C
|
16
|
+
attr_accessor :strict, :errors
|
17
|
+
|
18
|
+
class << self
|
19
|
+
# RFC 6868
|
20
|
+
def rfc6868decode(x)
|
21
|
+
x.gsub(/\^n/, "\n").gsub(/\^\^/, "^").gsub(/\^'/, '"')
|
22
|
+
end
|
23
|
+
|
24
|
+
def unfold(str)
|
25
|
+
str.gsub(/(\r|\n|\r\n)[ \t]/, "")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def vobject_grammar
|
30
|
+
# properties with value cardinality 1
|
31
|
+
@cardinality1 = {}
|
32
|
+
@cardinality1[:ICAL] = Set.new [:PRODID, :VERSION, :CALSCALE, :METHOD, :UID, :LAST_MOD, :URL,
|
33
|
+
:REFRESH_INTERVAL, :SOURCE, :COLOR]
|
34
|
+
@cardinality1[:EVENT] = Set.new [:UID, :DTSTAMP, :DTSTART, :CLASS, :CREATED, :DESCRIPTION, :GEO, :LAST_MOD,
|
35
|
+
:LOCATION, :ORGANIZER, :PRIORITY, :SEQ, :STATUS, :TRANSP, :URL, :RECURID, :COLOR]
|
36
|
+
@cardinality1[:TODO] = Set.new [:UID, :DTSTAMP, :CLASS, :COMPLETED, :CREATED, :DESCRIPTION, :DTSTART, :GEO, :LAST_MOD,
|
37
|
+
:LOCATION, :ORGANIZER, :PERCENT_COMPLETE, :PRIORITY, :SEQ, :STATUS, :SUMMARY, :URL, :RECURID, :COLOR]
|
38
|
+
@cardinality1[:JOURNAL] = Set.new [:UID, :DTSTAMP, :CLASS, :CREATED, :DTSTART, :LAST_MOD,
|
39
|
+
:ORGANIZER, :SEQ, :STATUS, :SUMMARY, :URL, :RECURID, :COLOR]
|
40
|
+
@cardinality1[:FREEBUSY] = Set.new [:UID, :DTSTAMP, :CONTACT, :DTSTART, :DTEND, :ORGANIZER, :URL]
|
41
|
+
@cardinality1[:TIMEZONE] = Set.new [:TZID, :LAST_MOD, :TZURL]
|
42
|
+
@cardinality1[:TZ] = Set.new [:DTSTART, :TZOFFSETTTO, :TZOFFSETFROM]
|
43
|
+
@cardinality1[:ALARM] = Set.new [:ACTION, :TRIGGER, :DURATION, :REPEAT, :DESCRIPTION, :SUMMARY]
|
44
|
+
@cardinality1[:VAVAILABILITY] = Set.new [:UID, :DTSTAMP, :DTSTART, :BUSYTYPE, :CLASS, :CREATED, :DESCRIPTION, :LAST_MOD,
|
45
|
+
:LOCATION, :ORGANIZER, :PRIORITY, :SEQ, :SUMMARY, :URL]
|
46
|
+
@cardinality1[:AVAILABLE] = Set.new [:DTSTAMP, :DTSTART, :UID, :CREATED, :DESCRIPTION, :LAST_MOD, :LOCATION,
|
47
|
+
:RECURID, :RRULE, :SUMMARY]
|
48
|
+
@cardinality1[:PARAM] = Set.new [:FMTTYPE, :LANGUAGE, :ALTREP, :FBTYPE, :TRANSP, :CUTYPE, :MEMBER, :ROLE, :PARTSTAT, :RSVP, :DELEGATED_TO,
|
49
|
+
:DELEGATED_FROM, :SENT_BY, :CN, :DIR, :RANGE, :RELTYPE, :RELATED, :DISPLAY, :FEATURE, :LABEL]
|
50
|
+
|
51
|
+
group = C::IANATOKEN
|
52
|
+
linegroup = group << "."
|
53
|
+
beginend = /BEGIN/i.r | /END/i.r
|
54
|
+
|
55
|
+
# parameters && parameter types
|
56
|
+
paramname = /ALTREP/i.r | /CN/i.r | /CUTYPE/i.r | /DELEGATED-FROM/i.r | /DELEGATED-TO/i.r |
|
57
|
+
/DIR/i.r | /ENCODING/i.r | /FMTTYPE/i.r | /FBTYPE/i.r | /LANGUAGE/i.r |
|
58
|
+
/MEMBER/i.r | /PARTSTAT/i.r | /RANGE/i.r | /RELATED/i.r | /RELTYPE/i.r |
|
59
|
+
/ROLE/i.r | /RSVP/i.r | /SENT-BY/i.r | /TZID/i.r | /RSCALE/i.r | /DISPLAY/i.r |
|
60
|
+
/FEATURE/i.r | /LABEL/i.r | /EMAIL/i.r
|
61
|
+
otherparamname = C::XNAME_VCAL | seq("".r ^ paramname, C::IANATOKEN)[1]
|
62
|
+
paramvalue = C::QUOTEDSTRING_VCAL.map { |s| self.class.rfc6868decode s } |
|
63
|
+
C::PTEXT_VCAL.map { |s| self.class.rfc6868decode(s) }
|
64
|
+
quotedparamvalue = C::QUOTEDSTRING_VCAL.map { |s| self.class.rfc6868decode s }
|
65
|
+
cutypevalue = /INDIVIDUAL/i.r | /GROUP/i.r | /RESOURCE/i.r | /ROOM/i.r | /UNKNOWN/i.r |
|
66
|
+
C::XNAME_VCAL | C::IANATOKEN.map
|
67
|
+
encodingvalue = /8BIT/i.r | /BASE64/i.r
|
68
|
+
fbtypevalue = /FREE/i.r | /BUSY/i.r | /BUSY-UNAVAILABLE/i.r | /BUSY-TENTATIVE/i.r |
|
69
|
+
C::XNAME_VCAL | C::IANATOKEN
|
70
|
+
partstatevent = /NEEDS-ACTION/i.r | /ACCEPTED/i.r | /DECLINED/i.r | /TENTATIVE/i.r |
|
71
|
+
/DELEGATED/i.r | C::XNAME_VCAL | C::IANATOKEN
|
72
|
+
partstattodo = /NEEDS-ACTION/i.r | /ACCEPTED/i.r | /DECLINED/i.r | /TENTATIVE/i.r |
|
73
|
+
/DELEGATED/i.r | /COMPLETED/i.r | /IN-PROCESS/i.r | C::XNAME_VCAL | C::IANATOKEN
|
74
|
+
partstatjour = /NEEDS-ACTION/i.r | /ACCEPTED/i.r | /DECLINED/i.r | C::XNAME_VCAL | C::IANATOKEN
|
75
|
+
partstatvalue = partstatevent | partstattodo | partstatjour
|
76
|
+
rangevalue = /THISANDFUTURE/i.r
|
77
|
+
relatedvalue = /START/i.r | /END/i.r
|
78
|
+
reltypevalue = /PARENT/i.r | /CHILD/i.r | /SIBLING/i.r | C::XNAME_VCAL | C::IANATOKEN
|
79
|
+
tzidvalue = seq("/".r._?, C::PTEXT_VCAL).map { |_, val| val }
|
80
|
+
valuetype = /BINARY/i.r | /BOOLEAN/i.r | /CAL-ADDRESS/i.r | /DATE-TIME/i.r | /DATE/i.r |
|
81
|
+
/DURATION/i.r | /FLOAT/i.r | /INTEGER/i.r | /PERIOD/i.r | /RECUR/i.r | /TEXT/i.r |
|
82
|
+
/TIME/i.r | /URI/i.r | /UTC-OFFSET/i.r | C::XNAME_VCAL | C::IANATOKEN
|
83
|
+
rolevalue = /CHAIR/i.r | /REQ-PARTICIPANT/i.r | /OPT-PARTICIPANT/i.r | /NON-PARTICIPANT/i.r |
|
84
|
+
C::XNAME_VCAL | C::IANATOKEN
|
85
|
+
pvalue_list = (seq(paramvalue << ",".r, lazy { pvalue_list }) & /[;:]/.r).map do |e, list|
|
86
|
+
[e.sub(Regexp.new("^\"(.+)\"$"), '\1').gsub(/\\n/, "\n"), list].flatten
|
87
|
+
end | (paramvalue & /[;:]/.r).map do |e|
|
88
|
+
[e.sub(Regexp.new("^\"(.+)\"$"), '\1').gsub(/\\n/, "\n")]
|
89
|
+
end
|
90
|
+
quoted_string_list = (seq(C::QUOTEDSTRING_VCAL << ",".r, lazy { quoted_string_list }) & /[;:]/.r).map do |e, list|
|
91
|
+
[self.class.rfc6868decode(e.sub(Regexp.new("^\"(.+)\"$"), "\1").gsub(/\\n/, "\n")), list].flatten
|
92
|
+
end | (C::QUOTEDSTRING_VCAL & /[;:]/.r).map do |e|
|
93
|
+
[self.class.rfc6868decode(e.sub(Regexp.new("^\"(.+)\"$"), "\1").gsub(/\\n/, "\n"))]
|
94
|
+
end
|
95
|
+
|
96
|
+
rfc4288regname = /[A-Za-z0-9!#$&.+^+-]{1,127}/.r
|
97
|
+
rfc4288typename = rfc4288regname
|
98
|
+
rfc4288subtypename = rfc4288regname
|
99
|
+
fmttypevalue = seq(rfc4288typename, "/", rfc4288subtypename).map(&:join)
|
100
|
+
|
101
|
+
# RFC 7986
|
102
|
+
displayval = /BADGE/i.r | /GRAPHIC/i.r | /FULLSIZE/i.r | /THUMBNAIL/i.r | C::XNAME_VCAL | C::IANATOKEN
|
103
|
+
displayvallist = seq(displayval << ",".r, lazy { displayvallist }) do |d, l|
|
104
|
+
[d, l].flatten
|
105
|
+
end | displayval.map { |d| [d] }
|
106
|
+
featureval = /AUDIO/i.r | /CHAT/i.r | /FEED/i.r | /MODERATOR/i.r | /PHONE/i.r | /SCREEN/i.r |
|
107
|
+
/VIDEO/i.r | C::XNAME_VCAL | C::IANATOKEN
|
108
|
+
featurevallist = seq(featureval << ",".r, lazy { featurevallist }) do |d, l|
|
109
|
+
[d, l].flatten
|
110
|
+
end | featureval.map { |d| [d] }
|
111
|
+
|
112
|
+
param = seq(/ALTREP/i.r, "=", quotedparamvalue) do |name, _, val|
|
113
|
+
{ name.upcase.tr("-", "_").to_sym => val }
|
114
|
+
end | seq(/CN/i.r, "=", paramvalue) do |name, _, val|
|
115
|
+
{ name.upcase.tr("-", "_").to_sym => val }
|
116
|
+
end | seq(/CUTYPE/i.r, "=", cutypevalue) do |name, _, val|
|
117
|
+
{ name.upcase.tr("-", "_").to_sym => val.upcase }
|
118
|
+
end | seq(/DELEGATED-FROM/i.r, "=", quoted_string_list) do |name, _, val|
|
119
|
+
val = val[0] if val.length == 1
|
120
|
+
{ name.upcase.tr("-", "_").to_sym => val }
|
121
|
+
end | seq(/DELEGATED-TO/i.r, "=", quoted_string_list) do |name, _, val|
|
122
|
+
val = val[0] if val.length == 1
|
123
|
+
{ name.upcase.tr("-", "_").to_sym => val }
|
124
|
+
end | seq(/DIR/i.r, "=", quotedparamvalue) do |name, _, val|
|
125
|
+
{ name.upcase.tr("-", "_").to_sym => val }
|
126
|
+
end | seq(/ENCODING/i.r, "=", encodingvalue) do |name, _, val|
|
127
|
+
{ name.upcase.tr("-", "_").to_sym => val.upcase }
|
128
|
+
end | seq(/FMTTYPE/i.r, "=", fmttypevalue) do |name, _, val|
|
129
|
+
{ name.upcase.tr("-", "_").to_sym => val.downcase }
|
130
|
+
end | seq(/FBTYPE/i.r, "=", fbtypevalue) do |name, _, val|
|
131
|
+
{ name.upcase.tr("-", "_").to_sym => val.upcase }
|
132
|
+
end | seq(/LANGUAGE/i.r, "=", C::RFC5646LANGVALUE) do |name, _, val|
|
133
|
+
{ name.upcase.tr("-", "_").to_sym => val.upcase }
|
134
|
+
end | seq(/MEMBER/i.r, "=", quoted_string_list) do |name, _, val|
|
135
|
+
val = val[0] if val.length == 1
|
136
|
+
{ name.upcase.tr("-", "_").to_sym => val }
|
137
|
+
end | seq(/PARTSTAT/i.r, "=", partstatvalue) do |name, _, val|
|
138
|
+
{ name.upcase.tr("-", "_").to_sym => val.upcase }
|
139
|
+
end | seq(/RANGE/i.r, "=", rangevalue) do |name, _, val|
|
140
|
+
{ name.upcase.tr("-", "_").to_sym => val.upcase }
|
141
|
+
end | seq(/RELATED/i.r, "=", relatedvalue) do |name, _, val|
|
142
|
+
{ name.upcase.tr("-", "_").to_sym => val.upcase }
|
143
|
+
end | seq(/RELTYPE/i.r, "=", reltypevalue) do |name, _, val|
|
144
|
+
{ name.upcase.tr("-", "_").to_sym => val.upcase }
|
145
|
+
end | seq(/ROLE/i.r, "=", rolevalue) do |name, _, val|
|
146
|
+
{ name.upcase.tr("-", "_").to_sym => val.upcase }
|
147
|
+
end | seq(/RSVP/i.r, "=", C::BOOLEAN) do |name, _, val|
|
148
|
+
{ name.upcase.tr("-", "_").to_sym => val }
|
149
|
+
end | seq(/SENT-BY/i.r, "=", quotedparamvalue) do |name, _, val|
|
150
|
+
{ name.upcase.tr("-", "_").to_sym => val }
|
151
|
+
end | seq(/TZID/i.r, "=", tzidvalue) do |name, _, val|
|
152
|
+
{ name.upcase.tr("-", "_").to_sym => val }
|
153
|
+
end | seq(/VALUE/i.r, "=", valuetype) do |name, _, val|
|
154
|
+
{ name.upcase.tr("-", "_").to_sym => val }
|
155
|
+
# RFC 7986
|
156
|
+
end | seq(/DISPLAY/i.r, "=", displayvallist) do |name, _, val|
|
157
|
+
{ name.upcase.tr("-", "_").to_sym => val }
|
158
|
+
end | seq(/FEATURE/i.r, "=", featurevallist) do |name, _, val|
|
159
|
+
{ name.upcase.tr("-", "_").to_sym => val }
|
160
|
+
end | seq(/EMAIL/i.r, "=", paramvalue) do |name, _, val|
|
161
|
+
{ name.upcase.tr("-", "_").to_sym => val }
|
162
|
+
end | seq(/LABEL/i.r, "=", paramvalue) do |name, _, val|
|
163
|
+
{ name.upcase.tr("-", "_").to_sym => val }
|
164
|
+
end | seq(otherparamname, "=", pvalue_list) do |name, _, val|
|
165
|
+
val = val[0] if val.length == 1
|
166
|
+
{ name.upcase.tr("-", "_").to_sym => val }
|
167
|
+
end | seq(paramname, "=", pvalue_list) do |name, _, val|
|
168
|
+
parse_err("Violated format of parameter value #{name} = #{val}")
|
169
|
+
end
|
170
|
+
|
171
|
+
params = seq(";".r >> param & ";", lazy { params }) do |p, ps|
|
172
|
+
p.merge(ps) do |key, old, new|
|
173
|
+
if @cardinality1[:PARAM].include?(key)
|
174
|
+
parse_err("Violated cardinality of parameter #{key}")
|
175
|
+
end
|
176
|
+
[old, new].flatten
|
177
|
+
# deal with duplicate properties
|
178
|
+
end
|
179
|
+
end | seq(";".r >> param).map { |e| e[0] }
|
180
|
+
|
181
|
+
contentline = seq(linegroup._?, C::NAME_VCAL, params._? << ":".r,
|
182
|
+
C::VALUE, /(\r|\n|\r\n)/) do |g, name, p, value, _|
|
183
|
+
key = name.upcase.tr("-", "_").to_sym
|
184
|
+
hash = { key => { value: value } }
|
185
|
+
hash[key][:group] = g[0] unless g.empty?
|
186
|
+
errors << Paramcheck.paramcheck(strict, key, p.empty? ? {} : p[0], @ctx)
|
187
|
+
hash[key][:params] = p[0] unless p.empty?
|
188
|
+
hash
|
189
|
+
end
|
190
|
+
|
191
|
+
props = ("".r & beginend).map { {} } |
|
192
|
+
seq(contentline, lazy { props }) do |c, rest|
|
193
|
+
k = c.keys[0]
|
194
|
+
c[k][:value], errors1 = Typegrammars.typematch(strict, k, c[k][:params], :GENERIC, c[k][:value], @ctx)
|
195
|
+
errors << errors1
|
196
|
+
c.merge(rest) do |_, old, new|
|
197
|
+
[old, new].flatten
|
198
|
+
# deal with duplicate properties
|
199
|
+
end
|
200
|
+
end
|
201
|
+
alarmprops = ("".r & beginend).map { {} } |
|
202
|
+
seq(contentline, lazy { alarmprops }) do |c, rest|
|
203
|
+
k = c.keys[0]
|
204
|
+
c[k][:value], errors1 = Typegrammars.typematch(strict, k, c[k][:params], :ALARM, c[k][:value], @ctx)
|
205
|
+
errors << errors1
|
206
|
+
c.merge(rest) do |key, old, new|
|
207
|
+
if @cardinality1[:ALARM].include?(key.upcase)
|
208
|
+
parse_err("Violated cardinality of property #{key}")
|
209
|
+
end
|
210
|
+
[old, new].flatten
|
211
|
+
end
|
212
|
+
end
|
213
|
+
fbprops = ("".r & beginend).map { {} } |
|
214
|
+
seq(contentline, lazy { fbprops }) do |c, rest|
|
215
|
+
k = c.keys[0]
|
216
|
+
c[k][:value], errors1 = Typegrammars.typematch(strict, k, c[k][:params], :FREEBUSY, c[k][:value], @ctx)
|
217
|
+
errors << errors1
|
218
|
+
c.merge(rest) do |key, old, new|
|
219
|
+
if @cardinality1[:FREEBUSY].include?(key.upcase)
|
220
|
+
parse_err("Violated cardinality of property #{key}")
|
221
|
+
end
|
222
|
+
[old, new].flatten
|
223
|
+
end
|
224
|
+
end
|
225
|
+
journalprops = ("".r & beginend).map { {} } |
|
226
|
+
seq(contentline, lazy { journalprops }) do |c, rest|
|
227
|
+
k = c.keys[0]
|
228
|
+
c[k][:value], errors1 = Typegrammars.typematch(strict, k, c[k][:params], :JOURNAL, c[k][:value], @ctx)
|
229
|
+
errors << errors1
|
230
|
+
c.merge(rest) do |key, old, new|
|
231
|
+
if @cardinality1[:JOURNAL].include?(key.upcase)
|
232
|
+
parse_err("Violated cardinality of property #{key}")
|
233
|
+
end
|
234
|
+
[old, new].flatten
|
235
|
+
end
|
236
|
+
end
|
237
|
+
tzprops = ("".r & beginend).map { {} } |
|
238
|
+
seq(contentline, lazy { tzprops }) do |c, rest|
|
239
|
+
k = c.keys[0]
|
240
|
+
c[k][:value], errors1 = Typegrammars.typematch(strict, k, c[k][:params], :TZ, c[k][:value], @ctx)
|
241
|
+
errors << errors1
|
242
|
+
c.merge(rest) do |key, old, new|
|
243
|
+
if @cardinality1[:TZ].include?(key.upcase)
|
244
|
+
parse_err("Violated cardinality of property #{key}")
|
245
|
+
end
|
246
|
+
[old, new].flatten
|
247
|
+
end
|
248
|
+
end
|
249
|
+
standardc = seq(/BEGIN:STANDARD(\r|\n|\r\n)/i.r, tzprops, /END:STANDARD(\r|\n|\r\n)/i.r) do |_, e, _|
|
250
|
+
parse_err("Missing DTSTART property") unless e.has_key?(:DTSTART)
|
251
|
+
parse_err("Missing TZOFFSETTO property") unless e.has_key?(:TZOFFSETTO)
|
252
|
+
parse_err("Missing TZOFFSETFROM property") unless e.has_key?(:TZOFFSETFROM)
|
253
|
+
{ STANDARD: { component: [e] } }
|
254
|
+
end
|
255
|
+
daylightc = seq(/BEGIN:DAYLIGHT(\r|\n|\r\n)/i.r, tzprops, /END:DAYLIGHT(\r|\n|\r\n)/i.r) do |_, e, _|
|
256
|
+
parse_err("Missing DTSTART property") unless e.has_key?(:DTSTART)
|
257
|
+
parse_err("Missing TZOFFSETTO property") unless e.has_key?(:TZOFFSETTO)
|
258
|
+
parse_err("Missing TZOFFSETFROM property") unless e.has_key?(:TZOFFSETFROM)
|
259
|
+
{ DAYLIGHT: { component: [e] } }
|
260
|
+
end
|
261
|
+
timezoneprops =
|
262
|
+
seq(standardc, lazy { timezoneprops }) do |e, rest|
|
263
|
+
e.merge(rest) { |_, old, new| { component: [old[:component], new[:component]].flatten } }
|
264
|
+
end | seq(daylightc, lazy { timezoneprops }) do |e, rest|
|
265
|
+
e.merge(rest) { |_, old, new| { component: [old[:component], new[:component]].flatten } }
|
266
|
+
end | seq(contentline, lazy { timezoneprops }) do |e, rest|
|
267
|
+
k = e.keys[0]
|
268
|
+
e[k][:value], errors1 = Typegrammars.typematch(strict, k, e[k][:params], :TIMEZONE, e[k][:value], @ctx)
|
269
|
+
errors << errors1
|
270
|
+
e.merge(rest) do |key, old, new|
|
271
|
+
if @cardinality1[:TIMEZONE].include?(key.upcase)
|
272
|
+
parse_err("Violated cardinality of property #{key}")
|
273
|
+
end
|
274
|
+
[old, new].flatten
|
275
|
+
end
|
276
|
+
end |
|
277
|
+
("".r & beginend).map { {} }
|
278
|
+
todoprops = ("".r & beginend).map { {} } |
|
279
|
+
seq(contentline, lazy { todoprops }) do |c, rest|
|
280
|
+
k = c.keys[0]
|
281
|
+
c[k][:value], errors1 = Typegrammars.typematch(strict, k, c[k][:params], :TODO, c[k][:value], @ctx)
|
282
|
+
errors << errors1
|
283
|
+
c.merge(rest) do |key, old, new|
|
284
|
+
if @cardinality1[:TODO].include?(key.upcase)
|
285
|
+
parse_err("Violated cardinality of property #{key}")
|
286
|
+
end
|
287
|
+
[old, new].flatten
|
288
|
+
end
|
289
|
+
end
|
290
|
+
eventprops = seq(contentline, lazy { eventprops }) do |c, rest|
|
291
|
+
k = c.keys[0]
|
292
|
+
c[k][:value], errors1 = Typegrammars.typematch(strict, k, c[k][:params], :EVENT, c[k][:value], @ctx)
|
293
|
+
errors << errors1
|
294
|
+
c.merge(rest) do |key, old, new|
|
295
|
+
if @cardinality1[:EVENT].include?(key.upcase)
|
296
|
+
parse_err("Violated cardinality of property #{key}")
|
297
|
+
end
|
298
|
+
[old, new].flatten
|
299
|
+
end
|
300
|
+
end |
|
301
|
+
("".r & beginend).map { {} }
|
302
|
+
alarmc = seq(/BEGIN:VALARM(\r|\n|\r\n)/i.r, alarmprops, /END:VALARM(\r|\n|\r\n)/i.r) do |_, e, _|
|
303
|
+
parse_err("Missing ACTION property") unless e.has_key?(:ACTION)
|
304
|
+
parse_err("Missing TRIGGER property") unless e.has_key?(:TRIGGER)
|
305
|
+
if e.has_key?(:DURATION) && !e.has_key?(:REPEAT) || !e.has_key?(:DURATION) && e.has_key?(:REPEAT)
|
306
|
+
parse_err("Missing DURATION && REPEAT properties")
|
307
|
+
end
|
308
|
+
if e[:ACTION] == "AUDIO"
|
309
|
+
parse_err("Multiple ATTACH properties") if e.has_key?(:ATTACH) && e[:ATTACH].is_a?(Array)
|
310
|
+
parse_err("Invalid DESCRIPTION property") if e.has_key?(:DESCRIPTION)
|
311
|
+
parse_err("Invalid SUMMARY property") if e.has_key?(:SUMMARY)
|
312
|
+
parse_err("Invalid ATTENDEE property") if e.has_key?(:ATTENDEE)
|
313
|
+
elsif e[:ACTION] == "DISP"
|
314
|
+
parse_err("Missing DESCRIPTION property") unless e.has_key?(:DESCRIPTION)
|
315
|
+
parse_err("Invalid ATTACH property") if e.has_key?(:ATTACH)
|
316
|
+
parse_err("Invalid SUMMARY property") if e.has_key?(:SUMMARY)
|
317
|
+
parse_err("Invalid ATTENDEE property") if e.has_key?(:ATTENDEE)
|
318
|
+
elsif e[:ACTION] == "EMAIL"
|
319
|
+
parse_err("Missing DESCRIPTION property") unless e.has_key?(:DESCRIPTION)
|
320
|
+
end
|
321
|
+
{ VALARM: { component: [e] } }
|
322
|
+
end
|
323
|
+
freebusyc = seq(/BEGIN:VFREEBUSY(\r|\n|\r\n)/i.r, fbprops, /END:VFREEBUSY(\r|\n|\r\n)/i.r) do |_, e, _|
|
324
|
+
parse_err("Missing DTSTAMP property") unless e.has_key?(:DTSTAMP)
|
325
|
+
parse_err("Missing UID property") unless e.has_key?(:UID)
|
326
|
+
parse_err("DTEND before DTSTART") if e.has_key?(:DTEND) && e.has_key?(:DTSTART) &&
|
327
|
+
e[:DTEND][:value] < e[:DTSTART][:value]
|
328
|
+
{ VFREEBUSY: { component: [e] } }
|
329
|
+
end
|
330
|
+
journalc = seq(/BEGIN:VJOURNAL(\r|\n|\r\n)/i.r, journalprops, /END:VJOURNAL(\r|\n|\r\n)/i.r) do |_, e, _|
|
331
|
+
parse_err("Missing DTSTAMP property") unless e.has_key?(:DTSTAMP)
|
332
|
+
parse_err("Missing UID property") unless e.has_key?(:UID)
|
333
|
+
parse_err("Missing DTSTART property with RRULE property") if e.has_key?(:RRULE) && !e.has_key?(:DTSTART)
|
334
|
+
{ VJOURNAL: { component: [e] } }
|
335
|
+
end
|
336
|
+
timezonec = seq(/BEGIN:VTIMEZONE(\r|\n|\r\n)/i.r, timezoneprops, /END:VTIMEZONE(\r|\n|\r\n)/i.r) do |_, e, _|
|
337
|
+
parse_err("Missing STANDARD || DAYLIGHT property") unless e.has_key?(:STANDARD) || e.has_key?(:DAYLIGHT)
|
338
|
+
{ VTIMEZONE: { component: [e] } }
|
339
|
+
end
|
340
|
+
todoc = seq(/BEGIN:VTODO(\r|\n|\r\n)/i.r, todoprops, alarmc.star, /END:VTODO(\r|\n|\r\n)/i.r) do |_, e, a, _|
|
341
|
+
parse_err("Missing DTSTAMP property") unless e.has_key?(:DTSTAMP)
|
342
|
+
parse_err("Missing UID property") unless e.has_key?(:UID)
|
343
|
+
parse_err("Coocurring DUE && DURATION properties") if e.has_key?(:DUE) && e.has_key?(:DURATION)
|
344
|
+
parse_err("Missing DTSTART property with DURATION property") if e.has_key?(:DURATION) && !e.has_key?(:DTSTART)
|
345
|
+
parse_err("Missing DTSTART property with RRULE property") if e.has_key?(:RRULE) && !e.has_key?(:DTSTART)
|
346
|
+
parse_err("DUE before DTSTART") if e.has_key?(:DUE) &&
|
347
|
+
e.has_key?(:DTSTART) &&
|
348
|
+
e[:DUE][:value] < e[:DTSTART][:value]
|
349
|
+
# TODO not doing constraint that due && dtstart are both || neither local time
|
350
|
+
# TODO not doing constraint that recurrence-id && dtstart are both || neither local time
|
351
|
+
# TODO not doing constraint that recurrence-id && dtstart are both || neither date
|
352
|
+
a.each do |x|
|
353
|
+
e = e.merge(x) { |_, old, new| { component: [old[:component], new[:component]].flatten } }
|
354
|
+
end
|
355
|
+
{ VTODO: { component: [e] } }
|
356
|
+
end
|
357
|
+
eventc = seq(/BEGIN:VEVENT(\r|\n|\r\n)/i.r, eventprops, alarmc.star, /END:VEVENT(\r|\n|\r\n)/i.r) do |_, e, a, _|
|
358
|
+
parse_err("Missing DTSTAMP property") unless e.has_key?(:DTSTAMP)
|
359
|
+
parse_err("Missing UID property") unless e.has_key?(:UID)
|
360
|
+
parse_err("Coocurring DTEND && DURATION properties") if e.has_key?(:DTEND) && e.has_key?(:DURATION)
|
361
|
+
parse_err("Missing DTSTART property with RRULE property") if e.has_key?(:RRULE) && !e.has_key?(:DTSTART)
|
362
|
+
parse_err("DTEND before DTSTART") if e.has_key?(:DTEND) &&
|
363
|
+
e.has_key?(:DTSTART) &&
|
364
|
+
e[:DTEND][:value] < e[:DTSTART][:value]
|
365
|
+
# TODO not doing constraint that dtend && dtstart are both || neither local time
|
366
|
+
a.each do |x|
|
367
|
+
e = e.merge(x) { |_, old, new| { component: [old[:component], new[:component]].flatten } }
|
368
|
+
end
|
369
|
+
{ VEVENT: { component: [e] } }
|
370
|
+
end
|
371
|
+
xcomp = seq(/BEGIN:/i.r, C::XNAME_VCAL, /(\r|\n|\r\n)/i.r, props, /END:/i.r, C::XNAME_VCAL, /(\r|\n|\r\n)/i.r) do |_, n, _, p, _, n1, _|
|
372
|
+
n = n.upcase
|
373
|
+
n1 = n1.upcase
|
374
|
+
parse_err("Mismatch BEGIN:#{n}, END:#{n1}") if n != n1
|
375
|
+
{ n1.to_sym => { component: [p] } }
|
376
|
+
end
|
377
|
+
ianacomp = seq(/BEGIN:/i.r ^ C::ICALPROPNAMES, C::IANATOKEN, /(\r|\n|\r\n)/i.r, props, /END:/i.r ^ C::ICALPROPNAMES, C::IANATOKEN, /(\r|\n|\r\n)/i.r) do |_, n, _, p, _, n1, _|
|
378
|
+
n = n.upcase
|
379
|
+
n1 = n1.upcase
|
380
|
+
parse_err("Mismatch BEGIN:#{n}, END:#{n1}") if n != n1
|
381
|
+
{ n1.to_sym => { component: [p] } }
|
382
|
+
end
|
383
|
+
# RFC 7953
|
384
|
+
availableprops = seq(contentline, lazy { availableprops }) do |c, rest|
|
385
|
+
k = c.keys[0]
|
386
|
+
c[k][:value], errors1 = Typegrammars.typematch(strict, k, c[k][:params], :AVAILABLE, c[k][:value], @ctx)
|
387
|
+
errors << errors1
|
388
|
+
c.merge(rest) do |key, old, new|
|
389
|
+
if @cardinality1[:AVAILABLE].include?(key.upcase)
|
390
|
+
parse_err("Violated cardinality of property #{key}")
|
391
|
+
end
|
392
|
+
[old, new].flatten
|
393
|
+
end
|
394
|
+
end | ("".r & beginend).map { {} }
|
395
|
+
availablec = seq(/BEGIN:AVAILABLE(\r|\n|\r\n)/i.r, availableprops, /END:AVAILABLE(\r|\n|\r\n)/i.r) do |_, e, _|
|
396
|
+
# parse_err("Missing DTSTAMP property") unless e.has_key?(:DTSTAMP) # required in spec, but not in examples
|
397
|
+
parse_err("Missing DTSTART property") unless e.has_key?(:DTSTART)
|
398
|
+
parse_err("Missing UID property") unless e.has_key?(:UID)
|
399
|
+
parse_err("Coocurring DTEND && DURATION properties") if e.has_key?(:DTEND) && e.has_key?(:DURATION)
|
400
|
+
{ AVAILABLE: { component: [e] } }
|
401
|
+
end
|
402
|
+
availabilityprops = seq(contentline, lazy { availabilityprops }) do |c, rest|
|
403
|
+
k = c.keys[0]
|
404
|
+
c[k][:value], errors1 = Typegrammars.typematch(strict, k, c[k][:params], :VAVAILABILITY, c[k][:value], @ctx)
|
405
|
+
errors << errors1
|
406
|
+
c.merge(rest) do |key, old, new|
|
407
|
+
if @cardinality1[:VAVAILABILITY].include?(key.upcase)
|
408
|
+
parse_err("Violated cardinality of property #{key}")
|
409
|
+
end
|
410
|
+
[old, new].flatten
|
411
|
+
end
|
412
|
+
end | ("".r & beginend).map { {} }
|
413
|
+
vavailabilityc = seq(/BEGIN:VAVAILABILITY(\r|\n|\r\n)/i.r, availabilityprops, availablec.star, /END:VAVAILABILITY(\r|\n|\r\n)/i.r) do |_, e, a, _|
|
414
|
+
parse_err("Missing DTSTAMP property") unless e.has_key?(:DTSTAMP)
|
415
|
+
parse_err("Missing UID property") unless e.has_key?(:UID)
|
416
|
+
parse_err("Coocurring DTEND && DURATION properties") if e.has_key?(:DTEND) && e.has_key?(:DURATION)
|
417
|
+
parse_err("Missing DTSTART property with DURATION property") if e.has_key?(:DURATION) && !e.has_key?(:DTSTART)
|
418
|
+
parse_err("DTEND before DTSTART") if e.has_key?(:DTEND) && e.has_key?(:DTSTART) && e[:DTEND][:value] < e[:DTSTART][:value]
|
419
|
+
# TODO not doing constraint that dtend && dtstart are both || neither local time
|
420
|
+
# TODO not doing constraint that each TZID param must have matching VTIMEZONE component
|
421
|
+
a.each do |x|
|
422
|
+
e = e.merge(x) { |_key, old, new| { component: [old[:component], new[:component]].flatten } }
|
423
|
+
end
|
424
|
+
{ VAVAILABILITY: { component: [e] } }
|
425
|
+
end
|
426
|
+
|
427
|
+
component = eventc | todoc | journalc | freebusyc | timezonec | ianacomp | xcomp | vavailabilityc
|
428
|
+
components = seq(component, lazy { components }) do |c, r|
|
429
|
+
c.merge(r) do |_key, old, new|
|
430
|
+
{ component: [old[:component], new[:component]].flatten }
|
431
|
+
end
|
432
|
+
end | component
|
433
|
+
|
434
|
+
calpropname = /CALSCALE/i.r | /METHOD/i.r | /PRODID/i.r | /VERSION/i.r |
|
435
|
+
/UID/i.r | /LAST-MOD/i.r | /URL/i.r | /REFRESH/i.r | /SOURCE/i.r | /COLOR/i.r | # RFC 7986
|
436
|
+
/NAME/i.r | /DESCRIPTION/i.r | /CATEGORIES/i.r | /IMAGE/i.r | # RFC 7986
|
437
|
+
C::XNAME_VCAL | C::IANATOKEN
|
438
|
+
calprop = seq(calpropname, params._? << ":".r, C::VALUE, /(\r|\n|\r\n)/) do |key, p, value, _|
|
439
|
+
key = key.upcase.tr("-", "_").to_sym
|
440
|
+
val, errors1 = Typegrammars.typematch(strict, key, p[0], :CALENDAR, value, @ctx)
|
441
|
+
errors << errors1
|
442
|
+
hash = { key => { value: val } }
|
443
|
+
errors << Paramcheck.paramcheck(strict, key, p.empty? ? {} : p[0], @ctx)
|
444
|
+
hash[key][:params] = p[0] unless p.empty?
|
445
|
+
hash
|
446
|
+
# TODO not doing constraint that each description must be in a different language
|
447
|
+
end
|
448
|
+
calprops = ("".r & beginend).map { {} } |
|
449
|
+
seq(calprop, lazy { calprops }) do |c, rest|
|
450
|
+
c.merge(rest) do |key, old, new|
|
451
|
+
if @cardinality1[:ICAL].include?(key.upcase)
|
452
|
+
parse_err("Violated cardinality of property #{key}")
|
453
|
+
end
|
454
|
+
[old, new].flatten
|
455
|
+
end
|
456
|
+
end
|
457
|
+
vobject = seq(/BEGIN:VCALENDAR(\r|\n|\r\n)/i.r, calprops, components, /END:VCALENDAR(\r|\n|\r\n)/i.r) do |_b, v, rest, _e|
|
458
|
+
parse_err("Missing PRODID attribute") unless v.has_key?(:PRODID)
|
459
|
+
parse_err("Missing VERSION attribute") unless v.has_key?(:VERSION)
|
460
|
+
rest.delete(:END)
|
461
|
+
if !v.has_key?(:METHOD) && rest.has_key?(:VEVENT)
|
462
|
+
rest[:VEVENT][:component].each do |e1|
|
463
|
+
parse_err("Missing DTSTART property from VEVENT component") if !e1.has_key?(:DTSTART)
|
464
|
+
end
|
465
|
+
end
|
466
|
+
tidyup(VCALENDAR: v.merge(rest), errors: errors.flatten)
|
467
|
+
end
|
468
|
+
vobject.eof
|
469
|
+
end
|
470
|
+
|
471
|
+
# any residual tidying of object
|
472
|
+
def tidyup(v)
|
473
|
+
# adjust any VTIMEZONE.{STANDARD|DAYLIGHT}.{DTSTART|RDATE} times from floating local to the time within the timezone component
|
474
|
+
if !v[:VCALENDAR].has_key?(:VTIMEZONE) || v[:VCALENDAR][:VTIMEZONE][:component].nil? || v[:VCALENDAR][:VTIMEZONE][:component].empty?
|
475
|
+
return v
|
476
|
+
elsif v[:VCALENDAR][:VTIMEZONE][:component].is_a?(Array)
|
477
|
+
v[:VCALENDAR][:VTIMEZONE][:component].map do |x|
|
478
|
+
timezoneadjust x
|
479
|
+
end
|
480
|
+
else
|
481
|
+
v[:VCALENDAR][:VTIMEZONE][:component] = timezoneadjust v[:VCALENDAR][:VTIMEZONE][:component]
|
482
|
+
end
|
483
|
+
v
|
484
|
+
end
|
485
|
+
|
486
|
+
def timezoneadjust(x)
|
487
|
+
if x[:TZID].nil? || x[:TZID].empty?
|
488
|
+
return x
|
489
|
+
end
|
490
|
+
# TODO deal with unregistered timezones
|
491
|
+
begin
|
492
|
+
tz = TZInfo::Timezone.get(x[:TZID][:value].value)
|
493
|
+
rescue
|
494
|
+
return x
|
495
|
+
end
|
496
|
+
[:STANDARD, :DAYLIGHT].each do |k|
|
497
|
+
next unless x.has_key?(k)
|
498
|
+
if x[k][:component].is_a?(Array)
|
499
|
+
x[k][:component].each do |y|
|
500
|
+
# subtracting an hour and a minute to avoid PeriodNotFound exceptions on the boundary between daylight saving && standard time
|
501
|
+
# if that doesn't work either, we'll rescue to floating localtime
|
502
|
+
# ... no, I will treat STANDARD times as standard, and DAYLIGHT times as daylight savings
|
503
|
+
# TODO lookup offsets applicable by parsing dates && offsets in the ical. I'd rather not.
|
504
|
+
begin
|
505
|
+
y[:DTSTART][:value].value[:time] = tz.local_to_utc(y[:DTSTART][:value].value[:time] - 3660, true) + 3660
|
506
|
+
rescue
|
507
|
+
# nop
|
508
|
+
else
|
509
|
+
y[:DTSTART][:value].value[:zone] = x[:TZID][:value].value
|
510
|
+
end
|
511
|
+
next unless y.has_key?(:RDATE)
|
512
|
+
if y[:RDATE].is_a?(Array)
|
513
|
+
y[:RDATE].each do |z|
|
514
|
+
z[:value].value.each do |w|
|
515
|
+
begin
|
516
|
+
w.value[:time] = tz.local_to_utc(w.value[:time] - 3660, true) + 3660
|
517
|
+
rescue
|
518
|
+
# nop
|
519
|
+
else
|
520
|
+
w.value[:zone] = x[:TZID][:value].value
|
521
|
+
end
|
522
|
+
end
|
523
|
+
end
|
524
|
+
else
|
525
|
+
begin
|
526
|
+
y[:RDATE][:value].value[:time] = tz.local_to_utc(y[:RDATE].value[:time] - 3660, true) + 3660
|
527
|
+
rescue
|
528
|
+
# nop
|
529
|
+
else
|
530
|
+
y[:RDATE][:value].value[:zone] = x[:TZID][:value].value
|
531
|
+
end
|
532
|
+
end
|
533
|
+
end
|
534
|
+
else
|
535
|
+
begin
|
536
|
+
x[k][:component][:DTSTART][:value].value[:time] = tz.local_to_utc(x[k][:component][:DTSTART][:value].value[:time] - 3660, true) + 3660
|
537
|
+
rescue
|
538
|
+
# nop
|
539
|
+
else
|
540
|
+
x[k][:component][:DTSTART][:value].value[:zone] = x[:TZID][:value].value
|
541
|
+
end
|
542
|
+
next unless x[k][:component].has_key?(:RDATE)
|
543
|
+
if x[k][:component][:RDATE].is_a?(Array)
|
544
|
+
x[k][:component][:RDATE].each do |z|
|
545
|
+
z[:value].value.each do |w|
|
546
|
+
begin
|
547
|
+
w.value[:time] = tz.local_to_utc(w.value[:time] - 3660, true) + 3660
|
548
|
+
rescue
|
549
|
+
# nop
|
550
|
+
else
|
551
|
+
w.value[:zone] = x[:TZID][:value].value
|
552
|
+
end
|
553
|
+
end
|
554
|
+
end
|
555
|
+
else
|
556
|
+
begin
|
557
|
+
x[k][:component][:RDATE][:value].value[:time] = tz.local_to_utc(x[k][:component][:RDATE][:value].value[:time] - 3660, true) + 3660
|
558
|
+
rescue
|
559
|
+
# nop
|
560
|
+
else
|
561
|
+
x[k][:component][:RDATE][:value].value[:zone] = x[:TZID][:value].value
|
562
|
+
end
|
563
|
+
end
|
564
|
+
end
|
565
|
+
end
|
566
|
+
x
|
567
|
+
end
|
568
|
+
|
569
|
+
def initialize(strict)
|
570
|
+
self.strict = strict
|
571
|
+
self.errors = []
|
572
|
+
end
|
573
|
+
|
574
|
+
def parse(vobject)
|
575
|
+
@ctx = Rsec::ParseContext.new self.class.unfold(vobject), "source"
|
576
|
+
ret = vobject_grammar._parse @ctx
|
577
|
+
if !ret || Rsec::INVALID[ret]
|
578
|
+
parse_err(@ctx.generate_error("source"))
|
579
|
+
ret = { VCALENDAR: nil, errors: errors.flatten }
|
580
|
+
end
|
581
|
+
Rsec::Fail.reset
|
582
|
+
ret
|
583
|
+
end
|
584
|
+
|
585
|
+
private
|
586
|
+
|
587
|
+
def parse_err(msg)
|
588
|
+
if strict
|
589
|
+
raise @ctx.report_error msg, "source"
|
590
|
+
else
|
591
|
+
errors << @ctx.report_error(msg, "source")
|
592
|
+
end
|
593
|
+
end
|
594
|
+
end
|
595
|
+
end
|