menu: added new Keywords tag to .desktop files
[barry.git] / src / vcard.cc
blobd30da518b358e5ae7171e65fbe0a9f1ffd6cf8ee
1 ///
2 /// \file vcard.cc
3 /// Conversion routines for vcards
4 ///
6 /*
7 Copyright (C) 2006-2013, Net Direct Inc. (http://www.netdirect.ca/)
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18 See the GNU General Public License in the COPYING file at the
19 root directory of this project for more details.
22 #include "i18n.h"
23 #include "vcard.h"
24 #include <string.h>
25 #include <stdlib.h>
26 #include <ctype.h>
27 #include <sstream>
29 namespace Barry { namespace Sync {
31 //////////////////////////////////////////////////////////////////////////////
32 // Utility functions
34 namespace {
36 void ToLower(std::string &str)
38 size_t i = 0;
39 while( i < str.size() ) {
40 str[i] = tolower(str[i]);
41 i++;
47 //////////////////////////////////////////////////////////////////////////////
48 // vCard
50 vCard::vCard()
51 : m_gCardData(0)
55 vCard::~vCard()
57 if( m_gCardData ) {
58 g_free(m_gCardData);
62 void vCard::AddAddress(const char *rfc_type, const Barry::PostalAddress &address)
64 // add label first
65 vAttrPtr label = NewAttr("LABEL");
66 AddParam(label, "TYPE", rfc_type);
67 AddValue(label, address.GetLabel().c_str());
68 AddAttr(label);
70 // add breakout address form
71 vAttrPtr adr = NewAttr("ADR"); // RFC 2426, 3.2.1
72 AddParam(adr, "TYPE", rfc_type);
73 AddValue(adr, address.Address3.c_str()); // PO Box
74 AddValue(adr, address.Address2.c_str()); // Extended address
75 AddValue(adr, address.Address1.c_str()); // Street address
76 AddValue(adr, address.City.c_str()); // Locality (city)
77 AddValue(adr, address.Province.c_str()); // Region (province)
78 AddValue(adr, address.PostalCode.c_str()); // Postal code
79 AddValue(adr, address.Country.c_str()); // Country name
80 AddAttr(adr);
83 /// Add phone conditionally, only if phone has data in it. This version
84 /// does not add a TYPE parameter to the item.
85 void vCard::AddPhoneCond(const std::string &phone)
87 if( phone.size() ) {
88 vAttrPtr tel = NewAttr("TEL", phone.c_str());
89 AddAttr(tel);
93 /// Add phone conditionally, only if phone has data in it
94 void vCard::AddPhoneCond(const char *rfc_type, const std::string &phone)
96 if( phone.size() ) {
97 vAttrPtr tel = NewAttr("TEL", phone.c_str());
98 AddParam(tel, "TYPE", rfc_type);
99 AddAttr(tel);
103 void vCard::ParseAddress(vAttr &adr, Barry::PostalAddress &address)
105 // RFC 2426, 3.2.1
106 address.Address3 = adr.GetValue(0); // PO Box
107 address.Address2 = adr.GetValue(1); // Extended address
108 address.Address1 = adr.GetValue(2); // Street address
109 address.City = adr.GetValue(3); // Locality (city)
110 address.Province = adr.GetValue(4); // Region (province)
111 address.PostalCode = adr.GetValue(5); // Postal code
112 address.Country = adr.GetValue(6); // Country name
115 void vCard::ParseCategories(vAttr &cat, Barry::CategoryList &cats)
117 int i = 0;
118 std::string value = cat.GetValue(i);
119 while( value.size() ) {
120 cats.push_back(value);
121 i++;
122 value = cat.GetValue(i);
128 // Main conversion routine for converting from Barry::Contact to
129 // a vCard string of data.
130 const std::string& vCard::ToVCard(const Barry::Contact &con)
132 // Trace trace("vCard::ToVCard");
133 std::ostringstream oss;
134 con.Dump(oss);
135 // trace.logf("ToVCard, initial Barry record: %s", oss.str().c_str());
137 // start fresh
138 Clear();
139 SetFormat( b_vformat_new() );
140 if( !Format() )
141 throw ConvertError(_("resource error allocating vformat"));
143 // store the Barry object we're working with
144 m_BarryContact = con;
147 // begin building vCard data
150 AddAttr(NewAttr("PRODID", "-//OpenSync//NONSGML Barry Contact Record//EN"));
152 std::string fullname = con.GetFullName();
153 if( fullname.size() ) {
154 AddAttr(NewAttr("FN", fullname.c_str()));
156 else {
158 // RFC 2426, 3.1.1 states that FN MUST be present in the
159 // vcard object. Unfortunately, the Blackberry doesn't
160 // require a name, only a name or company name.
162 // In this case we do nothing, and generate an invalid
163 // vcard, since if we try to fix our output here, we'll
164 // likely end up with duplicated company names in the
165 // Blackberry record after a few syncs.
169 if( con.FirstName.size() || con.LastName.size() ) {
170 vAttrPtr name = NewAttr("N"); // RFC 2426, 3.1.2
171 AddValue(name, con.LastName.c_str()); // Family Name
172 AddValue(name, con.FirstName.c_str()); // Given Name
173 AddValue(name, ""); // Additional Names
174 AddValue(name, con.Prefix.c_str()); // Honorific Prefixes
175 AddValue(name, ""); // Honorific Suffixes
176 AddAttr(name);
179 if( con.Nickname.size() )
180 AddAttr(NewAttr("NICKNAME", con.Nickname.c_str()));
182 if( con.WorkAddress.HasData() )
183 AddAddress("work", con.WorkAddress);
184 if( con.HomeAddress.HasData() )
185 AddAddress("home", con.HomeAddress);
187 // add all applicable phone numbers... there can be multiple
188 // TEL fields, even with the same TYPE value... therefore, the
189 // second TEL field with a TYPE=work, will be stored in WorkPhone2
190 AddPhoneCond("voice,pref", con.Phone);
191 AddPhoneCond("fax", con.Fax);
192 AddPhoneCond("voice,work", con.WorkPhone);
193 AddPhoneCond("voice,work", con.WorkPhone2);
194 AddPhoneCond("voice,home", con.HomePhone);
195 AddPhoneCond("voice,home", con.HomePhone2);
196 AddPhoneCond("msg,cell", con.MobilePhone);
197 AddPhoneCond("msg,pager", con.Pager);
198 AddPhoneCond("voice", con.OtherPhone);
200 // add all email addresses, marking first one as "pref"
201 Barry::Contact::EmailList::const_iterator eai = con.EmailAddresses.begin();
202 for( unsigned int i = 0; eai != con.EmailAddresses.end(); ++eai, ++i ) {
203 const std::string& e = con.GetEmail(i);
204 if( e.size() ) {
205 vAttrPtr email = NewAttr("EMAIL", e.c_str());
206 if( i == 0 ) {
207 AddParam(email, "TYPE", "internet,pref");
209 else {
210 AddParam(email, "TYPE", "internet");
212 AddAttr(email);
216 if( con.JobTitle.size() ) {
217 AddAttr(NewAttr("TITLE", con.JobTitle.c_str()));
218 AddAttr(NewAttr("ROLE", con.JobTitle.c_str()));
221 if( con.Company.size() ) {
222 // RFC 2426, 3.5.5
223 vAttrPtr org = NewAttr("ORG", con.Company.c_str()); // Organization name
224 AddValue(org, ""); // Division name
225 AddAttr(org);
228 if( con.Birthday.HasData() )
229 AddAttr(NewAttr("BDAY", con.Birthday.ToYYYYMMDD().c_str()));
231 if( con.Notes.size() )
232 AddAttr(NewAttr("NOTE", con.Notes.c_str()));
233 if( con.URL.size() )
234 AddAttr(NewAttr("URL", con.URL.c_str()));
235 if( con.Categories.size() )
236 AddCategories(con.Categories);
238 // Image / Photo
239 if (con.Image.size()) {
240 vAttrPtr photo = NewAttr("PHOTO");
241 AddEncodedValue(photo, VF_ENCODING_BASE64, con.Image.c_str(), con.Image.size());
242 AddParam(photo, "ENCODING", "BASE64");
243 AddAttr(photo);
246 // generate the raw VCARD data
247 m_gCardData = b_vformat_to_string(Format(), VFORMAT_CARD_30);
248 m_vCardData = m_gCardData;
250 // trace.logf("ToVCard, resulting vcard data: %s", m_vCardData.c_str());
251 return m_vCardData;
255 // NOTE:
256 // Treat the following pairs of variables like
257 // sliding buffers, where higher priority values
258 // can push existings values from 1 to 2, or from
259 // 2 to oblivion:
261 // HomePhone + HomePhone2
262 // WorkPhone + WorkPhone2
263 // Phone + OtherPhone
266 // class SlidingPair
268 // This class handles the sliding pair logic for a pair of std::strings.
270 class SlidingPair
272 std::string &m_first;
273 std::string &m_second;
274 int m_evolutionSlot1, m_evolutionSlot2;
275 public:
276 static const int DefaultSlot = 99;
277 SlidingPair(std::string &first, std::string &second)
278 : m_first(first)
279 , m_second(second)
280 , m_evolutionSlot1(DefaultSlot)
281 , m_evolutionSlot2(DefaultSlot)
285 bool assign(const std::string &value, const char *type_str, int evolutionSlot)
287 bool used = false;
289 if( strstr(type_str, "pref") || evolutionSlot < m_evolutionSlot1 ) {
290 m_second = m_first;
291 m_evolutionSlot2 = m_evolutionSlot1;
293 m_first = value;
294 m_evolutionSlot1 = evolutionSlot;
296 used = true;
298 else if( evolutionSlot < m_evolutionSlot2 ) {
299 m_second = value;
300 m_evolutionSlot2 = evolutionSlot;
301 used = true;
303 else if( m_first.size() == 0 ) {
304 m_first = value;
305 m_evolutionSlot1 = evolutionSlot;
306 used = true;
308 else if( m_second.size() == 0 ) {
309 m_second = value;
310 m_evolutionSlot2 = evolutionSlot;
311 used = true;
314 return used;
319 // Main conversion routine for converting from vCard data string
320 // to a Barry::Contact object.
321 const Barry::Contact& vCard::ToBarry(const char *vcard, uint32_t RecordId)
323 using namespace std;
325 // Trace trace("vCard::ToBarry");
326 // trace.logf("ToBarry, working on vcard data: %s", vcard);
328 // start fresh
329 Clear();
331 // store the vCard raw data
332 m_vCardData = vcard;
334 // create format parser structures
335 SetFormat( b_vformat_new_from_string(vcard) );
336 if( !Format() )
337 throw ConvertError(_("resource error allocating vformat"));
341 // Parse the vcard data
344 Barry::Contact &con = m_BarryContact;
345 con.SetIds(Barry::Contact::GetDefaultRecType(), RecordId);
347 vAttr name = GetAttrObj("N");
348 if( name.Get() ) {
349 // RFC 2426, 3.1.2
350 con.LastName = name.GetValue(0); // Family Name
351 con.FirstName = name.GetValue(1); // Given Name
352 con.Prefix = name.GetValue(3); // Honorific Prefixes
355 con.Nickname = GetAttr("NICKNAME");
357 vAttr adr = GetAttrObj("ADR");
358 for( int i = 0; adr.Get(); adr = GetAttrObj("ADR", ++i) )
360 std::string type = adr.GetAllParams("TYPE");
361 ToLower(type);
363 // do not use "else" here, since TYPE can have multiple keys
364 if( strstr(type.c_str(), "work") )
365 ParseAddress(adr, con.WorkAddress);
366 if( strstr(type.c_str(), "home") )
367 ParseAddress(adr, con.HomeAddress);
372 // NOTE:
373 // Treat the following pairs of variables like
374 // sliding buffers, where higher priority values
375 // can push existings values from 1 to 2, or from
376 // 2 to oblivion:
378 // HomePhone + HomePhone2
379 // WorkPhone + WorkPhone2
380 // Phone + OtherPhone
382 SlidingPair HomePair(con.HomePhone, con.HomePhone2);
383 SlidingPair WorkPair(con.WorkPhone, con.WorkPhone2);
384 SlidingPair OtherPair(con.Phone, con.OtherPhone);
386 // add all applicable phone numbers... there can be multiple
387 // TEL fields, even with the same TYPE value... therefore, the
388 // second TEL field with a TYPE=work, will be stored in WorkPhone2
389 vAttr tel = GetAttrObj("TEL");
390 for( int i = 0; tel.Get(); tel = GetAttrObj("TEL", ++i) )
392 // grab all parameter values for this param name
393 std::string stype = tel.GetAllParams("TYPE");
395 // grab evolution-specific parameter... evolution is too
396 // lazy to sort its VCARD output, but instead it does
397 // its own non-standard tagging... so we try to
398 // accommodate it, so Work and Home phone numbers keep
399 // their order if possible
400 int evolutionSlot = atoi(tel.GetAllParams("X-EVOLUTION-UI-SLOT").c_str());
401 if( evolutionSlot == 0 )
402 evolutionSlot = SlidingPair::DefaultSlot;
404 // turn to lower case for comparison
405 // FIXME - is this i18n safe?
406 ToLower(stype);
408 // state
409 const char *type = stype.c_str();
410 bool used = false;
412 // Check for possible TYPE conflicts:
413 // pager can coexist with cell/pcs/car
414 // fax conflicts with cell/pcs/car
415 // fax conflicts with pager
416 bool mobile_type = strstr(type, "cell") ||
417 strstr(type, "pcs") ||
418 strstr(type, "car");
419 bool fax_type = strstr(type, "fax");
420 bool pager_type = strstr(type, "pager");
421 if( fax_type && (mobile_type || pager_type) ) {
422 // conflict found, log and skip
423 // trace.logf("ToBarry: skipping phone number due to TYPE conflict: fax cannot coexist with %s: %s",
424 // mobile_type ? "cell/pcs/car" : "pager",
425 // type);
426 continue;
429 // If phone number has the "pref" flag
430 if( strstr(type, "pref") ) {
431 // Always use cell phone if the "pref" flag is set
432 if( strstr(type, "cell") ) {
433 used = OtherPair.assign(tel.GetValue(), type, evolutionSlot);
435 // Otherwise, the phone has to be "voice" type
436 else if( strstr(type, "voice") && con.Phone.size() == 0 ) {
437 used = OtherPair.assign(tel.GetValue(), type, evolutionSlot);
441 // For each known phone type
442 // Fax :
443 if( strstr(type, "fax") && (strstr(type, "pref") || con.Fax.size() == 0) ) {
444 con.Fax = tel.GetValue();
445 used = true;
447 // Mobile phone :
448 else if( mobile_type && (strstr(type, "pref") || con.MobilePhone.size() == 0) ) {
449 con.MobilePhone = tel.GetValue();
450 used = true;
452 // Pager :
453 else if( strstr(type, "pager") && (strstr(type, "pref") || con.Pager.size() == 0) ) {
454 con.Pager = tel.GetValue();
455 used = true;
457 // Check for any TEL-ignore types, and use other phone field if possible
458 // bbs/video/modem entire TEL ignored by Barry
459 // isdn entire TEL ignored by Barry
460 else if( strstr(type, "bbs") || strstr(type, "video") || strstr(type, "modem") ) {
462 else if( strstr(type, "isdn") ) {
464 // Voice telephone :
465 else {
466 if( strstr(type, "work") ) {
467 used = WorkPair.assign(tel.GetValue(), type, evolutionSlot);
470 if( strstr(type, "home") ) {
471 used = HomePair.assign(tel.GetValue(), type, evolutionSlot);
475 // if this value has not been claimed by any of the
476 // cases above, claim it now as "OtherPhone"
477 if( !used && con.OtherPhone.size() == 0 ) {
478 OtherPair.assign(tel.GetValue(), type, evolutionSlot);
482 // scan for all email addresses... append addresses to the
483 // list by default, but prepend if its type is set to "pref"
484 // i.e. we want the preferred email address first
485 vAttr email = GetAttrObj("EMAIL");
486 for( int i = 0; email.Get(); email = GetAttrObj("EMAIL", ++i) )
488 std::string type = email.GetAllParams("TYPE");
489 ToLower(type);
491 bool of_interest = (i == 0 || strstr(type.c_str(), "pref"));
492 bool x400 = strstr(type.c_str(), "x400");
494 if( of_interest && !x400 ) {
495 con.EmailAddresses.insert(con.EmailAddresses.begin(), email.GetValue());
497 else {
498 con.EmailAddresses.push_back( email.GetValue() );
502 // figure out which company title we want to keep...
503 // favour the TITLE field, but if it's empty, use ROLE
504 con.JobTitle = GetAttr("TITLE");
505 if( !con.JobTitle.size() )
506 con.JobTitle = GetAttr("ROLE");
508 con.Company = GetAttr("ORG");
509 con.Notes = GetAttr("NOTE");
510 con.URL = GetAttr("URL");
511 if( GetAttr("BDAY").size() && !con.Birthday.FromYYYYMMDD( GetAttr("BDAY") ) )
512 throw ConvertError(_("Unable to parse BDAY field"));
514 // Photo vCard ?
515 vAttr photo = GetAttrObj("PHOTO");
516 if (photo.Get()) {
517 std::string sencoding = photo.GetAllParams("ENCODING");
519 ToLower(sencoding);
521 const char *encoding = sencoding.c_str();
523 if (strstr(encoding, "quoted-printable")) {
524 photo.Get()->encoding = VF_ENCODING_QP;
526 con.Image = photo.GetDecodedValue();
528 else if (strstr(encoding, "b")) {
529 photo.Get()->encoding = VF_ENCODING_BASE64;
531 con.Image = photo.GetDecodedValue();
533 // Else
534 // We ignore the photo, I don't know decoded !
537 vAttr cat = GetAttrObj("CATEGORIES");
538 if( cat.Get() )
539 ParseCategories(cat, con.Categories);
541 // Last sanity check: Blackberry requires that at least
542 // name or Company has data.
543 if( !con.GetFullName().size() && !con.Company.size() )
544 throw ConvertError(_("FN and ORG fields both blank in VCARD data"));
546 return m_BarryContact;
549 // Transfers ownership of m_gCardData to the caller.
550 char* vCard::ExtractVCard()
552 char *ret = m_gCardData;
553 m_gCardData = 0;
554 return ret;
557 void vCard::Clear()
559 vBase::Clear();
560 m_vCardData.clear();
561 m_BarryContact.Clear();
563 if( m_gCardData ) {
564 g_free(m_gCardData);
565 m_gCardData = 0;
569 }} // namespace Barry::Sync