vobject 0.1.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|