ruby-vobject 0.1.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +5 -5
  2. data/.hound.yml +3 -0
  3. data/.rubocop.tb.yml +650 -0
  4. data/.rubocop.yml +1077 -0
  5. data/.travis.yml +16 -3
  6. data/Gemfile +1 -1
  7. data/LICENSE.txt +21 -17
  8. data/README.adoc +142 -0
  9. data/Rakefile +1 -1
  10. data/lib/c.rb +173 -0
  11. data/lib/error.rb +19 -0
  12. data/lib/vcalendar.rb +77 -0
  13. data/lib/vcard.rb +67 -0
  14. data/lib/vobject.rb +13 -170
  15. data/lib/vobject/component.rb +87 -36
  16. data/lib/vobject/parameter.rb +116 -0
  17. data/lib/vobject/parametervalue.rb +26 -0
  18. data/lib/vobject/property.rb +134 -55
  19. data/lib/vobject/propertyvalue.rb +46 -0
  20. data/lib/vobject/vcalendar/component.rb +106 -0
  21. data/lib/vobject/vcalendar/grammar.rb +595 -0
  22. data/lib/vobject/vcalendar/paramcheck.rb +259 -0
  23. data/lib/vobject/vcalendar/propertyparent.rb +98 -0
  24. data/lib/vobject/vcalendar/propertyvalue.rb +606 -0
  25. data/lib/vobject/vcalendar/typegrammars.rb +605 -0
  26. data/lib/vobject/vcard/v3_0/component.rb +40 -0
  27. data/lib/vobject/vcard/v3_0/grammar.rb +176 -0
  28. data/lib/vobject/vcard/v3_0/paramcheck.rb +111 -0
  29. data/lib/vobject/vcard/v3_0/parameter.rb +17 -0
  30. data/lib/vobject/vcard/v3_0/property.rb +18 -0
  31. data/lib/vobject/vcard/v3_0/propertyvalue.rb +401 -0
  32. data/lib/vobject/vcard/v3_0/typegrammars.rb +426 -0
  33. data/lib/vobject/vcard/v4_0/component.rb +40 -0
  34. data/lib/vobject/vcard/v4_0/grammar.rb +225 -0
  35. data/lib/vobject/vcard/v4_0/paramcheck.rb +270 -0
  36. data/lib/vobject/vcard/v4_0/parameter.rb +18 -0
  37. data/lib/vobject/vcard/v4_0/property.rb +63 -0
  38. data/lib/vobject/vcard/v4_0/propertyvalue.rb +404 -0
  39. data/lib/vobject/vcard/v4_0/typegrammars.rb +540 -0
  40. data/lib/vobject/vcard/version.rb +3 -0
  41. data/lib/vobject/version.rb +1 -1
  42. data/ruby-vobject.gemspec +19 -11
  43. metadata +93 -17
  44. data/README.md +0 -94
