Class: Virginity::Vcard

Inherits:
DirectoryInformation show all
Includes:
Encodings, Patching, VcardCategories, VcardCleaning
Defined in:
lib/virginity/vcard.rb,
lib/virginity/fixes.rb,
lib/virginity/vcard/patching.rb,
lib/virginity/vcard/name_handler.rb

Overview

rfc 2426, vCard MIME Directory Profile

Defined Under Namespace

Modules: Patching Classes: NameHandler

Constant Summary collapse

EMPTY =
"BEGIN:VCARD\nN:;;;;\nVERSION:3.0\nEND:VCARD\n"
VCARD_REGEX =
/^[\ \t]*VCARD[\ \t]*$/i
VERSION21 =
/^VERSION\:2\.1(\s*)$/i
END_VCARD =
/^END\:VCARD\s*$/i
CRLF =

a vCard 2.1 string representation of this vCard if :windows_line_endings => true then lines end with rn otherwise with n

"\r\n"
IGNORE_IN_SUBSET_COMPARISON =

are all fields also present or supersets in other?

%w(BEGIN END FN)
SINGLETON_FIELDS =
%w(N FN BEGIN END VERSION)
ADR =
"ADR"
BDAY =
"BDAY"
CATEGORIES =
"CATEGORIES"
EMAIL =
"EMAIL"
IMPP =
"IMPP"
LOGO =
"LOGO"
NICKNAME =
"NICKNAME"
TEL =
"TEL"
NOTE =
"NOTE"
ORG =
"ORG"
PHOTO =
"PHOTO"
TITLE =
"TITLE"
URL =
"URL"
XABRELATEDNAMES =
"X-ABRELATEDNAMES"
XABDATE =
"X-ABDATE"
XANNIVERSARY =
"X-ANNIVERSARY"

Constants included from VcardCleaning

Virginity::VcardCleaning::NAME, Virginity::VcardCleaning::RSTRIPPABLE_FIELDS, Virginity::VcardCleaning::VERSION, Virginity::VcardCleaning::VERSION30, Virginity::VcardCleaning::X_ABUID, Virginity::VcardCleaning::X_IRMC_LUID

Constants inherited from DirectoryInformation

DirectoryInformation::LF

Constants included from Virginity::Vcard21::Reader

Virginity::Vcard21::Reader::LATIN1, Virginity::Vcard21::Reader::UNSUPPORTED_CONTROL_CHARS

Constants included from Query

Query::COLON, Query::COMMA, Query::EQUALS, Query::GROUP, Query::KEY, Query::NAME, Query::PARAM_NAME, Query::PARAM_VALUE, Query::SEMICOLON

Instance Attribute Summary

Attributes inherited from DirectoryInformation

#lines

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Encodings

#binary?, #to_ascii, #to_binary, #to_default, #to_default!, #verify_utf8ness

Methods included from Patching

#patch!

Methods included from VcardCategories

#add_category, #category_values, #in_category?, #remove_category, #tag, #tags, #tags=

Methods included from VcardCleaning

#clean!, #clean_adrs!, #clean_categories!, #clean_dates!, #clean_multivalue_fields!, #clean_name!, #clean_orgs!, #clean_same_value_fields!, #clean_version!, #convert_custom_osx_field_to_param!, #convert_xabadrs_to_param!, #convert_xablabels_to_param!, #max_one_name!, #remove_duplicate_lines!, #remove_empty_fields!, #remove_extra_logos!, #remove_extra_photos!, #remove_singleton_groups!, #remove_subset_addresses!, #remove_subsets_of_structured_fields!, #remove_x_abuids!, #remove_x_irmc_luids!, #reset_empty_formatted_name!, #rstrip_text_fields!, #strip_structured_fields!, #unpack_categories!, #unpack_field!, #unpack_list!, #unpack_nicknames!

Methods inherited from DirectoryInformation

#==, #delete, #delete_content_line, #encode, #eql?, #pretty_print, #superset_of?, #unfolded

Methods included from Virginity::Vcard21::Reader

