ruby-vobject 0.1.0 → 1.0.1

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 (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,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,176 @@
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/v3_0/paramcheck"
10
+ require "vobject/vcard/v3_0/typegrammars"
11
+ require_relative "../../../c"
12
+ require_relative "../../../error"
13
+
14
+ module Vcard::V3_0
15
+ class Grammar
16
+ attr_accessor :strict, :errors
17
+ class << self
18
+ def unfold(str)
19
+ str.gsub(/[\n\r]+[ \t]/, "")
20
+ end
21
+ end
22
+
23
+ def vobject_grammar
24
+ # properties with value cardinality 1
25
+ @cardinality1 = {}
26
+ @cardinality1[:PARAM] = Set.new [:VALUE]
27
+ @cardinality1[:PROP] = Set.new [:KIND, :N, :BDAY, :ANNIVERSARY, :GENDER, :PRODID, :REV, :UID]
28
+
29
+ group = C::IANATOKEN
30
+ linegroup = group << "."
31
+ beginend = /BEGIN/i.r | /END/i.r
32
+
33
+ # parameters && parameter types
34
+ paramname = /ENCODING/i.r | /LANGUAGE/i.r | /CONTEXT/i.r | /TYPE/i.r | /VALUE/i.r | /PREF/i.r
35
+ otherparamname = C::NAME_VCARD ^ paramname
36
+ paramvalue = C::QUOTEDSTRING_VCARD.map { |s| s } | C::PTEXT_VCARD.map(&:upcase)
37
+
38
+ # prefvalue = /[0-9]{1,2}/i.r | "100".r
39
+ valuetype = /URI/i.r | /DATE/i.r | /DATE-TIME/i.r | /BINARY/i.r | /PTEXT/i.r
40
+ # mediaattr = /[!\"#$%&'*+.^A-Z0-9a-z_`i{}|~-]+/.r
41
+ # mediavalue1 = mediaattr | C::QUOTEDSTRING_VCARD
42
+ # mediatail = seq(";".r >> mediaattr, "=".r << mediavalue1).map do |a, v|
43
+ # ";#{a}=#{v}"
44
+ # end
45
+ # rfc4288regname = /[A-Za-z0-9!#$&.+^+-]{1,127}/.r
46
+ # rfc4288typename = rfc4288regname
47
+ # rfc4288subtypename = rfc4288regname
48
+ # mediavalue = seq(rfc4288typename << "/".r, rfc4288subtypename, # mediatail.star).map do |t, s, tail|
49
+ # ret = "#{t}/#{s}"
50
+ # ret = ret . tail[0] unless tail.empty?
51
+ # ret
52
+ # end
53
+ pvalue_list = (seq(paramvalue << ",".r, lazy { pvalue_list }) & /[;:]/.r).map do |e, list|
54
+ [e.sub(Regexp.new("^\"(.+)\"$"), '\1').gsub(/\\n/, "\n"), list].flatten
55
+ end | (paramvalue & /[;:]/.r).map do |e|
56
+ [e.sub(Regexp.new("^\"(.+)\"$"), '\1').gsub(/\\n/, "\n")]
57
+ end
58
+ typevaluelist = seq(C::IANATOKEN, ",".r >> lazy { typevaluelist }).map do |t, l|
59
+ [t.upcase, l].flatten
60
+ end | C::IANATOKEN.map { |t| [t.upcase] }
61
+ quoted_string_list = (seq(C::QUOTEDSTRING_VCARD << ",".r, lazy { quoted_string_list }) & /[;:]/.r).map do |e, list|
62
+ [e.sub(Regexp.new("^\"(.+)\"$"), '\1').gsub(/\\n/, "\n"), list].flatten
63
+ end | (C::QUOTEDSTRING_VCARD & /[;:]/.r).map do |e|
64
+ [e.sub(Regexp.new("^\"(.+)\"$"), '\1').gsub(/\\n/, "\n")]
65
+ end
66
+
67
+ # fmttypevalue = seq(rfc4288typename, "/", rfc4288subtypename).map(&:join)
68
+ rfc1766primarytag = /[A-Za-z]{1,8}/.r
69
+ rfc1766subtag = seq("-", /[A-Za-z]{1,8}/.r) { |a, b| a + b }
70
+ rfc1766language = seq(rfc1766primarytag, rfc1766subtag.star) do |a, b|
71
+ a += b[0] unless b.empty?
72
+ a
73
+ end
74
+
75
+ param = seq(/ENCODING/i.r, "=", /b/.r) do |name, _, val|
76
+ { name.upcase.tr("-", "_").to_sym => val }
77
+ end | seq(/LANGUAGE/i.r, "=", rfc1766language) do |name, _, val|
78
+ { name.upcase.tr("-", "_").to_sym => val.upcase }
79
+ end | seq(/CONTEXT/i.r, "=", /word/.r) do |name, _, val|
80
+ { name.upcase.tr("-", "_").to_sym => val.upcase }
81
+ end | seq(/TYPE/i.r, "=", typevaluelist) do |name, _, val|
82
+ { name.upcase.tr("-", "_").to_sym => val }
83
+ end | seq(/VALUE/i.r, "=", valuetype) do |name, _, val|
84
+ { name.upcase.tr("-", "_").to_sym => val }
85
+ end | /PREF/i.r.map do |_name|
86
+ # this is likely erroneous use of VCARD 2.1 convention in RFC2739; converting to canonical TYPE=PREF
87
+ { TYPE: ["PREF"] }
88
+ end | seq(otherparamname, "=", pvalue_list) do |name, _, val|
89
+ val = val[0] if val.length == 1
90
+ { name.upcase.tr("-", "_").to_sym => val }
91
+ end | seq(paramname, "=", pvalue_list) do |name, _, val|
92
+ parse_err("Violated format of parameter value #{name} = #{val}")
93
+ end
94
+
95
+ params = seq(";".r >> param & ";", lazy { params }) do |p, ps|
96
+ p.merge(ps) do |key, old, new|
97
+ if @cardinality1[:PARAM].include?(key)
98
+ parse_err("Violated cardinality of parameter #{key}")
99
+ end
100
+ [old, new].flatten
101
+ # deal with duplicate properties
102
+ end
103
+ end | seq(";".r >> param).map { |e| e[0] }
104
+
105
+ contentline = seq(linegroup._?, C::NAME_VCARD, params._? << ":".r,
106
+ C::VALUE, /(\r|\n|\r\n)/) do |g, name, p, value, _|
107
+ key = name.upcase.tr("-", "_").to_sym
108
+ hash = { key => {} }
109
+ hash[key][:value], errors1 = Typegrammars.typematch(strict, key, p[0], :GENERIC, value, @ctx)
110
+ errors << errors1
111
+ hash[key][:group] = g[0] unless g.empty?
112
+ errors << Paramcheck.paramcheck(strict, key, p.empty? ? {} : p[0], @ctx)
113
+ hash[key][:params] = p[0] unless p.empty?
114
+ hash
115
+ end
116
+ props = seq(contentline, lazy { props }) do |c, rest|
117
+ c.merge(rest) do |key, old, new|
118
+ if @cardinality1[:PROP].include?(key.upcase)
119
+ parse_err("Violated cardinality of property #{key}")
120
+ end
121
+ [old, new].flatten
122
+ # deal with duplicate properties
123
+ end
124
+ end | ("".r & beginend).map { {} }
125
+
126
+ calpropname = /VERSION/i.r
127
+ calprop = seq(linegroup._?, calpropname << ":".r, C::VALUE, /[\r\n]/) do |g, key, value, _|
128
+ key = key.upcase.tr("-", "_").to_sym
129
+ hash = { key => {} }
130
+ hash[key][:value], errors1 = Typegrammars.typematch(strict, key, nil, :VCARD, value, @ctx)
131
+ errors << errors1
132
+ hash[key][:group] = g[0] unless g.empty?
133
+ hash
134
+ end
135
+ vobject = seq(linegroup._?, /BEGIN:VCARD[\r\n]/i.r >> calprop, props, linegroup._? << /END:VCARD[\r\n]/i.r) do |(_g, v, rest, _g1)|
136
+ # TODO what do we do with the groups here?
137
+ parse_err("Missing VERSION attribute") unless v.has_key?(:VERSION)
138
+ parse_err("Missing FN attribute") unless rest.has_key?(:FN)
139
+ parse_err("Missing N attribute") unless rest.has_key?(:N)
140
+ rest.delete(:END)
141
+ { VCARD: v.merge(rest), errors: errors.flatten }
142
+ end
143
+ vobject.eof
144
+ end
145
+
146
+ def initialize(strict)
147
+ self.strict = strict
148
+ self.errors = []
149
+ end
150
+
151
+ def parse(vobject)
152
+ @ctx = Rsec::ParseContext.new self.class.unfold(vobject), "source"
153
+ ret = vobject_grammar._parse @ctx
154
+ if !ret || Rsec::INVALID[ret]
155
+ if strict
156
+ raise @ctx.generate_error "source"
157
+ else
158
+ errors << @ctx.generate_error("source")
159
+ ret = { VCARD: nil, errors: errors.flatten }
160
+ end
161
+ end
162
+ Rsec::Fail.reset
163
+ ret
164
+ end
165
+
166
+ private
167
+
168
+ def parse_err(msg)
169
+ if strict
170
+ raise @ctx.report_error msg, "source"
171
+ else
172
+ errors << @ctx.report_error(msg, "source")
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,111 @@
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
+
9
+ module Vcard::V3_0
10
+ class Paramcheck
11
+ class << self
12
+ def paramcheck(strict, prop, params, ctx)
13
+ errors = []
14
+ if params && params[:TYPE]
15
+ 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
16
+ end
17
+ case prop
18
+ when :NAME, :PROFILE, :GEO, :PRODID, :URL, :VERSION, :CLASS
19
+ parse_err(strict, errors, "illegal parameters #{params} given for #{prop}", ctx) unless params.empty?
20
+ when :CALURI, :CAPURI, :CALADRURI, :FBURL
21
+ params.each do |key, val|
22
+ parse_err(strict, errors, "illegal parameter #{key} given for #{prop}", ctx) unless key == :TYPE
23
+ if params[:TYPE].is_a?(Array)
24
+ val.each do |v|
25
+ parse_err(strict, errors, "illegal parameter value #{v} given for parameter #{key} of #{prop}", ctx) unless v == "PREF"
26
+ end
27
+ else
28
+ parse_err(strict, errors, "illegal parameter value #{val} given for parameter #{key} of #{prop}", ctx) unless val == "PREF"
29
+ end
30
+ end
31
+ when :SOURCE
32
+ params.each do |key, val|
33
+ parse_err(strict, errors, "illegal parameter #{key} given for #{prop}", ctx) unless key == :VALUE || key == :CONTEXT || key =~ /^x/i
34
+ parse_err(strict, errors, "illegal value #{val} given for parameter #{key} of #{prop}", ctx) if key == :VALUE && val != "uri"
35
+ parse_err(strict, errors, "illegal value #{val} given for parameter #{key} of #{prop}", ctx) if key == :CONTEXT && val != "word"
36
+ end
37
+ when :FN, :N, :NICKNAME, :MAILER, :TITLE, :ROLE, :ORG, :CATEGORIES, :NOTE, :SORT_STRING
38
+ params.each do |key, val|
39
+ parse_err(strict, errors, "illegal parameter #{key} given for #{prop}", ctx) unless key == :VALUE || key == :LANGUAGE || key =~ /^x/i
40
+ parse_err(strict, errors, "illegal value #{val} given for parameter #{key} of #{prop}", ctx) if key == :VALUE && val != "ptext"
41
+ end
42
+ when :TEL, :IMPP, :UID
43
+ # UID included here per errata
44
+ params.each_key do |key|
45
+ parse_err(strict, errors, "illegal parameter #{key} given for #{prop}", ctx) unless key == :TYPE
46
+ end
47
+ # we do not check the values of the :TEL :TYPE parameter, because they include ianaToken
48
+ when :EMAIL
49
+ params.each_key do |key|
50
+ parse_err(strict, errors, "illegal parameter #{key} given for #{prop}", ctx) unless key == :TYPE
51
+ end
52
+ # we do not check the values of the first :EMAIL :TYPE parameter, because they include ianaToken
53
+ when :ADR, :LABEL
54
+ params.each do |key, val|
55
+ parse_err(strict, errors, "illegal parameter #{key} given for #{prop}", ctx) unless [:VALUE, :LANGUAGE, :TYPE].include? key || key =~ /^x/i
56
+ parse_err(strict, errors, "illegal value #{val} given for parameter #{key} of #{prop}", ctx) if key == :VALUE && val != "ptext"
57
+ end
58
+ # we do not check the values of the :ADR :TYPE parameter, because they include ianaToken
59
+ when :KEY
60
+ params.each do |key, val|
61
+ # VALUE included here per errata
62
+ parse_err(strict, errors, "illegal parameter #{key} given for #{prop}", ctx) unless [:TYPE, :ENCODING, :VALUE].include? key
63
+ parse_err(strict, errors, "illegal value #{val} given for parameter #{key} of #{prop}", ctx) if key == :VALUE && val != "binary"
64
+ end
65
+ # we do not check the values of the :KEY :TYPE parameter, because they include ianaToken
66
+ when :PHOTO, :LOGO, :SOUND
67
+ params.each_key do |key|
68
+ parse_err(strict, errors, "illegal parameter #{key} given for #{prop}", ctx) unless [:VALUE, :TYPE, :ENCODING].include? key
69
+ end
70
+ parse_err(strict, errors, "illegal value #{params[:VALUE]} of :VALUE given for #{prop}", ctx) if params[:VALUE] && params[:VALUE] != "binary" && params[:VALUE] != "uri"
71
+ parse_err(strict, errors, "illegal value #{params[:ENCODING]} of :ENCODING given for #{prop}", ctx) if params[:ENCODING] && (params[:ENCODING] != "b" || params[:VALUE] == "uri")
72
+ parse_err(strict, errors, "mandatory parameter of :ENCODING missing for #{prop}", ctx) if !params.has_key?(:ENCODING) && (!params.key?(:VALUE) || params[:VALUE] == "binary")
73
+ # TODO restriction of :TYPE to image types registered with IANA
74
+ # TODO restriction of :TYPE to sound types registered with IANA
75
+ when :BDAY, :REV
76
+ params.each_key do |key|
77
+ parse_err(strict, errors, "illegal parameter #{key} given for #{prop}", ctx) unless key == :VALUE
78
+ end
79
+ parse_err(strict, errors, "illegal value #{params[:VALUE]} of :VALUE given for #{prop}", ctx) if params[:VALUE] && params[:VALUE] != "date" && params[:VALUE] != "date-time"
80
+ when :AGENT
81
+ params.each_key do |key|
82
+ parse_err(strict, errors, "illegal parameter #{key} given for #{prop}", ctx) unless key == :VALUE
83
+ end
84
+ parse_err(strict, errors, "illegal value #{params[:VALUE]} of :VALUE given for #{prop}", ctx) if params[:VALUE] && params[:VALUE] != "uri"
85
+ when :TZ
86
+ # example in definition contradicts spec! Spec says :TZ takes no params at all
87
+ params.each_key do |key|
88
+ parse_err(strict, errors, "illegal parameter #{key} given for #{prop}", ctx) unless key == :VALUE
89
+ end
90
+ parse_err(strict, errors, "illegal value #{params[:VALUE]} of :VALUE given for #{prop}", ctx) if params[:VALUE] && params[:VALUE] != "text"
91
+ else
92
+ params.each_key do |key|
93
+ parse_err(strict, errors, "illegal parameter #{key} given for #{prop}", ctx) unless key == :VALUE || key == :LANGUAGE || key =~ /^x/i
94
+ parse_err(strict, errors, "illegal value #{val} given for parameter #{key} of #{prop}") if key == :VALUE && val != "ptext"
95
+ end
96
+ end
97
+ errors
98
+ end
99
+
100
+ private
101
+
102
+ def parse_err(strict, errors, msg, ctx)
103
+ if strict
104
+ raise ctx.report_error msg, "source"
105
+ else
106
+ errors << ctx.report_error(msg, "source")
107
+ end
108
+ end
109
+ end
110
+ end
111
+ 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