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.
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