3 /// Conversion routines for vcards
7 Copyright (C) 2006-2009, 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.
23 #include "environment.h"
25 #include "vformat.h" // comes from opensync, but not a public header yet
33 //////////////////////////////////////////////////////////////////////////////
36 void ToLower(std::string
&str
)
39 while( i
< str
.size() ) {
40 str
[i
] = tolower(str
[i
]);
45 //////////////////////////////////////////////////////////////////////////////
60 void vCard::AddAddress(const char *rfc_type
, const Barry::PostalAddress
&address
)
63 vAttrPtr label
= NewAttr("LABEL");
64 AddParam(label
, "TYPE", rfc_type
);
65 AddValue(label
, address
.GetLabel().c_str());
68 // add breakout address form
69 vAttrPtr adr
= NewAttr("ADR"); // RFC 2426, 3.2.1
70 AddParam(adr
, "TYPE", rfc_type
);
71 AddValue(adr
, address
.Address3
.c_str()); // PO Box
72 AddValue(adr
, address
.Address2
.c_str()); // Extended address
73 AddValue(adr
, address
.Address1
.c_str()); // Street address
74 AddValue(adr
, address
.City
.c_str()); // Locality (city)
75 AddValue(adr
, address
.Province
.c_str()); // Region (province)
76 AddValue(adr
, address
.PostalCode
.c_str()); // Postal code
77 AddValue(adr
, address
.Country
.c_str()); // Country name
82 void vCard::AddCategories(const Barry::CategoryList
&categories
)
84 if( !categories
.size() )
87 vAttrPtr cat
= NewAttr("CATEGORIES"); // RFC 2426, 3.6.1
88 Barry::CategoryList::const_iterator i
= categories
.begin();
89 for( ; i
< categories
.end(); ++i
) {
90 AddValue(cat
, i
->c_str());
95 /// Add phone conditionally, only if phone has data in it. This version
96 /// does not add a TYPE parameter to the item.
97 void vCard::AddPhoneCond(const std::string
&phone
)
100 vAttrPtr tel
= NewAttr("TEL", phone
.c_str());
105 /// Add phone conditionally, only if phone has data in it
106 void vCard::AddPhoneCond(const char *rfc_type
, const std::string
&phone
)
109 vAttrPtr tel
= NewAttr("TEL", phone
.c_str());
110 AddParam(tel
, "TYPE", rfc_type
);
115 void vCard::ParseAddress(vAttr
&adr
, Barry::PostalAddress
&address
)
118 address
.Address3
= adr
.GetValue(0); // PO Box
119 address
.Address2
= adr
.GetValue(1); // Extended address
120 address
.Address1
= adr
.GetValue(2); // Street address
121 address
.City
= adr
.GetValue(3); // Locality (city)
122 address
.Province
= adr
.GetValue(4); // Region (province)
123 address
.PostalCode
= adr
.GetValue(5); // Postal code
124 address
.Country
= adr
.GetValue(6); // Country name
127 void vCard::ParseCategories(vAttr
&cat
, Barry::CategoryList
&cats
)
130 std::string value
= cat
.GetValue(i
);
131 while( value
.size() ) {
132 cats
.push_back(value
);
134 value
= cat
.GetValue(i
);
140 // Main conversion routine for converting from Barry::Contact to
141 // a vCard string of data.
142 const std::string
& vCard::ToVCard(const Barry::Contact
&con
)
144 Trace
trace("vCard::ToVCard");
145 std::ostringstream oss
;
147 trace
.logf("ToVCard, initial Barry record: %s", oss
.str().c_str());
151 SetFormat( b_vformat_new() );
153 throw ConvertError("resource error allocating vformat");
155 // store the Barry object we're working with
156 m_BarryContact
= con
;
159 // begin building vCard data
162 AddAttr(NewAttr("PRODID", "-//OpenSync//NONSGML Barry Contact Record//EN"));
164 std::string fullname
= con
.GetFullName();
165 if( fullname
.size() ) {
166 AddAttr(NewAttr("FN", fullname
.c_str()));
170 // RFC 2426, 3.1.1 states that FN MUST be present in the
171 // vcard object. Unfortunately, the Blackberry doesn't
172 // require a name, only a name or company name.
174 // In this case we do nothing, and generate an invalid
175 // vcard, since if we try to fix our output here, we'll
176 // likely end up with duplicated company names in the
177 // Blackberry record after a few syncs.
181 if( con
.FirstName
.size() || con
.LastName
.size() ) {
182 vAttrPtr name
= NewAttr("N"); // RFC 2426, 3.1.2
183 AddValue(name
, con
.LastName
.c_str()); // Family Name
184 AddValue(name
, con
.FirstName
.c_str()); // Given Name
185 AddValue(name
, ""); // Additional Names
186 AddValue(name
, con
.Prefix
.c_str()); // Honorific Prefixes
187 AddValue(name
, ""); // Honorific Suffixes
191 if( con
.WorkAddress
.HasData() )
192 AddAddress("work", con
.WorkAddress
);
193 if( con
.HomeAddress
.HasData() )
194 AddAddress("home", con
.HomeAddress
);
196 // add all applicable phone numbers... there can be multiple
197 // TEL fields, even with the same TYPE value... therefore, the
198 // second TEL field with a TYPE=work, will be stored in WorkPhone2
199 AddPhoneCond("pref", con
.Phone
);
200 AddPhoneCond("fax", con
.Fax
);
201 AddPhoneCond("work", con
.WorkPhone
);
202 AddPhoneCond("work", con
.WorkPhone2
);
203 AddPhoneCond("home", con
.HomePhone
);
204 AddPhoneCond("home", con
.HomePhone2
);
205 AddPhoneCond("cell", con
.MobilePhone
);
206 AddPhoneCond("pager", con
.Pager
);
207 AddPhoneCond(con
.OtherPhone
);
209 // add all email addresses, marking first one as "pref"
210 Barry::Contact::EmailList::const_iterator eai
= con
.EmailAddresses
.begin();
211 for( unsigned int i
= 0; eai
!= con
.EmailAddresses
.end(); ++eai
, ++i
) {
212 const std::string
& e
= con
.GetEmail(i
);
214 vAttrPtr email
= NewAttr("EMAIL", e
.c_str());
216 AddParam(email
, "TYPE", "internet,pref");
219 AddParam(email
, "TYPE", "internet");
225 if( con
.JobTitle
.size() ) {
226 AddAttr(NewAttr("TITLE", con
.JobTitle
.c_str()));
227 AddAttr(NewAttr("ROLE", con
.JobTitle
.c_str()));
230 // Image not supported, since vformat routines probably don't
231 // support binary VCARD fields....
233 if( con
.Company
.size() ) {
235 vAttrPtr org
= NewAttr("ORG", con
.Company
.c_str()); // Organization name
236 AddValue(org
, ""); // Division name
240 if( con
.Birthday
.HasData() )
241 AddAttr(NewAttr("BDAY", con
.Birthday
.ToYYYYMMDD().c_str()));
243 if( con
.Notes
.size() )
244 AddAttr(NewAttr("NOTE", con
.Notes
.c_str()));
246 AddAttr(NewAttr("URL", con
.URL
.c_str()));
247 if( con
.Categories
.size() )
248 AddCategories(con
.Categories
);
251 if (con
.Image
.size()) {
252 vAttrPtr photo
= NewAttr("PHOTO");
253 AddEncodedValue(photo
, VF_ENCODING_BASE64
, con
.Image
.c_str(), con
.Image
.size());
254 AddParam(photo
, "ENCODING", "BASE64");
258 // generate the raw VCARD data
259 m_gCardData
= b_vformat_to_string(Format(), VFORMAT_CARD_30
);
260 m_vCardData
= m_gCardData
;
262 trace
.logf("ToVCard, resulting vcard data: %s", m_vCardData
.c_str());
266 // Main conversion routine for converting from vCard data string
267 // to a Barry::Contact object.
268 const Barry::Contact
& vCard::ToBarry(const char *vcard
, uint32_t RecordId
)
272 Trace
trace("vCard::ToBarry");
273 trace
.logf("ToBarry, working on vcard data: %s", vcard
);
278 // store the vCard raw data
281 // create format parser structures
282 SetFormat( b_vformat_new_from_string(vcard
) );
284 throw ConvertError("resource error allocating vformat");
288 // Parse the vcard data
291 Barry::Contact
&con
= m_BarryContact
;
292 con
.SetIds(Barry::Contact::GetDefaultRecType(), RecordId
);
294 vAttr name
= GetAttrObj("N");
297 con
.LastName
= name
.GetValue(0); // Family Name
298 con
.FirstName
= name
.GetValue(1); // Given Name
299 con
.Prefix
= name
.GetValue(3); // Honorific Prefixes
302 vAttr adr
= GetAttrObj("ADR");
303 for( int i
= 0; adr
.Get(); adr
= GetAttrObj("ADR", ++i
) )
305 std::string type
= adr
.GetAllParams("TYPE");
308 // do not use "else" here, since TYPE can have multiple keys
309 if( strstr(type
.c_str(), "work") )
310 ParseAddress(adr
, con
.WorkAddress
);
311 if( strstr(type
.c_str(), "home") )
312 ParseAddress(adr
, con
.HomeAddress
);
316 // add all applicable phone numbers... there can be multiple
317 // TEL fields, even with the same TYPE value... therefore, the
318 // second TEL field with a TYPE=work, will be stored in WorkPhone2
319 vAttr tel
= GetAttrObj("TEL");
320 for( int i
= 0; tel
.Get(); tel
= GetAttrObj("TEL", ++i
) )
322 // grab all parameter values for this param name
323 std::string stype
= tel
.GetAllParams("TYPE");
325 // turn to lower case for comparison
326 // FIXME - is this i18n safe?
330 const char *type
= stype
.c_str();
333 // Do not use "else" here, since TYPE can have multiple keys
334 // Instead, only fill in a field if it is empty
335 if( strstr(type
, "pref") && con
.Phone
.size() == 0 ) {
336 con
.Phone
= tel
.GetValue();
340 if( strstr(type
, "fax") && con
.Fax
.size() == 0 ) {
341 con
.Fax
= tel
.GetValue();
345 if( strstr(type
, "work") ) {
346 if( con
.WorkPhone
.size() == 0 ) {
347 con
.WorkPhone
= tel
.GetValue();
350 else if( con
.WorkPhone2
.size() == 0 ) {
351 con
.WorkPhone2
= tel
.GetValue();
356 if( strstr(type
, "home") ) {
357 if( con
.HomePhone
.size() == 0 ) {
358 con
.HomePhone
= tel
.GetValue();
361 else if( con
.HomePhone2
.size() == 0 ) {
362 con
.HomePhone2
= tel
.GetValue();
367 if( strstr(type
, "cell") && con
.MobilePhone
.size() == 0 ) {
368 con
.MobilePhone
= tel
.GetValue();
372 if( (strstr(type
, "pager") || strstr(type
, "msg")) && con
.Pager
.size() == 0 ) {
373 con
.Pager
= tel
.GetValue();
377 // if this value has not been claimed by any of the
378 // cases above, claim it now as "OtherPhone"
379 if( !used
&& con
.OtherPhone
.size() == 0 ) {
380 con
.OtherPhone
= tel
.GetValue();
383 // Final check: If Phone is blank, and OtherPhone has a value, swap.
384 if( con
.OtherPhone
.size() && !con
.Phone
.size() ) {
385 con
.Phone
.swap(con
.OtherPhone
);
388 // scan for all email addresses... append addresses to the
389 // list by default, but prepend if its type is set to "pref"
390 // i.e. we want the preferred email address first
391 vAttr email
= GetAttrObj("EMAIL");
392 for( int i
= 0; email
.Get(); email
= GetAttrObj("EMAIL", ++i
) )
394 std::string type
= email
.GetAllParams("TYPE");
397 bool of_interest
= (i
== 0 || strstr(type
.c_str(), "pref"));
398 bool x400
= strstr(type
.c_str(), "x400");
400 if( of_interest
&& !x400
) {
401 con
.EmailAddresses
.insert(con
.EmailAddresses
.begin(), email
.GetValue());
404 con
.EmailAddresses
.push_back( email
.GetValue() );
408 // figure out which company title we want to keep...
409 // favour the TITLE field, but if it's empty, use ROLE
410 con
.JobTitle
= GetAttr("TITLE");
411 if( !con
.JobTitle
.size() )
412 con
.JobTitle
= GetAttr("ROLE");
414 con
.Company
= GetAttr("ORG");
415 con
.Notes
= GetAttr("NOTE");
416 con
.URL
= GetAttr("URL");
417 if( GetAttr("BDAY").size() && !con
.Birthday
.FromYYYYMMDD( GetAttr("BDAY") ) )
418 throw ConvertError("Unable to parse BDAY field");
421 vAttr photo
= GetAttrObj("PHOTO");
423 std::string sencoding
= photo
.GetAllParams("ENCODING");
427 const char *encoding
= sencoding
.c_str();
429 if (strstr(encoding
, "quoted-printable")) {
430 photo
.Get()->encoding
= VF_ENCODING_QP
;
432 con
.Image
= photo
.GetDecodedValue();
434 else if (strstr(encoding
, "b")) {
435 photo
.Get()->encoding
= VF_ENCODING_BASE64
;
437 con
.Image
= photo
.GetDecodedValue();
440 // We ignore the photo, I don't know decoded !
443 vAttr cat
= GetAttrObj("CATEGORIES");
445 ParseCategories(cat
, con
.Categories
);
447 // Last sanity check: Blackberry requires that at least
448 // name or Company has data.
449 if( !con
.GetFullName().size() && !con
.Company
.size() )
450 throw ConvertError("FN and ORG fields both blank in VCARD data");
452 return m_BarryContact
;
455 // Transfers ownership of m_gCardData to the caller.
456 char* vCard::ExtractVCard()
458 char *ret
= m_gCardData
;
467 m_BarryContact
.Clear();
477 //////////////////////////////////////////////////////////////////////////////
480 VCardConverter::VCardConverter()
485 VCardConverter::VCardConverter(uint32_t newRecordId
)
487 m_RecordId(newRecordId
)
491 VCardConverter::~VCardConverter()
497 // Transfers ownership of m_Data to the caller
498 char* VCardConverter::ExtractData()
500 Trace
trace("VCardConverter::ExtractData");
506 bool VCardConverter::ParseData(const char *data
)
508 Trace
trace("VCardConverter::ParseData");
513 m_Contact
= vcard
.ToBarry(data
, m_RecordId
);
516 catch( vCard::ConvertError
&ce
) {
517 trace
.logf("ERROR: vCard::ConvertError exception: %s", ce
.what());
524 // Barry storage operator
525 void VCardConverter::operator()(const Barry::Contact
&rec
)
527 Trace
trace("VCardConverter::operator()");
529 // Delete data if some already exists
539 m_Data
= vcard
.ExtractVCard();
542 catch( vCard::ConvertError
&ce
) {
543 trace
.logf("ERROR: vCard::ConvertError exception: %s", ce
.what());
547 // Barry builder operator
548 bool VCardConverter::operator()(Barry::Contact
&rec
, unsigned int dbId
)
550 Trace
trace("VCardConverter::builder operator()");
556 // Handles calling of the Barry::Controller to fetch a specific
557 // record, indicated by index (into the RecordStateTable).
558 // Returns a g_malloc'd string of data containing the vcard30
559 // data. It is the responsibility of the caller to free it.
560 // This is intended to be passed into the GetChanges() function.
561 char* VCardConverter::GetRecordData(BarryEnvironment
*env
, unsigned int dbId
,
562 Barry::RecordStateTable::IndexType index
)
564 Trace
trace("VCardConverter::GetRecordData()");
566 using namespace Barry
;
568 VCardConverter contact2vcard
;
569 RecordParser
<Contact
, VCardConverter
> parser(contact2vcard
);
570 env
->m_pDesktop
->GetRecord(dbId
, index
, parser
);
571 return contact2vcard
.ExtractData();
574 bool VCardConverter::CommitRecordData(BarryEnvironment
*env
, unsigned int dbId
,
575 Barry::RecordStateTable::IndexType StateIndex
, uint32_t recordId
,
576 const char *data
, bool add
, std::string
&errmsg
)
578 Trace
trace("VCardConverter::CommitRecordData()");
580 uint32_t newRecordId
;
582 // use given id if possible
583 if( recordId
&& !env
->m_ContactsSync
.m_Table
.GetIndex(recordId
) ) {
584 // recordId is unique and non-zero
585 newRecordId
= recordId
;
588 trace
.log("Can't use recommended recordId, generating new one.");
589 newRecordId
= env
->m_ContactsSync
.m_Table
.MakeNewRecordId();
593 newRecordId
= env
->m_ContactsSync
.m_Table
.StateMap
[StateIndex
].RecordId
;
595 trace
.logf("newRecordId: %lu", newRecordId
);
597 VCardConverter
convert(newRecordId
);
598 if( !convert
.ParseData(data
) ) {
599 std::ostringstream oss
;
600 oss
<< "unable to parse change data for new RecordId: "
601 << newRecordId
<< " data: " << data
;
603 trace
.log(errmsg
.c_str());
607 Barry::RecordBuilder
<Barry::Contact
, VCardConverter
> builder(convert
);
611 trace
.log("adding record");
612 env
->m_pDesktop
->AddRecord(dbId
, builder
);
615 trace
.log("setting record");
616 env
->m_pDesktop
->SetRecord(dbId
, StateIndex
, builder
);
617 trace
.log("clearing dirty flag");
618 env
->m_pDesktop
->ClearDirty(dbId
, StateIndex
);
621 catch (Barry::Error
&e
) {
622 trace
.logf("ERROR: VCardConverter::CommitRecordData - Format error");