#convert_base64_to_b!, #convert_charsets!, #fix_vcard21_line!, #from_vcard21, #guess_charset!, #guess_charset_for_part!, #line21_parts, #lines_from_vcard21, #read_21_line, #reencode_quoted_printable!

Methods included from Query

decode_query, #find_first, #first_match, #line_matches_query?, #lines_with_name, params, #query, #where

Constructor Details

#initialize(lines = EMPTY, options = {}) ⇒ Vcard

Returns a new instance of Vcard.



37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/virginity/vcard.rb', line 37

def initialize(lines = EMPTY, options = {})
  if lines.is_a? Array
    @lines = lines.map {|line| line.to_field }
  else # it's expected to be a String
    verify_utf8ness lines
    @lines = LineFolding::unfold_and_split(lines.lstrip).map { |line| Field.parse(line) }
  end
  raise InvalidVcard, "missing BEGIN:VCARD" unless @lines.first.name =~ Field::BEGIN_REGEX
  raise InvalidVcard, "missing END:VCARD" unless @lines.last.name =~ Field::END_REGEX
  raise InvalidVcard, "missing BEGIN:VCARD" unless @lines.first.raw_value =~ VCARD_REGEX
  raise InvalidVcard, "missing END:VCARD" unless @lines.last.raw_value =~ VCARD_REGEX
rescue => error
  raise InvalidVcard, error.message
end

Class Method Details

.diff(old, new) ⇒ Object



30
31
32
# File 'lib/virginity/vcard.rb', line 30

def self.diff(old, new)
  Patching::diff(old, new)
end

.emptyObject

the empty vcard



53
54
55
# File 'lib/virginity/vcard.rb', line 53

def self.empty
  new(LineFolding::unfold_and_split(EMPTY.lstrip).map { |line| Field.parse(line) })
end

.fields_from_broken_vcard(vcard_as_string) ⇒ Object



169
170
171
172
173
174
175
176
177
178
179
# File 'lib/virginity/fixes.rb', line 169

def self.fields_from_broken_vcard(vcard_as_string)
  lines = Virginity::LineFolding::unfold_and_split(vcard_as_string.lstrip).map do |line|
    begin
      # if it is not valid 3.0, could it be a 2.1-line?
      Virginity::Field.parse(line)
    rescue
      group, name, params, value = DirectoryInformation::line21_parts(line)
      Virginity::Field[name].new(name, value, params, group, :no_deep_copy => true)
    end
  end
end

.fix_and_clean(vcard_as_string) ⇒ Object

NB. this only works for vCard 3.0, not for 2.1!



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/virginity/fixes.rb', line 192

def self.fix_and_clean(vcard_as_string)
  fixes ||= []
  # puts vcard_as_string
  # puts "fixes => #{fixes.inspect}"
  lines = fields_from_broken_vcard(vcard_as_string)
  v = Virginity::Vcard.new(lines)
  v.super_clean!
  valid_utf8?(v)
  v
rescue EncodingError
  #puts e.class, e
  if !fixes.include?(:faulty_qp)
    newcard = Fixes::fix_faulty_qp_chars(vcard_as_string)
    fixes << :faulty_qp
  elsif !fixes.include?(:guess_latin1)
    newcard = Fixes::guess_latin1(lines)
    fixes << :guess_latin1
  else
    File.open("illegal_sequence_#{vcard_as_string.hash}.vcf", "wb") do |f|
      f.puts Virginity::Vcard.new(vcard_as_string).super_clean!
    end
    raise
  end
  vcard_as_string = newcard
  retry
rescue Virginity::InvalidEncoding, Virginity::Vcard21::ParseError => e
  if !fixes.include?(:fix_folding)
    vcard_as_string = Fixes::unfold_wrongly_folded_lines(vcard_as_string)
    fixes << :fix_folding
    retry
  elsif !fixes.include?(:weird_mac_16bit_encoding_that_is_not_utf16) && vcard_as_string.include?("\000")
    vcard_as_string.gsub!("\000", "")
    fixes << :weird_mac_16bit_encoding_that_is_not_utf16
    retry
  else
    raise
  end
