vobject 0.1.0 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/macos.yml +38 -0
  3. data/.github/workflows/ubuntu.yml +56 -0
  4. data/.github/workflows/windows.yml +40 -0
  5. data/.hound.yml +3 -0
  6. data/.rubocop.tb.yml +650 -0
  7. data/.rubocop.yml +1077 -0
  8. data/Gemfile +1 -1
  9. data/LICENSE.txt +21 -17
  10. data/README.adoc +151 -0
  11. data/Rakefile +1 -1
  12. data/lib/c.rb +173 -0
  13. data/lib/error.rb +19 -0
  14. data/lib/vcalendar.rb +77 -0
  15. data/lib/vcard.rb +67 -0
  16. data/lib/vobject.rb +13 -170
  17. data/lib/vobject/component.rb +87 -36
  18. data/lib/vobject/parameter.rb +116 -0
  19. data/lib/vobject/parametervalue.rb +26 -0
  20. data/lib/vobject/property.rb +134 -55
  21. data/lib/vobject/propertyvalue.rb +46 -0
  22. data/lib/vobject/vcalendar/component.rb +106 -0
  23. data/lib/vobject/vcalendar/grammar.rb +595 -0
  24. data/lib/vobject/vcalendar/paramcheck.rb +259 -0
  25. data/lib/vobject/vcalendar/propertyparent.rb +98 -0
  26. data/lib/vobject/vcalendar/propertyvalue.rb +606 -0
  27. data/lib/vobject/vcalendar/typegrammars.rb +605 -0
  28. data/lib/vobject/vcard/v3_0/component.rb +40 -0
  29. data/lib/vobject/vcard/v3_0/grammar.rb +175 -0
  30. data/lib/vobject/vcard/v3_0/paramcheck.rb +110 -0
  31. data/lib/vobject/vcard/v3_0/parameter.rb +17 -0
  32. data/lib/vobject/vcard/v3_0/property.rb +18 -0
  33. data/lib/vobject/vcard/v3_0/propertyvalue.rb +401 -0
  34. data/lib/vobject/vcard/v3_0/typegrammars.rb +425 -0
  35. data/lib/vobject/vcard/v4_0/component.rb +40 -0
  36. data/lib/vobject/vcard/v4_0/grammar.rb +224 -0
  37. data/lib/vobject/vcard/v4_0/paramcheck.rb +269 -0
  38. data/lib/vobject/vcard/v4_0/parameter.rb +18 -0
  39. data/lib/vobject/vcard/v4_0/property.rb +63 -0
  40. data/lib/vobject/vcard/v4_0/propertyvalue.rb +404 -0
  41. data/lib/vobject/vcard/v4_0/typegrammars.rb +539 -0
  42. data/lib/vobject/version.rb +1 -1
  43. data/vobject.gemspec +19 -16
  44. metadata +81 -26
  45. data/.travis.yml +0 -5
  46. data/README.md +0 -94
