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