end

.from_vcard(vcf, options = {}) ⇒ Object



77
78
79
# File 'lib/virginity/vcard.rb', line 77

def self.from_vcard(vcf, options = {})
  parse(vcf, options)
end

.from_vcard21(vcf, options = {}) ⇒ Object

import vcard21 and convert it to 3.0



58
59
60
61
62
63
64
# File 'lib/virginity/vcard.rb', line 58

def self.from_vcard21(vcf, options = {})
  verify_utf8ness vcf
  vcard = new(lines_from_vcard21(vcf, options), options)
  vcard.clean_version!
rescue => error
  raise InvalidVcard, error.message
end

.list(vcf, options = {}) ⇒ Object

returns an array of Vcards for a string of concatenated vcards



103
104
105
106
107
# File 'lib/virginity/vcard.rb', line 103

def self.list(vcf, options = {})
  vcards_in_list(vcf).map do |v|
    from_vcard(v, options)
  end
end

.load_all_from(filename, options = {}) ⇒ Object



81
82
83
# File 'lib/virginity/vcard.rb', line 81

def self.load_all_from(filename, options = {})
  list(File.read(filename), options)
end

.parse(vcf, options = {}) ⇒ Object



67
68
69
70
71
72
73
74
75
# File 'lib/virginity/vcard.rb', line 67

def self.parse(vcf, options = {})
  vcf = vcf.to_s
  verify_utf8ness vcf
  if vcf =~ VERSION21
    from_vcard21(vcf, options)
  else
    new(vcf, options)
  end
end

.valid_utf8?(v) ⇒ Boolean

Returns:

  • (Boolean)


181
182
183
184
185
186
187
188
189
# File 'lib/virginity/fixes.rb', line 181

def self.valid_utf8?(v)
  if v.to_s.dup.force_encoding(Encoding::UTF_8).valid_encoding?
    true
  else
    false
  end
rescue EncodingError
  return false
end

.vcards_in_list(vcf) ⇒ Object

split a given string of concatenated vcards to an array containing one vcard per element



87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/virginity/vcard.rb', line 87

def self.vcards_in_list(vcf)
  s = StringScanner.new(vcf)
  array = []
  while !s.eos?
    if v = s.scan_until(END_VCARD)
      v.lstrip!
      array << v
    else
      puts s.peek(100)
      break
    end
  end
  array
end

Instance Method Details

#<<(line) ⇒ Object Also known as: push

add a field, returns the vcard



136
137
138
139
# File 'lib/virginity/vcard.rb', line 136

def <<(line)
  add_field(line)
  self
end

#add(name) ⇒ Object



183
184
185
186
187
# File 'lib/virginity/vcard.rb', line 183

def add(name)
  add_field(name + ":") do |field|
    yield field if block_given?
  end
end

#add_email(address = nil) ⇒ Object



189
190
191
192
193
194
# File 'lib/virginity/vcard.rb', line 189

def add_email(address = nil)
  add(EMAIL) do |email|
    email.address = address.to_s
    yield email if block_given?
  end
end

#add_field(line) {|field| ... } ⇒ Object

add a field and return it if a block is given, then the new field is yielded so it can be changed

Yields:

  • (field)

Raises:



126
127
128
129
130
131
132
133
# File 'lib/virginity/vcard.rb', line 126

def add_field(line)
  end_vcard = @lines.pop
  raise InvalidVcard, "there is no last line? ('END:VCARD')" if end_vcard.nil?
  @lines << (field = Field.parse(line))
  @lines << end_vcard
  yield field if block_given?
  field
end

#add_telephone(number = nil) ⇒ Object



196
197
198
199
200
201
# File 'lib/virginity/vcard.rb', line 196

def add_telephone(number = nil)
  add(TEL) do |tel|
    tel.number = number.to_s
    yield tel if block_given?
  end
end

#addressesObject



220
# File 'lib/virginity/vcard.rb', line 220

def addresses; lines_with_name(ADR); end

#anniversariesObject



241
# File 'lib/virginity/vcard.rb', line 241