@@ -0,0 +1,40 @@
1
+ require "vobject/component"
2
+ require "vobject/vcard/v3_0/property"
3
+ require "vobject/vcard/v3_0/grammar"
4
+ require "pp"
5
+
6
+ module Vcard::V3_0
7
+ class Component < Vobject::Component
8
+ class << self
9
+ def parse(vcf, strict)
10
+ hash = Vcard::V3_0::Grammar.new(strict).parse(vcf)
11
+ comp_name = hash.keys.first
12
+ new comp_name, hash[comp_name], hash[:errors]
13
+ end
14
+
15
+ private
16
+
17
+ def raise_invalid_parsing
18
+ raise "vCard parse failed"
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def property_base_class
25
+ version_class.const_get(:Property)
26
+ end
27
+
28
+ def component_base_class
29
+ version_class.const_get(:Component)
30
+ end
31
+
32
+ def parameter_base_class
33
+ version_class.const_get(:Parameter)
34
+ end
35
+
36
+ def version_class
37
+ Vcard::V3_0
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,175 @@
1
+ require "rsec"
2
+ require "set"
3
+ require "uri"
4
+ require "date"
5
+ include Rsec::Helpers
6
+ require "vobject"
7
+ require "vobject/component"
8
+ require "vobject/vcard/v3_0/paramcheck"
9
+ require "vobject/vcard/v3_0/typegrammars"
10
+ require_relative "../../../c"
11
+ require_relative "../../../error"
12
+
13
+ module Vcard::V3_0
14
+ class Grammar
15
+ attr_accessor :strict, :errors
16
+ class << self
17
+ def unfold(str)
18
+ str.gsub(/[\n\r]+[ \t]/, "")
19
+ end
20
+ end
21
+
22
+ def vobject_grammar
23
+ # properties with value cardinality 1
24
+ @cardinality1 = {}
25
+ @cardinality1[:PARAM] = Set.new [:VALUE]
26
+ @cardinality1[:PROP] = Set.new [:KIND, :N, :BDAY, :ANNIVERSARY, :GENDER, :PRODID, :REV, :UID]
27
+
28
+ group = C::IANATOKEN
29
+ linegroup = group << "."
30
+ beginend = /BEGIN/i.r | /END/i.r
31
+
32
+ # parameters && parameter types
33
+ paramname = /ENCODING/i.r | /LANGUAGE/i.r | /CONTEXT/i.r | /TYPE/i.r | /VALUE/i.r | /PREF/i.r
34
+ otherparamname = C::NAME_VCARD ^ paramname
35
+ paramvalue = C::QUOTEDSTRING_VCARD.map { |s| s } | C::PTEXT_VCARD.map(&:upcase)
36
+
37
+ # prefvalue = /[0-9]{1,2}/i.r | "100".r
38
+ valuetype = /URI/i.r | /DATE/i.r | /DATE-TIME/i.r | /BINARY/i.r | /PTEXT/i.r
39
+ # mediaattr = /[!\"#$%&'*+.^A-Z0-9a-z_`i{}|~-]+/.r
40
+ # mediavalue1 = mediaattr | C::QUOTEDSTRING_VCARD
41
+ # mediatail = seq(";".r >> mediaattr, "=".r << mediavalue1).map do |a, v|
42
+ # ";#{a}=#{v}"
43
+ # end
44
+ # rfc4288regname = /[A-Za-z0-9!#$&.+^+-]{1,127}/.r
45
+ # rfc4288typename = rfc4288regname
46
+ # rfc4288subtypename = rfc4288regname
47
+ # mediavalue = seq(rfc4288typename << "/".r, rfc4288subtypename, # mediatail.star).map do |t, s, tail|
48
+ # ret = "#{t}/#{s}"
49
+ # ret = ret . tail[0] unless tail.empty?
50
+ # ret
51
+ # end
52
+ pvalue_list = (seq(paramvalue << ",".r, lazy { pvalue_list }) & /[;:]/.r).map do |e, list|
53
+ [e.sub(Regexp.new("^\"(.+)\"$"), '\1').gsub(/\\n/, "\n"), list].flatten
54
+ end | (paramvalue & /[;:]/.r).map do |e|
55
+ [e.sub(Regexp.new("^\"(.+)\"$"), '\1').gsub(/\\n/, "\n")]
56
+ end
57
+ typevaluelist = seq(C::IANATOKEN, ",".r >> lazy { typevaluelist }).map do |t, l|
58
+ [t.upcase, l].flatten
59
+ end | C::IANATOKEN.map { |t| [t.upcase] }
60
+ quoted_string_list = (seq(C::QUOTEDSTRING_VCARD << ",".r, lazy { quoted_string_list }) & /[;:]/.r).map do |e, list|
61
+ [e.sub(Regexp.new("^\"(.+)\"$"), '\1').gsub(/\\n/, "\n"), list].flatten
62
+ end | (C::QUOTEDSTRING_VCARD & /[;:]/.r).map do |e|
63
+ [e.sub(Regexp.new("^\"(.+)\"$"), '\1').gsub(/\\n/, "\n")]
64
+ end
65
+
66
+ # fmttypevalue = seq(rfc4288typename, "/", rfc4288subtypename).map(&:join)
67
+ rfc1766primarytag = /[A-Za-z]{1,8}/.r
68
+ rfc1766subtag = seq("-", /[A-Za-z]{1,8}/.r) { |a, b| a + b }
69
+ rfc1766language = seq(rfc1766primarytag, rfc1766subtag.star) do |a, b|
70
+ a += b[0] unless b.empty?
71
+ a
72
+ end
73
+
74
+ param = seq(/ENCODING/i.r, "=", /b/.r) do |name, _, val|
75
+ { name.upcase.tr("-", "_").to_sym => val }
76
+ end | seq(/LANGUAGE/i.r, "=", rfc1766language) do |name, _, val|
77
+ { name.upcase.tr("-", "_").to_sym => val.upcase }
78
+ end | seq(/CONTEXT/i.r, "=", /word/.r) do |name, _, val|
79
+ { name.upcase.tr("-", "_").to_sym => val.upcase }
80
+ end | seq(/TYPE/i.r, "=", typevaluelist) do |name, _, val|
81
+ { name.upcase.tr("-", "_").to_sym => val }
82
+ end | seq(/VALUE/i.r, "=", valuetype) do |name, _, val|
83
+ { name.upcase.tr("-", "_").to_sym => val }
84
+ end | /PREF/i.r.map do |_name|
85
+ # this is likely erroneous use of VCARD 2.1 convention in RFC2739; converting to canonical TYPE=PREF
86
+ { TYPE: ["PREF"] }
87
+ end | seq(otherparamname, "=", pvalue_list) do |name, _, val|
88
+ val = val[0] if val.length == 1
89
+ { name.upcase.tr("-", "_").to_sym => val }
90
+ end | seq(paramname, "=", pvalue_list) do |name, _, val|
91
+ parse_err("Violated format of parameter value #{name} = #{val}")
92
+ end
93
+
94
+ params = seq(";".r >> param & ";", lazy { params }) do |p, ps|
95
+ p.merge(ps) do |key, old, new|
96
+ if @cardinality1[:PARAM].include?(key)
97
+ parse_err("Violated cardinality of parameter #{key}")
98
+ end
99
+ [old, new].flatten
100
+ # deal with duplicate properties
101
+ end
102
+ end | seq(";".r >> param).map { |e| e[0] }
103
+
104
+ contentline = seq(linegroup._?, C::NAME_VCARD, params._? << ":".r,
105
+ C::VALUE, /(\r|\n|\r\n)/) do |g, name, p, value, _|
106
+ key = name.upcase.tr("-", "_").to_sym
107
+ hash = { key => {} }
108
+ hash[key][:value], errors1 = Typegrammars.typematch(strict, key, p[0], :GENERIC, value, @ctx)
109
+ errors << errors1
110
+ hash[key][:group] = g[0] unless g.empty?
111
+ errors << Paramcheck.paramcheck(strict, key, p.empty? ? {} : p[0], @ctx)
112
+ hash[key][:params] = p[0] unless p.empty?
113
+ hash
114
+ end
115
+ props = seq(contentline, lazy { props }) do |c, rest|
116
+ c.merge(rest) do |key, old, new|
117
+ if @cardinality1[:PROP].include?(key.upcase)
118
+ parse_err("Violated cardinality of property #{key}")
119
+ end
120
+ [old, new].flatten
121
+ # deal with duplicate properties
122
+ end
123
+ end | ("".r & beginend).map { {} }
124
+
125
+ calpropname = /VERSION/i.r
126
+ calprop = seq(linegroup._?, calpropname << ":".r, C::VALUE, /[\r\n]/) do |g, key, value, _|
127
+ key = key.upcase.tr("-", "_").to_sym
128
+ hash = { key => {} }
129
+ hash[key][:value], errors1 = Typegrammars.typematch(strict, key, nil, :VCARD, value, @ctx)
130
+ errors << errors1
131
+ hash[key][:group] = g[0] unless g.empty?
132
+ hash
133
+ end
134
+ vobject = seq(linegroup._?, /BEGIN:VCARD[\r\n]/i.r >> calprop, props, linegroup._? << /END:VCARD[\r\n]/i.r) do |(_g, v, rest, _g1)|
135
+ # TODO what do we do with the groups here?
136
+ parse_err("Missing VERSION attribute") unless v.has_key?(:VERSION)
137
+ parse_err("Missing FN attribute") unless rest.has_key?(:FN)
138
+ parse_err("Missing N attribute") unless rest.has_key?(:N)
139
+ rest.delete(:END)
140
+ { VCARD: v.merge(rest), errors: errors.flatten }
141
+ end
142
+ vobject.eof
143
+ end
144
+
145
+ def initialize(strict)
146
+ self.strict = strict
147
+ self.errors = []
148
+ end
149
+
150
+ def parse(vobject)
151
+ @ctx = Rsec::ParseContext.new self.class.unfold(vobject), "source"
152
+ ret = vobject_grammar._parse @ctx
153
+ if !ret || Rsec::INVALID[ret]
154
+ if strict
155
+ raise @ctx.generate_error "source"
156
+ else
157
+ errors << @ctx.generate_error("source")
158
+ ret = { VCARD: nil, errors: errors.flatten }
159
+ end
160
+ end
161
+ Rsec::Fail.reset
162
+ ret
163
+ end
164
+
165
+ private
166
+
167
+ def parse_err(msg)
168
+ if strict
169
+ raise @ctx.report_error msg, "source"
170
+ else
171
+ errors << @ctx.report_error(msg, "source")
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,110 @@
1
+ require "rsec"
2
+ require "set"
3
+ require "uri"
4
+ require "date"
5
+ include Rsec::Helpers
6
+ require "vobject"
7
+
8
+ module Vcard::V3_0
9
+ class Paramcheck
10
+ class << self
11
+ def paramcheck(strict, prop, params, ctx)
12
+ errors = []
13
+ if params && params[:TYPE]
14
+ parse_err(strict, errors, "multiple values for :TYPE parameter of #{prop}", ctx) if params[:TYPE].is_a?(Array) && params[:TYPE].length > 1 && prop != :EMAIL && prop != :ADR && prop != :TEL && prop != :LABEL && prop != :IMPP
15
+ end
16
+ case prop
17
+ when :NAME, :PROFILE, :GEO, :PRODID, :URL, :VERSION, :CLASS
18
+ parse_err(strict, errors, "illegal parameters #{params} given for #{prop}", ctx) unless params.empty?
19
+ when :CALURI, :CAPURI, :CALADRURI, :FBURL
20
+ params.each do |key, val|
21
+ parse_err(strict, errors, "illegal parameter #{key} given for #{prop}", ctx) unless key == :TYPE
22
+ if params[:TYPE].is_a?(Array)
23
+ val.each do |v|
24
+ parse_err(strict, errors, "illegal parameter value #{v} given for parameter #{key} of #{prop}", ctx) unless v == "PREF"
25
+ end
26
+ else
27
+ parse_err(strict, errors, "illegal parameter value #{val} given for parameter #{key} of #{prop}", ctx) unless val == "PREF"
28
+ end
29
+ end
30
+ when :SOURCE
31
+ params.each do |key, val|
32
+ parse_err(strict, errors, "illegal parameter #{key} given for #{prop}", ctx) unless key == :VALUE || key == :CONTEXT || key =~ /^x/i
33
+ parse_err(strict, errors, "illegal value #{val} given for parameter #{key} of #{prop}", ctx) if key == :VALUE && val != "uri"
34
+ parse_err(strict, errors, "illegal value #{val} given for parameter #{key} of #{prop}", ctx) if key == :CONTEXT && val != "word"
35
+ end
36
+ when :FN, :N, :NICKNAME, :MAILER, :TITLE, :ROLE, :ORG, :CATEGORIES, :NOTE, :SORT_STRING
37
+ params.each do |key, val|
38
+ parse_err(strict, errors, "illegal parameter #{key} given for #{prop}", ctx) unless key == :VALUE || key == :LANGUAGE || key =~ /^x/i
39
+ parse_err(strict, errors, "illegal value #{val} given for parameter #{key} of #{prop}", ctx) if key == :VALUE && val != "ptext"
40
+ end
41
+ when :TEL, :IMPP, :UID
42
+ # UID included here per errata
43
+ params.each_key do |key|
44
+ parse_err(strict, errors, "illegal parameter #{key} given for #{prop}", ctx) unless key == :TYPE
45
+ end
46
+ # we do not check the values of the :TEL :TYPE parameter, because they include ianaToken
47
+ when :EMAIL
48
+ params.each_key do |key|
49
+ parse_err(strict, errors, "illegal parameter #{key} given for #{prop}", ctx) unless key == :TYPE
50
+ end
51
+ # we do not check the values of the first :EMAIL :TYPE parameter, because they include ianaToken
52
+ when :ADR, :LABEL
53
+ params.each do |key, val|
54
+ parse_err(strict, errors, "illegal parameter #{key} given for #{prop}", ctx) unless [:VALUE, :LANGUAGE, :TYPE].include? key || key =~ /^x/i
55
+ parse_err(strict, errors, "illegal value #{val} given for parameter #{key} of #{prop}", ctx) if key == :VALUE && val != "ptext"
56
+ end
57
+ # we do not check the values of the :ADR :TYPE parameter, because they include ianaToken
58
+ when :KEY
59
+ params.each do |key, val|
60
+ # VALUE included here per errata
61
+ parse_err(strict, errors, "illegal parameter #{key} given for #{prop}", ctx) unless [:TYPE, :ENCODING, :VALUE].include? key
62
+ parse_err(strict, errors, "illegal value #{val} given for parameter #{key} of #{prop}", ctx) if key == :VALUE && val != "binary"
63
+ end
64
+ # we do not check the values of the :KEY :TYPE parameter, because they include ianaToken
65
+ when :PHOTO, :LOGO, :SOUND
66
+ params.each_key do |key|
67
+ parse_err(strict, errors, "illegal parameter #{key} given for #{prop}", ctx) unless [:VALUE, :TYPE, :ENCODING].include? key
68
+ end
69
+ parse_err(strict, errors, "illegal value #{params[:VALUE]} of :VALUE given for #{prop}", ctx) if params[:VALUE] && params[:VALUE] != "binary" && params[:VALUE] != "uri"
70
+ parse_err(strict, errors, "illegal value #{params[:ENCODING]} of :ENCODING given for #{prop}", ctx) if params[:ENCODING] && (params[:ENCODING] != "b" || params[:VALUE] == "uri")
71
+ parse_err(strict, errors, "mandatory parameter of :ENCODING missing for #{prop}", ctx) if !params.has_key?(:ENCODING) && (!params.key?(:VALUE) || params[:VALUE] == "binary")
72
+ # TODO restriction of :TYPE to image types registered with IANA
73
+ # TODO restriction of :TYPE to sound types registered with IANA
74
+ when :BDAY, :REV
75
+ params.each_key do |key|
76
+ parse_err(strict, errors, "illegal parameter #{key} given for #{prop}", ctx) unless key == :VALUE
77
+ end
78
+ parse_err(strict, errors, "illegal value #{params[:VALUE]} of :VALUE given for #{prop}", ctx) if params[:VALUE] && params[:VALUE] != "date" && params[:VALUE] != "date-time"
79
+ when :AGENT
80
+ params.each_key do |key|
81
+ parse_err(strict, errors, "illegal parameter #{key} given for #{prop}", ctx) unless key == :VALUE
82
+ end
83
+ parse_err(strict, errors, "illegal value #{params[:VALUE]} of :VALUE given for #{prop}", ctx) if params[:VALUE] && params[:VALUE] != "uri"
84
+ when :TZ
85
+ # example in definition contradicts spec! Spec says :TZ takes no params at all
86
+ params.each_key do |key|
87
+ parse_err(strict, errors, "illegal parameter #{key} given for #{prop}", ctx) unless key == :VALUE
88
+ end
89
+ parse_err(strict, errors, "illegal value #{params[:VALUE]} of :VALUE given for #{prop}", ctx) if params[:VALUE] && params[:VALUE] != "text"
90
+ else
91
+ params.each_key do |key|
92
+ parse_err(strict, errors, "illegal parameter #{key} given for #{prop}", ctx) unless key == :VALUE || key == :LANGUAGE || key =~ /^x/i
93
+ parse_err(strict, errors, "illegal value #{val} given for parameter #{key} of #{prop}") if key == :VALUE && val != "ptext"
94
+ end
95
+ end
96
+ errors
97
+ end
98
+
99
+ private
100
+
101
+ def parse_err(strict, errors, msg, ctx)
102
+ if strict
103
+ raise ctx.report_error msg, "source"
104
+ else
105
+ errors << ctx.report_error(msg, "source")
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,17 @@
1
+ require "vobject/parameter"
2
+
3
+ module Vcard::V3_0
4
+ class Parameter < Vobject::Parameter
5
+ def parameter_base_class
6
+ version_class.const_get(:Parameter)
7
+ end
8
+
9
+ def property_base_class
10
+ version_class.const_get(:Property)
11
+ end
12
+
13
+ def version_class
14
+ Vcard::V3_0
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ require "vobject/property"
2
+
3
+ module Vcard::V3_0
4
+ class Property < Vobject::Property
5
+ end
6
+
7
+ def parameter_base_class
8
+ version_class.const_get(:Parameter)
9
+ end
10
+
11
+ def property_base_class
12
+ version_class.const_get(:Property)
13
+ end
14
+
15
+ def version_class
16
+ Vcard::V3_0
17
+ end
18
+ end
@@ -0,0 +1,401 @@
1
+ require "vobject"
2
+ require "vobject/propertyvalue"
3
+
4
+ module Vcard::V3_0
5
+ module PropertyValue
6
+ class Text < Vobject::PropertyValue
7
+ class << self
8
+ def escape(x)
9
+ # temporarily escape \\ as \u007f, which is banned from text
10
+ x.tr("\\", "\u007f").gsub(/\n/, "\\n").gsub(/,/, "\\,").gsub(/;/, "\\;").gsub(/\u007f/, "\\\\")
11
+ end
12
+
13
+ def listencode(x)
14
+ ret = if x.is_a?(Array)
15
+ x.map { |m| Text.escape m }.join(",")
16
+ elsif x.nil? || x.empty?
17
+ ""
18
+ else
19
+ Text.escape x
20
+ end
21
+ ret
22
+ end
23
+ end
24
+
25
+ def initialize(val)
26
+ self.value = val
27
+ self.type = "text"
28
+ end
29
+
30
+ def to_s
31
+ Text.escape value
32
+ end
33
+
34
+ def to_hash
35
+ value
36
+ end
37
+ end
38
+
39
+ class ClassValue < Text
40
+ def initialize(val)
41
+ self.value = val
42
+ self.type = "classvalue"
43
+ end
44
+
45
+ def to_hash
46
+ value
47
+ end
48
+ end
49
+
50
+ class Profilevalue < Text
51
+ def initialize(val)
52
+ self.value = val
53
+ self.type = "profilevalue"
54
+ end
55
+
56
+ def to_hash
57
+ value
58
+ end
59
+ end
60
+
61
+ class Kindvalue < Text
62
+ def initialize(val)
63
+ self.value = val
64
+ self.type = "kindvalue"
65
+ end
66
+
67
+ def to_hash
68
+ value
69
+ end
70
+ end
71
+
72
+ class Ianatoken < Text
73
+ def initialize(val)
74
+ self.value = val
75
+ self.type = "ianatoken"
76
+ end
77
+
78
+ def to_hash
79
+ value
80
+ end
81
+ end
82
+
83
+ class Binary < Text
84
+ def initialize(val)
85
+ self.value = val
86
+ self.type = "binary"
87
+ end
88
+
89
+ def to_hash
90
+ value
91
+ end
92
+ end
93
+
94
+ class Phonenumber < Text
95
+ def initialize(val)
96
+ self.value = val
97
+ self.type = "phonenumber"
98
+ end
99
+
100
+ def to_hash
101
+ value
102
+ end
103
+ end
104
+
105
+ class Uri < Text
106
+ def initialize(val)
107
+ self.value = val
108
+ self.type = "uri"
109
+ end
110
+
111
+ def to_hash
112
+ value
113
+ end
114
+
115
+ def to_s
116
+ value
117
+ end
118
+ end
119
+
120
+ class Float < Vobject::PropertyValue
121
+ include Comparable
122
+ def <=>(another)
123
+ value <=> another.value
124
+ end
125
+
126
+ def initialize(val)
127
+ self.value = val
128
+ self.type = "float"
129
+ end
130
+
131
+ def to_s
132
+ value
133
+ end
134
+
135
+ def to_hash
136
+ value
137
+ end
138
+ end
139
+
140
+ class Integer < Vobject::PropertyValue
141
+ include Comparable
142
+ def <=>(another)
143
+ value <=> another.value
144
+ end
145
+
146
+ def initialize(val)
147
+ self.value = val
148
+ self.type = "integer"
149
+ end
150
+
151
+ def to_s
152
+ value.to_s
153
+ end
154
+
155
+ def to_hash
156
+ value
157
+ end
158
+ end
159
+
160
+ class Date < Vobject::PropertyValue
161
+ include Comparable
162
+ def <=>(another)
163
+ value <=> another.value
164
+ end
165
+
166
+ def initialize(val)
167
+ self.value = val
168
+ self.type = "date"
169
+ end
170
+
171
+ def to_s
172
+ sprintf("%04d-%02d-%02d", value[:year].to_i, value[:month].to_i, value[:day].to_i)
173
+ end
174
+
175
+ def to_hash
176
+ value
177
+ end
178
+ end
179
+
180
+ class DateTimeLocal < Vobject::PropertyValue
181
+ include Comparable
182
+ def <=>(another)
183
+ value[:time] <=> another.value[:time]
184
+ end
185
+
186
+ def initialize(val)
187
+ self.value = val.clone
188
+ # val consists of :time && :zone values. If :zone is empty, floating local time (i.e. system local time) is assumed
189
+ self.type = "datetimelocal"
190
+ val[:sec] += (val[:secfrac].to_f / (10**val[:secfrac].length)) if !val[:secfrac].nil? && !val[:secfrac].empty?
191
+ value[:time] = if val[:zone].nil? || val[:zone].empty?
192
+ ::Time.local(val[:year], val[:month], val[:day], val[:hour], val[:min], val[:sec])
193
+ else
194
+ ::Time.utc(val[:year], val[:month], val[:day], val[:hour], val[:min], val[:sec])
195
+ end
196
+ value[:origtime] = value[:time]
197
+ if val[:zone] && val[:zone] != "Z"
198
+ offset = val[:zone][:hour].to_i * 3600 + val[:zone][:min].to_i * 60
199
+ offset += val[:zone][:sec].to_i if val[:zone][:sec]
200
+ offset = -offset if val[:sign] == "-"
201
+ value[:time] += offset.to_i
202
+ end
203
+ end
204
+
205
+ def to_s
206
+ # ret = sprintf("%04d-%02d-%02dT%02d:%02d:%02d", value[:year], value[:month], value[:day], value[:hour], value[:min], value[:sec])
207
+ ret = sprintf("%s-%s-%sT%s:%s:%s", value[:year], value[:month], value[:day], value[:hour], value[:min], value[:sec])
208
+ ret = ret + ",#{value[:secfrac]}" if value[:secfrac]
209
+ zone = "Z" if value[:zone] && value[:zone] == "Z"
210
+ zone = "#{value[:zone][:sign]}#{value[:zone][:hour]}:#{value[:zone][:min]}" if value[:zone] && value[:zone].is_a?(Hash)
211
+ ret = ret + zone
212
+ ret
213
+ end
214
+
215
+ def to_hash
216
+ ret = {
217
+ year: value[:year],
218
+ month: value[:month],
219
+ day: value[:day],
220
+ hour: value[:hour],
221
+ min: value[:min],
222
+ sec: value[:sec],
223
+ }
224
+ ret[:zone] = value[:zone] if value[:zone]
225
+ ret
226
+ end
227
+ end
228
+
229
+ class Time < Vobject::PropertyValue
230
+ def initialize(val)
231
+ self.value = val
232
+ self.type = "time"
233
+ end
234
+
235
+ def to_s
236
+ ret = "#{value[:hour]}:#{value[:min]}:#{value[:sec]}"
237
+ ret = ret + ".#{value[:secfrac]}" if value[:secfrac]
238
+ zone = ""
239
+ zone = "Z" if value[:zone] && value[:zone] == "Z"
240
+ zone = "#{value[:zone][:sign]}#{value[:zone][:hour]}:#{value[:zone][:min]}" if value[:zone] && value[:zone].is_a?(Hash)
241
+ ret = ret + zone
242
+ ret
243
+ end
244
+
245
+ def to_hash
246
+ value
247
+ end
248
+ end
249
+
250
+ class Utcoffset < Vobject::PropertyValue
251
+ def initialize(val)
252
+ self.value = val
253
+ self.type = "utcoffset"
254
+ end
255
+
256
+ def to_s
257
+ ret = "#{value[:sign]}#{value[:hour]}:#{value[:min]}"
258
+ # ret += self.value[:sec] if self.value[:sec]
259
+ ret
260
+ end
261
+
262
+ def to_hash
263
+ value
264
+ end
265
+ end
266
+
267
+ class Geovalue < Vobject::PropertyValue
268
+ def initialize(val)
269
+ self.value = val
270
+ self.type = "geovalue"
271
+ end
272
+
273
+ def to_s
274
+ ret = "#{value[:lat]};#{value[:long]}"
275
+ ret
276
+ end
277
+
278
+ def to_hash
279
+ value
280
+ end
281
+ end
282
+
283
+ class Version < Vobject::PropertyValue
284
+ def initialize(val)
285
+ self.value = val
286
+ self.type = "version"
287
+ end
288
+
289
+ def to_s
290
+ value
291
+ end
292
+
293
+ def to_hash
294
+ value
295
+ end
296
+ end
297
+
298
+ class Org < Vobject::PropertyValue
299
+ def initialize(val)
300
+ self.value = val
301
+ self.type = "org"
302
+ end
303
+
304
+ def to_s
305
+ value.map { |m| Text.escape m }.join(";")
306
+ end
307
+
308
+ def to_hash
309
+ value
310
+ end
311
+ end
312
+
313
+ class Fivepartname < Vobject::PropertyValue
314
+ def initialize(val)
315
+ self.value = val
316
+ self.type = "fivepartname"
317
+ end
318
+
319
+ def to_s
320
+ ret = Text.listencode value[:surname]
321
+ ret += ";#{Text.listencode value[:givenname]}" if !value[:givenname].empty? || !value[:middlename].empty? || !value[:honprefix].empty? || !value[:honsuffix].empty?
322
+ ret += ";#{Text.listencode value[:middlename]}" if !value[:middlename].empty? || !value[:honprefix].empty?
323
+ ret += ";#{Text.listencode value[:honprefix]}" if !value[:honprefix].empty? || !value[:honsuffix].empty?
324
+ ret += ";#{Text.listencode value[:honsuffix]}" if !value[:honsuffix].empty?
325
+ ret
326
+ end
327
+
328
+ def to_hash
329
+ value
330
+ end
331
+ end
332
+
333
+ class Address < Vobject::PropertyValue
334
+ def initialize(val)
335
+ self.value = val
336
+ self.type = "address"
337
+ end
338
+
339
+ def to_s
340
+ ret = Text.listencode value[:pobox]
341
+ ret += ";#{Text.listencode value[:ext]}" if !value[:ext].empty? || !value[:street].empty? || !value[:locality].empty? || !value[:region].empty? || !value[:code].empty? || !value[:country].empty?
342
+ ret += ";#{Text.listencode value[:street]}" if !value[:street].empty? || !value[:locality].empty? || !value[:region].empty? || !value[:code].empty? || !value[:country].empty?
343
+ ret += ";#{Text.listencode value[:locality]}" if !value[:locality].empty? || !value[:region].empty? || !value[:code].empty? || !value[:country].empty?
344
+ ret += ";#{Text.listencode value[:region]}" if !value[:region].empty? || !value[:code].empty? || !value[:country].empty?
345
+ ret += ";#{Text.listencode value[:code]}" if !value[:code].empty? || !value[:country].empty?
346
+ ret += ";#{Text.listencode value[:country]}" if !value[:country].empty?
347
+ ret
348
+ end
349
+
350
+ def to_hash
351
+ value
352
+ end
353
+ end
354
+
355
+ class Textlist < Vobject::PropertyValue
356
+ def initialize(val)
357
+ self.value = val
358
+ self.type = "textlist"
359
+ end
360
+
361
+ def to_s
362
+ value.map { |m| Text.escape m }.join(",")
363
+ end
364
+
365
+ def to_hash
366
+ value
367
+ end
368
+ end
369
+
370
+ class Agent < Vobject::PropertyValue
371
+ def initialize(val)
372
+ val[:VCARD].delete(:VERSION)
373
+ self.value = val
374
+ self.type = "agent"
375
+ end
376
+
377
+ def to_hash
378
+ ret = {}
379
+ value.each do |k, v|
380
+ ret[k] = {}
381
+ v.each do |k1, v1|
382
+ if v1.is_a?(Hash)
383
+ ret[k][k1] = {}
384
+ v1.each { |k2, v2| ret[k][k1][k2] = v2.to_hash }
385
+ else
386
+ ret[k][k1] = v1
387
+ end
388
+ end
389
+ end
390
+ ret
391
+ end
392
+
393
+ def to_s
394
+ ret = Vobject::Component.new(:VCARD, value[:VCARD], []).to_s
395
+ # spec says that colons must be expected, but none of the examples do
396
+ ret.gsub(/\n/, "\\n").gsub(/,/, "\\,").gsub(/;/, "\\;")
397
+ # ret.gsub(/\n/,"\\n").gsub(/:/,"\\:")
398
+ end
399
+ end
400
+ end
401
+ end