@@ -0,0 +1,225 @@
1
+ require "rsec"
2
+ require "set"
3
+ require "uri"
4
+ require "date"
5
+ include Rsec::Helpers
6
+ require "vobject/vcard/version"
7
+ require "vobject"
8
+ require "vobject/component"
9
+ require "vobject/vcard/v4_0/paramcheck"
10
+ require "vobject/vcard/v4_0/typegrammars"
11
+ require_relative "../../../c"
12
+ require_relative "../../../error"
13
+
14
+ module Vcard::V4_0
15
+ class Grammar
16
+ attr_accessor :strict, :errors
17
+
18
+ class << self
19
+ def unfold(str)
20
+ str.gsub(/[\n\r]+[ \t]/, "")
21
+ end
22
+ end
23
+
24
+ # RFC 6868
25
+ def rfc6868decode(x)
26
+ x.gsub(/\^n/, "\n").gsub(/\^\^/, "^").gsub(/\^'/, '"')
27
+ end
28
+
29
+ def vobject_grammar
30
+ # properties with value cardinality 1
31
+ @cardinality1 = {}
32
+ @cardinality1[:PARAM] = Set.new [:VALUE]
33
+ @cardinality1[:PROP] = Set.new [:KIND, :N, :BDAY, :ANNIVERSARY, :GENDER, :PRODID, :REV, :UID, :BIRTHPLACE, :DEATHPLACE, :DEATHDATE]
34
+
35
+ group = C::IANATOKEN
36
+ linegroup = group << "."
37
+ beginend = /BEGIN/i.r | /END/i.r
38
+
39
+ # parameters && parameter types
40
+ paramname = /LANGUAGE/i.r | /VALUE/i.r | /PREF/i.r | /ALTID/i.r | /PID/i.r |
41
+ /TYPE/i.r | /MEDIATYPE/i.r | /CALSCALE/i.r | /SORT-AS/i.r |
42
+ /GEO/i.r | /TZ/i.r | /LABEL/i.r | /INDEX/i.r | /LEVEL/i.r
43
+ otherparamname = C::NAME_VCARD ^ paramname
44
+ paramvalue = C::QUOTEDSTRING_VCARD.map { |s| rfc6868decode s } | C::PTEXT_VCARD.map { |s| rfc6868decode(s).upcase }
45
+ # tzidvalue = seq("/".r._?, C::PTEXT_VCARD).map { |_, val| val }
46
+ calscalevalue = /GREGORIAN/i.r | C::IANATOKEN | C::XNAME_VCARD
47
+ prefvalue = /[0-9]{1,2}/i.r | "100".r
48
+ pidvalue = /[0-9]+(\.[0-9]+)?/.r
49
+ pidvaluelist = seq(pidvalue, ",", lazy { pidvaluelist }) do |a, _, b|
50
+ [a, b].flatten
51
+ end | (pidvalue ^ ",".r).map { |z| [z] }
52
+ typeparamtel1 = /TEXT/i.r | /VOICE/i.r | /FAX/i.r | /CELL/i.r | /VIDEO/i.r |
53
+ /PAGER/i.r | /TEXTPHONE/i.r
54
+ typeparamtel = typeparamtel1 | C::IANATOKEN | C::XNAME_VCARD
55
+ typeparamrelated = /CONTACT/i.r | /ACQUAINTANCE/i.r | /FRIEND/i.r | /MET/i.r |
56
+ /CO-WORKER/i.r | /COLLEAGUE/i.r | /CO-RESIDENT/i.r | /NEIGHBOR/i.r |
57
+ /CHILD/i.r | /PARENT/i.r | /SIBLING/i.r | /SPOUSE/i.r | /KIN/i.r |
58
+ /MUSE/i.r | /CRUSH/i.r | /DATE/i.r | /SWEETHEART/i.r | /ME/i.r |
59
+ /AGENT/i.r | /EMERGENCY/i.r
60
+ typevalue = /WORK/i.r | /HOME/i.r | typeparamtel1 | typeparamrelated | C::IANATOKEN | C::XNAME_VCARD
61
+ typevaluelist = seq(typevalue << ",".r, lazy { typevaluelist }) do |a, b|
62
+ [a.upcase, b].flatten
63
+ end | typevalue.map { |t| [t.upcase] }
64
+ typeparamtel1list = seq(typeparamtel << ",".r, lazy { typeparamtel1list }) do |a, b|
65
+ [a.upcase, b].flatten
66
+ end | typeparamtel.map { |t| [t.upcase] }
67
+ geourlvalue = seq('"'.r >> C::TEXT4 << '"'.r) do |s|
68
+ parse_err("geo value not a URI") unless s =~ URI::DEFAULT_PARSER.make_regexp
69
+ s
70
+ end
71
+ tzvalue = paramvalue | geourlvalue
72
+ valuetype = /TEXT/i.r | /URI/i.r | /TIMESTAMP/i.r | /TIME/i.r | /DATE-TIME/i.r | /DATE/i.r |
73
+ /DATE-AND-OR-TIME/i.r | /BOOLEAN/i.r | /INTEGER/i.r | /FLOAT/i.r | /UTC-OFFSET/i.r |
74
+ /LANGUAGE-TAG/i.r | C::IANATOKEN | C::XNAME_VCARD
75
+ mediaattr = /[!\"#$%&'*+.^A-Z0-9a-z_`i{}|~-]+/.r
76
+ mediavalue = mediaattr | C::QUOTEDSTRING_VCARD
77
+ mediatail = seq(";".r >> mediaattr << "=".r, mediavalue).map do |a, v|
78
+ ";#{a}=#{v}"
79
+ end
80
+ rfc4288regname = /[A-Za-z0-9!#$&.+^+-]{1,127}/.r
81
+ rfc4288typename = rfc4288regname
82
+ rfc4288subtypename = rfc4288regname
83
+ mediavalue = seq(rfc4288typename << "/".r, rfc4288subtypename, mediatail.star).map do |t, s, tail|
84
+ ret = "#{t}/#{s}"
85
+ ret = ret . tail[0] unless tail.empty?
86
+ ret
87
+ end
88
+ pvalue_list = (seq(paramvalue << ",".r, lazy { pvalue_list }) & /[;:]/.r).map do |e, list|
89
+ [e.sub(Regexp.new("^\"(.+)\"$"), '\1').gsub(/\\n/, "\n"), list].flatten
90
+ end | (paramvalue & /[;:]/.r).map do |e|
91
+ [e.sub(Regexp.new("^\"(.+)\"$"), '\1').gsub(/\\n/, "\n")]
92
+ end
93
+ quoted_string_list = (seq(C::QUOTEDSTRING_VCARD << ",".r, lazy { quoted_string_list }) & /[;:]/.r).map do |e, list|
94
+ [e.sub(Regexp.new("^\"(.+)\"$"), '\1').gsub(/\\n/, "\n"), list].flatten
95
+ end | (C::QUOTEDSTRING_VCARD & /[;:]/.r).map do |e|
96
+ [e.sub(Regexp.new("^\"(.+)\"$"), '\1').gsub(/\\n/, "\n")]
97
+ end
98
+
99
+ # fmttypevalue = seq(rfc4288typename, "/", rfc4288subtypename).map(&:join)
100
+ levelvalue = /beginner/i.r | /average/i.r | /expert/i.r | /high/i.r | /medium/i.r | /low/i.r
101
+
102
+ param = seq(/ALTID/i.r, "=", paramvalue) do |name, _, val|
103
+ { name.upcase.tr("-", "_").to_sym => val }
104
+ end | seq(/LANGUAGE/i.r, "=", C::RFC5646LANGVALUE) do |name, _, val|
105
+ { name.upcase.tr("-", "_").to_sym => val.upcase }
106
+ end | seq(/PREF/i.r, "=", prefvalue) do |name, _, val|
107
+ { name.upcase.tr("-", "_").to_sym => val.upcase }
108
+ end | seq(/TYPE/i.r, "=", "\"".r >> typevaluelist << "\"".r) do |name, _, val|
109
+ # not in spec but in examples. Errata ID 3488, "Held for Document Update": acknwoledged as error requiring an updated spec. With this included, TYPE="x,y,z" is a list of values; the proper ABNF behaviour is that "x,y,z" is interpreted as a single value
110
+ { name.upcase.tr("-", "_").to_sym => val }
111
+ end | seq(/TYPE/i.r, "=", typevaluelist) do |name, _, val|
112
+ { name.upcase.tr("-", "_").to_sym => val }
113
+ end | seq(/MEDIATYPE/i.r, "=", mediavalue) do |name, _, val|
114
+ { name.upcase.tr("-", "_").to_sym => val }
115
+ end | seq(/CALSCALE/i.r, "=", calscalevalue) do |name, _, val|
116
+ { name.upcase.tr("-", "_").to_sym => val }
117
+ end | seq(/SORT-AS/i.r, "=", pvalue_list) do |name, _, val|
118
+ { name.upcase.tr("-", "_").to_sym => val }
119
+ end | seq(/TZ/i.r, "=", tzvalue) do |name, _, val|
120
+ { name.upcase.tr("-", "_").to_sym => val }
121
+ end | seq(/GEO/i.r, "=", geourlvalue) do |name, _, val|
122
+ { name.upcase.tr("-", "_").to_sym => val }
123
+ end | seq(/VALUE/i.r, "=", valuetype) do |name, _, val|
124
+ { name.upcase.tr("-", "_").to_sym => val }
125
+ end | seq(/PID/i.r, "=", pidvaluelist) do |name, _, val|
126
+ { name.upcase.tr("-", "_").to_sym => val }
127
+ end | seq(/INDEX/i.r, "=", prim(:int32)) do |name, _, val|
128
+ { name.upcase.tr("-", "_").to_sym => val }
129
+ end | seq(/LEVEL/i.r, "=", levelvalue) do |name, _, val|
130
+ { name.upcase.tr("-", "_").to_sym => val.upcase }
131
+ end | seq(otherparamname, "=", pvalue_list) do |name, _, val|
132
+ val = val[0] if val.length == 1
133
+ { name.upcase.tr("-", "_").to_sym => val }
134
+ end | seq(paramname, "=", pvalue_list) do |name, _, val|
135
+ parse_err("Violated format of parameter value #{name} = #{val}")
136
+ end
137
+
138
+ params = seq(";".r >> param, lazy { params }) do |p, ps|
139
+ p.merge(ps) do |key, old, new|
140
+ if @cardinality1[:PARAM].include?(key)
141
+ parse_err("Violated cardinality of parameter #{key}")
142
+ end
143
+ [old, new].flatten
144
+ # deal with duplicate properties
145
+ end
146
+ end | seq(";".r >> param ^ ";".r).map { |e| e[0] }
147
+
148
+ contentline = seq(linegroup._?, C::NAME_VCARD, params._? << ":".r,
149
+ C::VALUE, /[\r\n]/) do |l, name, p, value, _|
150
+ key = name.upcase.tr("-", "_").to_sym
151
+ hash = { key => {} }
152
+ errors << Paramcheck.paramcheck(strict, key, p.empty? ? {} : p[0], @ctx)
153
+ hash[key][:value], errors1 = Typegrammars.typematch(strict, key, p[0], :GENERIC, value)
154
+ errors << errors1
155
+ hash[key][:group] = l[0] unless l.empty?
156
+ hash[key][:params] = p[0] unless p.empty?
157
+ hash
158
+ end
159
+ props = seq(contentline, lazy { props }) do |c, rest|
160
+ c.merge(rest) do |key, old, new|
161
+ if @cardinality1[:PROP].include?(key.upcase) &&
162
+ !(new.is_a?(Array) &&
163
+ new[0].key?(:params) && new[0][:params].key?(:ALTID) &&
164
+ old.key?(:params) && old[:params].key?(:ALTID) &&
165
+ old[:params][:ALTID] == new[0][:params][:ALTID]) &&
166
+ !(new.is_a?(Hash) &&
167
+ old.key?(:params) && old[:params].key?(:ALTID) &&
168
+ new.key?(:params) && new[:params].key?(:ALTID) &&
169
+ old[:params][:ALTID] == new[:params][:ALTID])
170
+ parse_err("Violated cardinality of property #{key}")
171
+ end
172
+ [old, new].flatten
173
+ # deal with duplicate properties
174
+ end
175
+ end | ("".r & beginend).map { {} }
176
+
177
+ calpropname = /VERSION/i.r
178
+ calprop = seq(calpropname << ":".r, C::VALUE, /[\r\n]/.r) do |key, value|
179
+ key = key.upcase.tr("-", "_").to_sym
180
+ hash = { key => {} }
181
+ hash[key][:value], errors1 = Typegrammars.typematch(strict, key, nil, :VCARD, value)
182
+ errors << errors1
183
+ hash
184
+ end
185
+ vobject = seq(/BEGIN:VCARD[\r\n]/i.r >> calprop, props << /END:VCARD[\r\n]/i.r) do |v, rest|
186
+ parse_err("Missing VERSION attribute") unless v.has_key?(:VERSION)
187
+ parse_err("Missing FN attribute") unless rest.has_key?(:FN)
188
+ rest.delete(:END)
189
+ { VCARD: v.merge(rest), errors: errors.flatten }
190
+ end
191
+ vobject.eof
192
+ end
193
+
194
+ def initialize(strict)
195
+ self.strict = strict
196
+ self.errors = []
197
+ end
198
+
199
+ def parse(vobject)
200
+ @ctx = Rsec::ParseContext.new self.class.unfold(vobject), "source"
201
+ ret = vobject_grammar._parse @ctx
202
+ if !ret || Rsec::INVALID[ret]
203
+ if strict
204
+ raise @ctx.generate_error "source"
205
+ else
206
+ errors << @ctx.generate_error("source")
207
+ ret = { VCARD: nil, errors: errors.flatten }
208
+ end
209
+
210
+ end
211
+ Rsec::Fail.reset
212
+ ret
213
+ end
214
+
215
+ private
216
+
217
+ def parse_err(msg)
218
+ if strict
219
+ raise @ctx.report_error msg, "source"
220
+ else
221
+ errors << @ctx.report_error(msg, "source")
222
+ end
223
+ end
224
+ end
225
+ end
@@ -0,0 +1,270 @@
1
+ require "set"
2
+ require "uri"
3
+ require "date"
4
+ include Rsec::Helpers
5
+ require "vobject/vcard/version"
6
+ require "vobject"
7
+
8
+ module Vcard::V4_0
9
+ class Paramcheck
10
+ class << self
11
+ def paramcheck(strict, prop, params, ctx)
12
+ errors = []
13
+ if params && params[:TYPE]
14
+ case prop
15
+ when :FN, :NICKNAME, :PHOTO, :ADR, :TEL, :EMAIL, :IMPP, :LANG, :TZ,
16
+ :GEO, :TITLE, :ROLE, :LOGO, :ORG, :RELATED, :CATEGORIES, :NOTE,
17
+ :SOUND, :URL, :KEY, :FBURL, :CALADRURI, :CALURI, :EXPERTISE,
18
+ :HOBBY, :INTEREST, :ORG_DIRECTORY
19
+ # no-op
20
+ when /^x/i
21
+ # no-op
22
+ else
23
+ parse_err(strict, errors, ":TYPE parameter given for #{prop}", ctx)
24
+ end
25
+ end
26
+ if params && params[:MEDIATYPE]
27
+ case prop
28
+ when :SOURCE, :PHOTO, :IMPP, :GEO, :LOGO, :MEMBER, :SOUND, :URL,
29
+ :FBURL, :CALADRURI, :CALURI, :UID, :TZ
30
+ # no-op
31
+ when :TEL, :KEY
32
+ if params[:VALUE] == "uri"
33
+ else
34
+ parse_err(strict, errors, ":MEDIATYPE parameter given for #{prop} with :VALUE of text", ctx)
35
+ end
36
+ when :RELATED
37
+ if params[:VALUE] == "text"
38
+ parse_err(strict, errors, ":MEDIATYPE parameter given for #{prop} with :VALUE of text", ctx)
39
+ end
40
+ when /^x/i
41
+ else
42
+ parse_err(strict, errors, ":MEDIATYPE parameter given for #{prop}", ctx)
43
+ end
44
+ end
45
+ if params && params[:CALSCALE]
46
+ case prop
47
+ when :BDAY, :ANNIVERSARY
48
+ # no-op
49
+ when :DEATHDATE
50
+ if params[:VALUE] == "text"
51
+ parse_err(strict, errors, ":CALSCALE parameter given for #{prop} with :VALUE of text", ctx)
52
+ end
53
+ when /^x/i
54
+ # no-op
55
+ else
56
+ parse_err(strict, errors, ":CALSCALE parameter given for #{prop}", ctx)
57
+ end
58
+ end
59
+ if params && params[:GEO]
60
+ case prop
61
+ when :ADR
62
+ # no-op
63
+ when /^x/i
64
+ # no-op
65
+ else
66
+ parse_err(strict, errors, ":GEO parameter given for #{prop}", ctx)
67
+ end
68
+ end
69
+ if params && params[:TZ]
70
+ case prop
71
+ when :ADR
72
+ # no-op
73
+ when /^x/i
74
+ # no-op
75
+ else
76
+ parse_err(strict, errors, ":TZ parameter given for #{prop}", ctx)
77
+ end
78
+ end
79
+ if params && params[:LANGUAGE]
80
+ case prop
81
+ when :FN, :N, :NICKNAME, :ADR, :TITLE, :ROLE, :LOGO, :ORG, :NOTE,
82
+ :SOUND, :BIRTHPLACE, :DEATHPLACE, :EXPERTISE, :HOBBY, :INTEREST, :ORG_DIRECTORY
83
+ # no-op
84
+ when :BDAY, :ANNIVERSARY, :DEATHDATE
85
+ # added :ANNIVERSARY per errata
86
+ if params[:VALUE] == "text"
87
+ else
88
+ parse_err(strict, errors, ":LANGUAGE parameter given for #{prop} with :VALUE of date/time", ctx)
89
+ end
90
+ when :RELATED
91
+ if params[:VALUE] == "text"
92
+ else
93
+ parse_err(strict, errors, ":LANGUAGE parameter given for #{prop} with :VALUE of uri", ctx)
94
+ end
95
+ when /^x/i
96
+ # no-op
97
+ else
98
+ parse_err(strict, errors, ":LANGUAGE parameter given for #{prop}", ctx)
99
+ end
100
+ end
101
+ if params && params[:VALUE]
102
+ case prop
103
+ when :SOURCE, :KIND, :XML, :FN, :N, :NICKNAME, :PHOTO, :GENDER, :ADR,
104
+ :TEL, :EMAIL, :IMPP, :LANG, :TZ, :GEO, :TITLE, :ROLE, :LOGO, :ORG,
105
+ :MEMBER, :RELATED, :CATEGORIES, :NOTE, :PRODID, :REV, :SOUND, :URL, :VERSION,
106
+ :KEY, :FBURL, :CALADRURI, :CALURI, :BDAY, :ANNIVERSARY, :BIRTHPLACE,
107
+ :DEATHPLACE, :DEATHDATE
108
+ # no-op
109
+ when /^x/i
110
+ # no-op
111
+ else
112
+ parse_err(strict, errors, ":VALUE parameter given for #{prop}", ctx)
113
+ end
114
+ end
115
+ if params && params[:PREF]
116
+ case prop
117
+ when :SOURCE, :FN, :NICKNAME, :PHOTO, :ADR, :TEL, :EMAIL, :IMPP, :LANG,
118
+ :TZ, :GEO, :TITLE, :ROLE, :LOGO, :ORG, :MEMBER, :RELATED, :CATEGORIES,
119
+ :NOTE, :SOUND, :URL, :KEY, :FBURL, :CALADRURI, :CALURI, :EXPERTISE,
120
+ :HOBBY, :INTEREST, :ORG_DIRECTORY, :ORG_DIRECTORY
121
+ # no-op
122
+ when /^x/i
123
+ # no-op
124
+ else
125
+ parse_err(strict, errors, ":PREF parameter given for #{prop}", ctx)
126
+ end
127
+ end
128
+ if params && params[:PID]
129
+ case prop
130
+ when :SOURCE, :FN, :NICKNAME, :PHOTO, :ADR, :TEL, :EMAIL, :IMPP, :LANG,
131
+ :TZ, :GEO, :TITLE, :ROLE, :LOGO, :ORG, :MEMBER, :RELATED, :CATEGORIES,
132
+ :NOTE, :SOUND, :URL, :KEY, :FBURL, :CALADRURI, :CALURI, :ORG_DIRECTORY
133
+ # no-op
134
+ when /^x/i
135
+ # no-op
136
+ else
137
+ parse_err(strict, errors, ":PID parameter given for #{prop}", ctx)
138
+ end
139
+ end
140
+ if params && params[:SORT_AS]
141
+ case prop
142
+ when :N, :ORG
143
+ # no-op
144
+ when /^x/i
145
+ # no-op
146
+ else
147
+ parse_err(strict, errors, ":SORT_AS parameter given for #{prop}", ctx)
148
+ end
149
+ end
150
+ if params && params[:ALTID]
151
+ case prop
152
+ when :SOURCE, :XML, :FN, :N, :NICKNAME, :PHOTO, :BDAY, :ANNIVERSARY, :ADR,
153
+ :TEL, :EMAIL, :IMPP, :LANG, :TZ, :GEO, :TITLE, :ROLE, :LOGO, :ORG, :MEMBER,
154
+ :RELATED, :CATEGORIES, :NOTE, :SOUND, :URL, :KEY, :FBURL, :CALADRURI, :CALURI,
155
+ :BIRTHPLACE, :DEATHPLACE, :DEATHDATE, :EXPERTISE, :HOBBY, :INTEREST, :ORG_DIRECTORY
156
+ # no-op
157
+ when /^x/i
158
+ # no-op
159
+ else
160
+ parse_err(strict, errors, ":SOURCE parameter given for #{prop}", ctx)
161
+ end
162
+ end
163
+ if params && params[:LABEL]
164
+ case prop
165
+ when :ADR
166
+ # no-op
167
+ when /^x/i
168
+ # no-op
169
+ else
170
+ parse_err(strict, errors, ":LABEL parameter given for #{prop}", ctx)
171
+ end
172
+ end
173
+ if params && params[:LEVEL]
174
+ case prop
175
+ when :EXPERTISE, :HOBBY, :INTEREST
176
+ # no-op
177
+ when /^x/i
178
+ # no-op
179
+ else
180
+ parse_err(strict, errors, ":LEVEL parameter given for #{prop}", ctx)
181
+ end
182
+ end
183
+ if params && params[:INDEX]
184
+ case prop
185
+ when :EXPERTISE, :HOBBY, :INTEREST, :ORG_DIRECTORY
186
+ # no-op
187
+ when /^x/i
188
+ # no-op
189
+ else
190
+ parse_err(strict, errors, ":INDEX parameter given for #{prop}", ctx)
191
+ end
192
+ end
193
+ params.each do |p|
194
+ case p
195
+ when :LANGUAGE, :VALUE, :PREF, :PID, :TYPE, :GEO, :TZ, :SORT_AS, :CALSCALE,
196
+ :LABEL, :ALTID
197
+ # no-op
198
+ when /^x/i
199
+ # xname parameters are always allowed
200
+ else
201
+ # any-param
202
+ case prop
203
+ when :SOURCE, :KIND, :FN, :N, :NICKNAME, :PHOTO, :BDAY, :ANNIVERSARY, :GENDER,
204
+ :ADR, :TEL, :EMAIL, :IMPP, :LANG, :TZ, :GEO, :TITLE, :ROLE, :LOGO, :ORG, :MEMBER,
205
+ :RELATED, :CATEGORIES, :NOTE, :PRODID, :REV, :SOUND, :UID, :CLIENTPIDMAP, :URL,
206
+ :VERSION, :KEY, :FBURL, :CALADRURI, :CALURI, :BIRTHPLACE, :DEATHPLACE, :DEATHDATE,
207
+ :EXPERTISE, :HOBBY, :INTEREST, :ORG_DIRECTORY
208
+ # no-op
209
+ when /^x/i
210
+ # no-op
211
+ else
212
+ parse_err(strict, errors, "#{p} parameter given for #{prop}", ctx)
213
+ end
214
+ end
215
+ end
216
+ case prop
217
+ when :SOURCE, :PHOTO, :IMPP, :GEO, :LOGO, :MEMBER, :SOUND, :URL, :FBURL,
218
+ :CALADRURI, :CALURI
219
+ params.each do |key, val|
220
+ parse_err(strict, errors, "illegal value #{val} given for parameter #{key} of #{prop}", ctx) if key == :VALUE && val != "uri"
221
+ end
222
+ when :LANG
223
+ params.each do |key, val|
224
+ parse_err(strict, errors, "illegal value #{val} given for parameter #{key} of #{prop}", ctx) if key == :VALUE && val != "language-tag"
225
+ end
226
+ when :REV
227
+ params.each do |key, val|
228
+ parse_err(strict, errors, "illegal value #{val} given for parameter #{key} of #{prop}", ctx) if key == :VALUE && val != "timestamp"
229
+ end
230
+ when :KIND, :XML, :FN, :N, :NICKNAME, :GENDER, :ADR, :EMAIL, :TITLE, :ROLE, :ORG,
231
+ :CATEGORIES, :NOTE, :PRODID, :VERSION
232
+ params.each do |key, val|
233
+ parse_err(strict, errors, "illegal value #{val} given for parameter #{key} of #{prop}", ctx) if key == :VALUE && val != "text"
234
+ end
235
+ when :BDAY, :ANNIVERSARY, :DEATHDATE
236
+ params.each do |key, val|
237
+ parse_err(strict, errors, "illegal value #{val} given for parameter #{key} of #{prop}", ctx) if key == :VALUE && val != "date-and-or-time" && val != "text"
238
+ end
239
+ when :TEL, :RELATED, :UID, :KEY, :BIRTHPLACE, :DEATHPLACE
240
+ params.each do |key, val|
241
+ parse_err(strict, errors, "illegal value #{val} given for parameter #{key} of #{prop}", ctx) if key == :VALUE && val != "uri" && val != "text"
242
+ end
243
+ when :TZ
244
+ params.each do |key, val|
245
+ parse_err(strict, errors, "illegal value #{val} given for parameter #{key} of #{prop}", ctx) if key == :VALUE && val != "uri" && val != "text" && val != "utc-offset"
246
+ end
247
+ when :EXPERTISE
248
+ if params && params[:LEVEL]
249
+ parse_err(strict, errors, "illegal value #{params[:LEVEL]} given for parameter :LEVEL of #{prop}", ctx) unless params[:LEVEL] =~ /^(beginner|average|expert)$/i
250
+ end
251
+ when :HOBBY, :INTEREST
252
+ if params && params[:LEVEL]
253
+ parse_err(strict, errors, "illegal value #{params[:LEVEL]} given for parameter :LEVEL of #{prop}", ctx) unless params[:LEVEL] =~ /^(high|medium|low)$/i
254
+ end
255
+ end
256
+ errors
257
+ end
258
+
259
+ private
260
+
261
+ def parse_err(strict, errors, msg, ctx)
262
+ if strict
263
+ raise ctx.report_error msg, "source"
264
+ else
265
+ errors << ctx.report_error(msg, "source")
266
+ end
267
+ end
268
+ end
269
+ end
270
+ end