def anniversaries; lines_with_name(XANNIVERSARY); end

#assimilate_fields_from!(other) ⇒ Object

import all fields except N, FN, and VERSION from other (another Vcard) duplicate fields are deduped



174
175
176
177
178
179
180
181
# File 'lib/virginity/vcard.rb', line 174

def assimilate_fields_from!(other)
  other.fields.each do |field|
    next if SINGLETON_FIELDS.include? field.name.upcase
    push(field)
  end
  clean_same_value_fields!
  self
end

#birthdaysObject



221
# File 'lib/virginity/vcard.rb', line 221

def birthdays; lines_with_name(BDAY); end

#categoriesObject



222
# File 'lib/virginity/vcard.rb', line 222

def categories; lines_with_name(CATEGORIES); end

#custom_im_fieldsObject



234
# File 'lib/virginity/vcard.rb', line 234

def custom_im_fields; @lines.select{|line| line.is_a? Virginity::Vcard::CustomImField}; end

#datesObject



239
# File 'lib/virginity/vcard.rb', line 239

def dates; lines_with_name(XABDATE); end

#deep_copyObject



117
118
119
# File 'lib/virginity/vcard.rb', line 117

def deep_copy
  Marshal::load(Marshal::dump(self))
end

#dir_infoObject



113
114
115
# File 'lib/virginity/vcard.rb', line 113

def dir_info
  DirectoryInformation.new(to_s)
end

#emailsObject



223
# File 'lib/virginity/vcard.rb', line 223

def emails; lines_with_name(EMAIL); end

#imppsObject



224
# File 'lib/virginity/vcard.rb', line 224

def impps; lines_with_name(IMPP); end

#inspectObject



109
110
111
# File 'lib/virginity/vcard.rb', line 109

def inspect
  super.chomp(">") + " name=" + name.to_s.inspect + ">"
end

#logosObject



225
# File 'lib/virginity/vcard.rb', line 225

def logos; lines_with_name(LOGO); end

#nameObject



203
204
205
# File 'lib/virginity/vcard.rb', line 203

def name
  @name ||= NameHandler.new(self)
end

#nicknamesObject



226
# File 'lib/virginity/vcard.rb', line 226

def nicknames; lines_with_name(NICKNAME); end

#notesObject



228
# File 'lib/virginity/vcard.rb', line 228

def notes; lines_with_name(NOTE); end

#organizationsObject Also known as: organisations



229
# File 'lib/virginity/vcard.rb', line 229

def organizations; lines_with_name(ORG); end

#photosObject



231
# File 'lib/virginity/vcard.rb', line 231

def photos; lines_with_name(PHOTO); end


237
# File 'lib/virginity/vcard.rb', line 237

def related_names; lines_with_name(XABRELATEDNAMES); end

#subset_of?(other) ⇒ Boolean

Returns:

  • (Boolean)


152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/virginity/vcard.rb', line 152

def subset_of?(other)
  fields.all? do |field|
    if IGNORE_IN_SUBSET_COMPARISON.include?(field.name)
      true
    else
      # puts "-----\n"
      # puts "#{field} in #{other}?\n"
      other.lines_with_name(field.name).any? do |f|
        begin
          # puts "considering #{f} ==> #{f.raw_value == field.raw_value or field.subset_of?(f)}"
          f.raw_value == field.raw_value or field.subset_of?(f)
        rescue NoMethodError
          false
        end
      end
    end
  end
end

#telephonesObject



227
# File 'lib/virginity/vcard.rb', line 227

def telephones; lines_with_name(TEL); end

#titlesObject



232
# File 'lib/virginity/vcard.rb', line 232

def titles; lines_with_name(TITLE); end

#to_vcard21(options = {}) ⇒ Object



145
146
147
148
# File 'lib/virginity/vcard.rb', line 145

def to_vcard21(options = {})
  line_ending = options[:windows_line_endings] ? CRLF : LF
  fields.map { |field| field.encode21(options) }.join(line_ending)
end

#urlsObject



233
# File 'lib/virginity/vcard.rb', line 233

def urls; lines_with_name(URL); end