3 /// Conversion routines for vcards
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.
29 namespace Barry
{ namespace Sync
{
31 //////////////////////////////////////////////////////////////////////////////
36 void ToLower(std::string
&str
)
39 while( i
< str
.size() ) {
40 str
[i
] = tolower(str
[i
]);
47 //////////////////////////////////////////////////////////////////////////////
62 void vCard::AddAddress(const char *rfc_type
, const Barry::PostalAddress
&address
)
65 vAttrPtr label
= NewAttr("LABEL");
66 AddParam(label
, "TYPE", rfc_type
);
67 AddValue(label
, address
.GetLabel().c_str());
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
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
)
88 vAttrPtr tel
= NewAttr("TEL", phone
.c_str());
93 /// Add phone conditionally, only if phone has data in it
94 void vCard::AddPhoneCond(const char *rfc_type
, const std::string
&phone
)
97 vAttrPtr tel
= NewAttr("TEL", phone
.c_str());
98 AddParam(tel
, "TYPE", rfc_type
);
103 void vCard::ParseAddress(vAttr
&adr
, Barry::PostalAddress
&address
)
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
)
118 std::string value
= cat
.GetValue(i
);
119 while( value
.size() ) {
120 cats
.push_back(value
);
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
;
135 // trace.logf("ToVCard, initial Barry record: %s", oss.str().c_str());
139 SetFormat( b_vformat_new() );
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()));
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
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
);
205 vAttrPtr email
= NewAttr("EMAIL", e
.c_str());
207 AddParam(email
, "TYPE", "internet,pref");
210 AddParam(email
, "TYPE", "internet");
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() ) {
223 vAttrPtr org
= NewAttr("ORG", con
.Company
.c_str()); // Organization name
224 AddValue(org
, ""); // Division name
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()));
234 AddAttr(NewAttr("URL", con
.URL
.c_str()));
235 if( con
.Categories
.size() )
236 AddCategories(con
.Categories
);
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");
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());
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
261 // HomePhone + HomePhone2
262 // WorkPhone + WorkPhone2
263 // Phone + OtherPhone
268 // This class handles the sliding pair logic for a pair of std::strings.
272 std::string
&m_first
;
273 std::string
&m_second
;
274 int m_evolutionSlot1
, m_evolutionSlot2
;
276 static const int DefaultSlot
= 99;
277 SlidingPair(std::string
&first
, std::string
&second
)
280 , m_evolutionSlot1(DefaultSlot
)
281 , m_evolutionSlot2(DefaultSlot
)
285 bool assign(const std::string
&value
, const char *type_str
, int evolutionSlot
)
289 if( strstr(type_str
, "pref") || evolutionSlot
< m_evolutionSlot1
) {
291 m_evolutionSlot2
= m_evolutionSlot1
;
294 m_evolutionSlot1
= evolutionSlot
;
298 else if( evolutionSlot
< m_evolutionSlot2
) {
300 m_evolutionSlot2
= evolutionSlot
;
303 else if( m_first
.size() == 0 ) {
305 m_evolutionSlot1
= evolutionSlot
;
308 else if( m_second
.size() == 0 ) {
310 m_evolutionSlot2
= evolutionSlot
;
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
)
325 // Trace trace("vCard::ToBarry");
326 // trace.logf("ToBarry, working on vcard data: %s", vcard);
331 // store the vCard raw data
334 // create format parser structures
335 SetFormat( b_vformat_new_from_string(vcard
) );
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");
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");
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
);
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
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?
409 const char *type
= stype
.c_str();
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") ||
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",
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
443 if( strstr(type
, "fax") && (strstr(type
, "pref") || con
.Fax
.size() == 0) ) {
444 con
.Fax
= tel
.GetValue();
448 else if( mobile_type
&& (strstr(type
, "pref") || con
.MobilePhone
.size() == 0) ) {
449 con
.MobilePhone
= tel
.GetValue();
453 else if( strstr(type
, "pager") && (strstr(type
, "pref") || con
.Pager
.size() == 0) ) {
454 con
.Pager
= tel
.GetValue();
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") ) {
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");
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());
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"));
515 vAttr photo
= GetAttrObj("PHOTO");
517 std::string sencoding
= photo
.GetAllParams("ENCODING");
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();
534 // We ignore the photo, I don't know decoded !
537 vAttr cat
= GetAttrObj("CATEGORIES");
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
;
561 m_BarryContact
.Clear();
569 }} // namespace Barry::Sync