virginity 0.3.31
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/virginity.rb +6 -0
- data/lib/virginity/api_extensions.rb +87 -0
- data/lib/virginity/api_extensions/fields_to_json.rb +82 -0
- data/lib/virginity/api_extensions/fields_to_xml.rb +151 -0
- data/lib/virginity/bnf.rb +84 -0
- data/lib/virginity/dir_info.rb +93 -0
- data/lib/virginity/dir_info/content_line.rb +146 -0
- data/lib/virginity/dir_info/line_folding.rb +60 -0
- data/lib/virginity/dir_info/param.rb +208 -0
- data/lib/virginity/dir_info/query.rb +144 -0
- data/lib/virginity/encoding_decoding.rb +177 -0
- data/lib/virginity/encodings.rb +36 -0
- data/lib/virginity/fixes.rb +230 -0
- data/lib/virginity/vcard.rb +244 -0
- data/lib/virginity/vcard/base_field.rb +126 -0
- data/lib/virginity/vcard/categories.rb +57 -0
- data/lib/virginity/vcard/cleaning.rb +364 -0
- data/lib/virginity/vcard/field.rb +22 -0
- data/lib/virginity/vcard/field/params.rb +93 -0
- data/lib/virginity/vcard/field_values.rb +10 -0
- data/lib/virginity/vcard/field_values/binary.rb +22 -0
- data/lib/virginity/vcard/field_values/boolean.rb +14 -0
- data/lib/virginity/vcard/field_values/case_insensitive_value.rb +13 -0
- data/lib/virginity/vcard/field_values/date.rb +16 -0
- data/lib/virginity/vcard/field_values/integer.rb +15 -0
- data/lib/virginity/vcard/field_values/optional_structured_text.rb +35 -0
- data/lib/virginity/vcard/field_values/separated_text.rb +59 -0
- data/lib/virginity/vcard/field_values/structured_text.rb +71 -0
- data/lib/virginity/vcard/field_values/text.rb +23 -0
- data/lib/virginity/vcard/field_values/uri.rb +15 -0
- data/lib/virginity/vcard/fields.rb +284 -0
- data/lib/virginity/vcard/fields_osx.rb +95 -0
- data/lib/virginity/vcard/fields_soocial.rb +45 -0
- data/lib/virginity/vcard/name_handler.rb +151 -0
- data/lib/virginity/vcard/patching.rb +262 -0
- data/lib/virginity/vcard21.rb +2 -0
- data/lib/virginity/vcard21/base.rb +30 -0
- data/lib/virginity/vcard21/parser.rb +359 -0
- data/lib/virginity/vcard21/reader.rb +103 -0
- data/lib/virginity/vcard21/writer.rb +139 -0
- metadata +111 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'virginity/vcard/base_field'
|
2
|
+
|
3
|
+
module Virginity
|
4
|
+
|
5
|
+
# Basic field, if we don't know anything about it, we assume it can at least handle text encoding
|
6
|
+
class Field < BaseField
|
7
|
+
include FieldValues::Text
|
8
|
+
|
9
|
+
field_register.default = self
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
# monkey patch ContentLine to make a #to_field method
|
14
|
+
class ContentLine
|
15
|
+
# convert to a vcard-field (see Field)
|
16
|
+
def to_field
|
17
|
+
Field.parse(self)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Virginity
|
2
|
+
|
3
|
+
module Params
|
4
|
+
module Type
|
5
|
+
|
6
|
+
|
7
|
+
class TypeArray < SerializingArray
|
8
|
+
def initialize(field)
|
9
|
+
@field = field
|
10
|
+
super(@field.params("TYPE").map { |p| p.value }.uniq)
|
11
|
+
end
|
12
|
+
|
13
|
+
# def reload!
|
14
|
+
# @array = Array.new(@field.params("TYPE").map { |p| p.value }.uniq)
|
15
|
+
# self
|
16
|
+
# end
|
17
|
+
|
18
|
+
def rewrite!
|
19
|
+
@field.params("TYPE").each {|t| @field.params.delete t }
|
20
|
+
@array.each do |type|
|
21
|
+
@field.params << Param.new('TYPE', type)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Locations are a subset of all the TYPE-params
|
26
|
+
LOCATIONS = { "CELL" => "Mobile", "HOME" => "Home", "OTHER" => "Other", "WORK" => "Work" }
|
27
|
+
def locations
|
28
|
+
@array.select { |t| LOCATIONS.keys.include?(t)}
|
29
|
+
end
|
30
|
+
|
31
|
+
def locations=(locs)
|
32
|
+
locations.each {|l| delete(l) }
|
33
|
+
locs.each { |l| self << l }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
def types
|
39
|
+
TypeArray.new(self)
|
40
|
+
end
|
41
|
+
|
42
|
+
def types=(array)
|
43
|
+
types.replace(array)
|
44
|
+
end
|
45
|
+
|
46
|
+
def type=(str)
|
47
|
+
self.types = str.split(/ /)
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_type(thing)
|
51
|
+
t = types
|
52
|
+
t << thing unless t.include? thing
|
53
|
+
end
|
54
|
+
|
55
|
+
def remove_type(thing)
|
56
|
+
types.delete(thing)
|
57
|
+
end
|
58
|
+
|
59
|
+
# =============================
|
60
|
+
# Preferred
|
61
|
+
def preferred?
|
62
|
+
types.include? 'PREF'
|
63
|
+
end
|
64
|
+
|
65
|
+
def preferred=(val)
|
66
|
+
if val
|
67
|
+
add_type 'PREF'
|
68
|
+
else
|
69
|
+
remove_type 'PREF'
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# =============================
|
74
|
+
# Location handling:
|
75
|
+
def locations
|
76
|
+
types.locations
|
77
|
+
end
|
78
|
+
|
79
|
+
def locations=(array)
|
80
|
+
types.locations = array
|
81
|
+
end
|
82
|
+
|
83
|
+
def location
|
84
|
+
locations.join(" ")
|
85
|
+
end
|
86
|
+
|
87
|
+
def location=(str)
|
88
|
+
self.locations = str.split(/ /)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'virginity/vcard/field_values/binary.rb'
|
2
|
+
require 'virginity/vcard/field_values/boolean.rb'
|
3
|
+
require 'virginity/vcard/field_values/case_insensitive_value.rb'
|
4
|
+
require 'virginity/vcard/field_values/date.rb'
|
5
|
+
require 'virginity/vcard/field_values/integer.rb'
|
6
|
+
require 'virginity/vcard/field_values/separated_text.rb'
|
7
|
+
require 'virginity/vcard/field_values/structured_text.rb'
|
8
|
+
require 'virginity/vcard/field_values/optional_structured_text.rb'
|
9
|
+
require 'virginity/vcard/field_values/text.rb'
|
10
|
+
require 'virginity/vcard/field_values/uri.rb'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Virginity
|
2
|
+
module FieldValues
|
3
|
+
|
4
|
+
module Binary
|
5
|
+
def binary
|
6
|
+
Base64.decode64(@value)
|
7
|
+
end
|
8
|
+
|
9
|
+
def binary=(s)
|
10
|
+
@params.delete_if { |p| p.key == "ENCODING" }
|
11
|
+
@params << Param.new("ENCODING", "b")
|
12
|
+
b64 = Base64.encode64(s)
|
13
|
+
b64.delete!("\n") # can return nil... bah, but probably faster than #delete without an exclamation mark
|
14
|
+
@value = b64
|
15
|
+
end
|
16
|
+
|
17
|
+
def sha1
|
18
|
+
Digest::SHA1.hexdigest(@value)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Virginity
|
2
|
+
module FieldValues
|
3
|
+
|
4
|
+
class OptionalStructuredText < StructuredText
|
5
|
+
def self.define(components)
|
6
|
+
m = super
|
7
|
+
|
8
|
+
m.module_eval <<-RUBY, __FILE__, __LINE__+1
|
9
|
+
def reencode!(options = {})
|
10
|
+
v = values
|
11
|
+
|
12
|
+
v.pop while v.last.empty?
|
13
|
+
|
14
|
+
@value = EncodingDecoding::encode_structured_text(v)
|
15
|
+
end
|
16
|
+
RUBY
|
17
|
+
|
18
|
+
components.each_with_index do |component, idx|
|
19
|
+
m.module_eval <<-RUBY, __FILE__, __LINE__+1
|
20
|
+
def #{component}=(new_value)
|
21
|
+
structure = values
|
22
|
+
structure[#{idx}] = new_value.to_s
|
23
|
+
|
24
|
+
structure.pop while structure.size > 0 && (structure.last.nil? || structure.last.empty?)
|
25
|
+
|
26
|
+
@value = EncodingDecoding::encode_structured_text(structure)
|
27
|
+
end
|
28
|
+
RUBY
|
29
|
+
end
|
30
|
+
|
31
|
+
m
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'reactive_array'
|
2
|
+
require 'digest/sha1'
|
3
|
+
|
4
|
+
module Virginity
|
5
|
+
module FieldValues
|
6
|
+
|
7
|
+
module SeparatedText
|
8
|
+
|
9
|
+
|
10
|
+
class TextList < SerializingArray
|
11
|
+
def initialize(field)
|
12
|
+
@field = field # a reference to the original Field
|
13
|
+
super(EncodingDecoding::decode_text_list(@field.raw_value))
|
14
|
+
save_sha1!
|
15
|
+
end
|
16
|
+
|
17
|
+
def sha1
|
18
|
+
Digest::SHA1.hexdigest(@field.raw_value)
|
19
|
+
end
|
20
|
+
|
21
|
+
def save_sha1!
|
22
|
+
@sha1 = sha1
|
23
|
+
end
|
24
|
+
|
25
|
+
def needs_refresh?
|
26
|
+
@sha1 != sha1
|
27
|
+
end
|
28
|
+
|
29
|
+
def rewrite!
|
30
|
+
@array.delete_if {|v| v.empty? }
|
31
|
+
@field.raw_value = EncodingDecoding::encode_text_list(@array)
|
32
|
+
save_sha1!
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
def values
|
38
|
+
if (@textlist.needs_refresh? rescue true)
|
39
|
+
@textlist = TextList.new(self)
|
40
|
+
else
|
41
|
+
@textlist
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def values=(a)
|
46
|
+
values.replace(a)
|
47
|
+
end
|
48
|
+
|
49
|
+
def reencode!
|
50
|
+
values.rewrite!
|
51
|
+
end
|
52
|
+
|
53
|
+
def subset_of?(other)
|
54
|
+
values.all? { |v| other.values.include? v }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Virginity
|
2
|
+
module FieldValues
|
3
|
+
|
4
|
+
class StructuredText
|
5
|
+
def self.define(components)
|
6
|
+
m = Module.new
|
7
|
+
m.const_set("COMPONENTS", components)
|
8
|
+
m.module_eval <<-RUBY
|
9
|
+
def components
|
10
|
+
COMPONENTS
|
11
|
+
end
|
12
|
+
|
13
|
+
def values
|
14
|
+
EncodingDecoding::decode_structured_text(@value, COMPONENTS.size)
|
15
|
+
end
|
16
|
+
|
17
|
+
def empty?
|
18
|
+
values.all? {|v| v.empty? }
|
19
|
+
end
|
20
|
+
|
21
|
+
def reencode!(options={})
|
22
|
+
v = values
|
23
|
+
unless options[:variable_number_of_fields]
|
24
|
+
v.pop while v.size > components.size
|
25
|
+
v.push(nil) while v.size < components.size
|
26
|
+
else
|
27
|
+
v.pop while v.last.empty?
|
28
|
+
end
|
29
|
+
@value = EncodingDecoding::encode_structured_text(v)
|
30
|
+
end
|
31
|
+
|
32
|
+
def [](component)
|
33
|
+
raise "which component? \#\{component\}?? I only know \#\{components.inspect\}" unless components.include? component.to_sym
|
34
|
+
send("\#\{component\}".to_sym)
|
35
|
+
end
|
36
|
+
|
37
|
+
def []=(component, new_value)
|
38
|
+
raise "which component? \#\{component\}?? I only know \#\{components.inspect\}" unless components.include? component.to_sym
|
39
|
+
send("\#\{component\}=", new_value)
|
40
|
+
end
|
41
|
+
RUBY
|
42
|
+
|
43
|
+
components.each_with_index do |component, idx|
|
44
|
+
m.module_eval <<-RUBY
|
45
|
+
def #{component}
|
46
|
+
values[#{idx}]
|
47
|
+
end
|
48
|
+
|
49
|
+
def #{component}=(new_value)
|
50
|
+
structure = values
|
51
|
+
structure[#{idx}] = new_value.to_s
|
52
|
+
@value = EncodingDecoding::encode_structured_text(structure)
|
53
|
+
end
|
54
|
+
|
55
|
+
def subset_of?(other_field)
|
56
|
+
components.all? do |component|
|
57
|
+
send(component).empty? or send(component) == other_field.send(component)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def superset_of?(other_field)
|
62
|
+
other_field.subset_of?(self)
|
63
|
+
end
|
64
|
+
RUBY
|
65
|
+
end
|
66
|
+
m
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Virginity
|
2
|
+
module FieldValues
|
3
|
+
|
4
|
+
module Text
|
5
|
+
def text
|
6
|
+
EncodingDecoding::decode_text(@value)
|
7
|
+
end
|
8
|
+
|
9
|
+
def text=(s)
|
10
|
+
@params.delete_if { |p| p.key == "ENCODING" }
|
11
|
+
@value = EncodingDecoding::encode_text(s)
|
12
|
+
end
|
13
|
+
|
14
|
+
def reencode!
|
15
|
+
self.text = text
|
16
|
+
end
|
17
|
+
|
18
|
+
def value_to_xml
|
19
|
+
xml_element("text", text.strip)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,284 @@
|
|
1
|
+
require "virginity/vcard/field"
|
2
|
+
require "virginity/vcard/field/params"
|
3
|
+
require "base64"
|
4
|
+
|
5
|
+
module Virginity
|
6
|
+
|
7
|
+
# BEGIN or END
|
8
|
+
#
|
9
|
+
# We don't do much with them
|
10
|
+
class BeginEnd < BaseField
|
11
|
+
include FieldValues::CaseInsensitiveValue
|
12
|
+
# value MUST be "VCARD"
|
13
|
+
register_for :BEGIN, :END
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
class Profile < BaseField
|
18
|
+
register_for :PROFILE
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
# Text fields, see FieldValues::Text for safe encoding/decoding methods
|
23
|
+
class TextField < BaseField
|
24
|
+
include FieldValues::Text
|
25
|
+
register_for :CLASS, :FN, :LABEL, :MAILER, :NOTE, :PRODID, :ROLE, 'SORT-STRING', :TITLE, :UID, :VERSION, :'X-PHONETIC-LAST-NAME', :'X-PHONETIC-FIRST-NAME'
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
# A BDAY in a vCard can be a free form text-value or a date. This class provides methods for both cases.
|
30
|
+
class Birthday < BaseField
|
31
|
+
include FieldValues::Text
|
32
|
+
include FieldValues::DateValue
|
33
|
+
register_for :BDAY
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
class Anniversary < BaseField
|
38
|
+
include FieldValues::Text
|
39
|
+
include FieldValues::DateValue
|
40
|
+
register_for "X-ANNIVERSARY"
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
# Instant messaging fields are defined in rfc 2427. They have a scheme and an address. If an IMPP is not 'clean' one can still get/set the value by using the methods from FieldValues::Text and FieldValues::Uri
|
45
|
+
class Impp < BaseField
|
46
|
+
include FieldValues::Text
|
47
|
+
include FieldValues::Uri
|
48
|
+
include Params::Type
|
49
|
+
register_for :IMPP
|
50
|
+
# include LocationHandling
|
51
|
+
# include PurposeHandling
|
52
|
+
# include PreferenceHandling
|
53
|
+
# PURPOSE_SETS = { "personal" => "Personal", "business" => "Business" }
|
54
|
+
# HUMAN_DESCRIPTION = "instant messaging"
|
55
|
+
|
56
|
+
# def initialize(content_line=ContentLine.new("IMPP:"))
|
57
|
+
# super
|
58
|
+
# @cline.name = "IMPP"
|
59
|
+
# end
|
60
|
+
|
61
|
+
def scheme
|
62
|
+
# every piece of text before the first colon, if there is a colon present
|
63
|
+
text.match(/^(.*?):/) # Note the use of "*?" for non greedy matching of the colon!
|
64
|
+
$1 || ""
|
65
|
+
end
|
66
|
+
|
67
|
+
def address
|
68
|
+
# everything after the first colon if that colon is present, otherwise the whole text
|
69
|
+
text.match(/^.*?:(.*)$|^(.*)$/)
|
70
|
+
$1 || $2
|
71
|
+
end
|
72
|
+
|
73
|
+
def scheme=(s)
|
74
|
+
self.text = "#{s}:#{address}"
|
75
|
+
end
|
76
|
+
|
77
|
+
def address=(s)
|
78
|
+
self.text = "#{self.scheme}:#{s}"
|
79
|
+
end
|
80
|
+
|
81
|
+
def raw_value
|
82
|
+
scheme.empty? && address.empty? ? "" : @value
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
# OS X AddressBook does not use the IMPP fields. Instead Apple chose to use their own proprietary format. This format is handles by CustomImField
|
88
|
+
class CustomImField < BaseField
|
89
|
+
include FieldValues::Text
|
90
|
+
PROTOCOL_TRANSLATION_TABLE = {
|
91
|
+
:aim => "X-AIM",
|
92
|
+
:msn => "X-MSN",
|
93
|
+
:ymsgr => "X-YAHOO",
|
94
|
+
:skype => "X-SKYPE",
|
95
|
+
:qq => "X-QQ",
|
96
|
+
:gtalk => "X-GOOGLE TALK",
|
97
|
+
:icq => "X-ICQ",
|
98
|
+
:xmpp => "X-JABBER",
|
99
|
+
}
|
100
|
+
PROTOCOL_TRANSLATION_TABLE.values.each { |protocol| register_for protocol }
|
101
|
+
PROTOCOL_TRANSLATION_INVERSE_TABLE = Hash[PROTOCOL_TRANSLATION_TABLE.map { |k, v| [v, k] }]
|
102
|
+
|
103
|
+
# convert a standard IMPP field to a Custom IM field. Only schemes defined in PROTOCOL_TRANSLATION_TABLE are supported.
|
104
|
+
def self.from_impp(impp)
|
105
|
+
nm = PROTOCOL_TRANSLATION_TABLE[impp.scheme.to_sym]
|
106
|
+
raise "unknown scheme #{impp.scheme} for #{impp.text.inspect}" if nm.nil?
|
107
|
+
x = Field.parse("#{nm}:")
|
108
|
+
x.params = Param::deep_copy(impp.params)
|
109
|
+
x.text = impp.address
|
110
|
+
x
|
111
|
+
end
|
112
|
+
|
113
|
+
def protocol
|
114
|
+
PROTOCOL_TRANSLATION_INVERSE_TABLE[name]
|
115
|
+
end
|
116
|
+
|
117
|
+
# convert to a standard IMPP field
|
118
|
+
def to_impp
|
119
|
+
impp = Field.parse("IMPP:")
|
120
|
+
impp.params = Param::deep_copy(@params)
|
121
|
+
impp.text = "#{protocol}:#{text}"
|
122
|
+
impp
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
# handle ORG fields.
|
128
|
+
#
|
129
|
+
# An Org has an orgname, a unit1 and a unit2. We stick to this simple
|
130
|
+
# definition for now since it is widely used. The vCard specs seem to
|
131
|
+
# specify an unlimited amount of units
|
132
|
+
class Org < BaseField
|
133
|
+
register_for :ORG
|
134
|
+
include FieldValues::StructuredText.define([:orgname, :unit1, :unit2])
|
135
|
+
|
136
|
+
def shortened
|
137
|
+
[orgname, unit1, unit2].join(" ").strip
|
138
|
+
end
|
139
|
+
|
140
|
+
# def ==(other)
|
141
|
+
# super ||
|
142
|
+
# has_name?(other.name) &&
|
143
|
+
# values == other.try(:values) && self.class === other && self.group == other.group
|
144
|
+
# end
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
# SeparatedField handles an array of text values
|
149
|
+
class SeparatedField < BaseField
|
150
|
+
include FieldValues::SeparatedText
|
151
|
+
register_for :CATEGORIES, :NICKNAME
|
152
|
+
|
153
|
+
def unpacked
|
154
|
+
values.map do |text|
|
155
|
+
self.class.new(name, EncodingDecoding::encode_text_list([text]), params, group)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
# telephone number
|
162
|
+
#
|
163
|
+
# provides the easy getter/setter #number and generators for random numbers
|
164
|
+
class Tel < BaseField
|
165
|
+
include FieldValues::Text
|
166
|
+
include Params::Type
|
167
|
+
# include Params::Type::Preference
|
168
|
+
register_for :TEL
|
169
|
+
COMPARISON_REGEX = /[^\+\*\#\/\,\w]/u # anything that is NOT a plus, a star, a hash, a slash, a comma, or 0-9/a-z/A-Z is insignificant
|
170
|
+
|
171
|
+
alias_method :number, :text
|
172
|
+
alias_method :number=, :text=
|
173
|
+
|
174
|
+
def self.random_number(length = 10)
|
175
|
+
# (1..length).map { rand(10).to_s }.join
|
176
|
+
# only 555-0100 through 555-0199 are now specifically reserved for fictional use
|
177
|
+
random_part = 100 + (rand(99) + 1)
|
178
|
+
"555-0#{random_part}"
|
179
|
+
end
|
180
|
+
|
181
|
+
def self.at_random
|
182
|
+
new("TEL", random_number)
|
183
|
+
end
|
184
|
+
|
185
|
+
def significant_chars
|
186
|
+
# it's a phone number (but people could store alphanumeric stuff here) so I opt to remove all dashes and spaces
|
187
|
+
# I leave the plus since we don't know with what prefix to replace it with, see: http://en.wikipedia.org/wiki/List_of_international_call_prefixes
|
188
|
+
number.gsub(COMPARISON_REGEX, "")
|
189
|
+
end
|
190
|
+
alias_method :normalized_number, :significant_chars
|
191
|
+
|
192
|
+
end
|
193
|
+
|
194
|
+
|
195
|
+
class Email < BaseField
|
196
|
+
include FieldValues::Text
|
197
|
+
include Params::Type
|
198
|
+
register_for :EMAIL
|
199
|
+
alias_method :address, :text
|
200
|
+
alias_method :address=, :text=
|
201
|
+
end
|
202
|
+
|
203
|
+
|
204
|
+
class Adr < BaseField
|
205
|
+
include FieldValues::StructuredText.define([:pobox, :extended, :street, :locality, :region, :postal_code, :country])
|
206
|
+
include Params::Type
|
207
|
+
register_for :ADR
|
208
|
+
NAME = "ADR"
|
209
|
+
end
|
210
|
+
|
211
|
+
|
212
|
+
class Url < BaseField
|
213
|
+
include FieldValues::Text
|
214
|
+
include FieldValues::Uri
|
215
|
+
# URLs officialy don't have TYPE params,
|
216
|
+
# but everyone and their dog thinks they do.
|
217
|
+
# So we decided to support them too.
|
218
|
+
# Conclusion: no-one reads the RFC
|
219
|
+
include Params::Type
|
220
|
+
register_for :URL
|
221
|
+
end
|
222
|
+
|
223
|
+
|
224
|
+
class Name < BaseField
|
225
|
+
PARTS = %w(family given additional prefix suffix)
|
226
|
+
include FieldValues::StructuredText.define(PARTS)
|
227
|
+
register_for :N
|
228
|
+
end
|
229
|
+
|
230
|
+
|
231
|
+
class Photo < BaseField
|
232
|
+
include FieldValues::Text
|
233
|
+
include FieldValues::Binary
|
234
|
+
include FieldValues::Uri
|
235
|
+
register_for :LOGO, :PHOTO
|
236
|
+
|
237
|
+
def is_binary?
|
238
|
+
@params.detect {|p| p.key == "ENCODING" and p.value =~ /B/i }
|
239
|
+
end
|
240
|
+
|
241
|
+
def is_url?
|
242
|
+
!is_binary?
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
|
247
|
+
class Sound < BaseField
|
248
|
+
include FieldValues::Binary
|
249
|
+
include FieldValues::Text
|
250
|
+
include FieldValues::Uri
|
251
|
+
register_for :SOUND
|
252
|
+
end
|
253
|
+
|
254
|
+
|
255
|
+
class Key < BaseField
|
256
|
+
include FieldValues::Binary
|
257
|
+
include FieldValues::Text
|
258
|
+
register_for :KEY
|
259
|
+
end
|
260
|
+
|
261
|
+
class Gender < BaseField
|
262
|
+
register_for :GENDER
|
263
|
+
PARTS = %w(sex identity)
|
264
|
+
include FieldValues::OptionalStructuredText.define(PARTS)
|
265
|
+
|
266
|
+
{male: 'M', female: 'F', other: 'O', none: 'N', unknown: 'U'}.each do |name, letter|
|
267
|
+
class_eval <<-RUBY
|
268
|
+
def #{name}?
|
269
|
+
sex == "#{letter}"
|
270
|
+
end
|
271
|
+
|
272
|
+
def #{name}=(v)
|
273
|
+
raise "Only true is acceptable" unless v
|
274
|
+
self.sex = "#{letter}"
|
275
|
+
end
|
276
|
+
RUBY
|
277
|
+
end
|
278
|
+
|
279
|
+
def neither?
|
280
|
+
sex !~ /^[MFNOU]$/
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
end
|