lib: fixed parsing of recurring VEVENTS: DAILY and interval support
[barry/progweb.git] / src / r_contact.cc
blob8b3e9584b269e3aeb208636b7648d69a589a4074
1 ///
2 /// \file r_contact.cc
3 /// Blackberry database record parser class for contact records.
4 ///
6 /*
7 Copyright (C) 2005-2012, 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 "r_contact.h"
23 #include "record-internal.h"
24 #include "protocol.h"
25 #include "protostructs.h"
26 #include "data.h"
27 #include "time.h"
28 #include "error.h"
29 #include "endian.h"
30 #include "iconv.h"
31 #include "trim.h"
32 #include <iostream>
33 #include <iomanip>
34 #include <sstream>
35 #include <time.h>
36 #include <stdexcept>
37 #include "ios_state.h"
39 #define __DEBUG_MODE__
40 #include "debug.h"
42 using namespace std;
43 using namespace Barry::Protocol;
45 namespace Barry {
49 ///////////////////////////////////////////////////////////////////////////////
50 // Contact class
52 // Contact field codes
53 #define CFC_EMAIL 1
54 #define CFC_PHONE 2
55 #define CFC_FAX 3
56 #define CFC_WORK_PHONE 6
57 #define CFC_HOME_PHONE 7
58 #define CFC_MOBILE_PHONE 8
59 #define CFC_PAGER 9
60 #define CFC_PIN 10
61 #define CFC_RADIO 14 // 0x0e
62 #define CFC_WORK_PHONE_2 16 // 0x10
63 #define CFC_HOME_PHONE_2 17 // 0x11
64 #define CFC_OTHER_PHONE 18 // 0x12
65 #define CFC_MOBILE_PHONE_2 19 // 0x13
66 #define CFC_HOME_FAX 20 // 0x14
67 #define CFC_NAME 32 // 0x20 used twice, in first/last name order
68 #define CFC_COMPANY 33
69 #define CFC_DEFAULT_COMM_METHOD 34
70 #define CFC_ADDRESS1 35
71 #define CFC_ADDRESS2 36
72 #define CFC_ADDRESS3 37
73 #define CFC_CITY 38
74 #define CFC_PROVINCE 39
75 #define CFC_POSTAL_CODE 40
76 #define CFC_COUNTRY 41
77 #define CFC_TITLE 42 // 0x2a
78 #define CFC_PUBLIC_KEY 43
79 #define CFC_GROUP_FLAG 44
80 #define CFC_GROUP_LINK 52
81 #define CFC_URL 54 // 0x36
82 #define CFC_PREFIX 55 // 0x37
83 #define CFC_CATEGORY 59 // 0x3B
84 #define CFC_HOME_ADDRESS1 61 // 0x3D
85 #define CFC_HOME_ADDRESS2 62 // 0x3E
86 // If the address 3 isn't mapped then it appears
87 // in the same field as address2 with a space
88 #define CFC_HOME_ADDRESS3 63 // 0x3F
89 #define CFC_NOTES 64 // 0x40
90 #define CFC_USER_DEFINED_1 65 // 0x41
91 #define CFC_USER_DEFINED_2 66 // 0x42
92 #define CFC_USER_DEFINED_3 67 // 0x43
93 #define CFC_USER_DEFINED_4 68 // 0x44
94 #define CFC_HOME_CITY 69 // 0x45
95 #define CFC_HOME_PROVINCE 70 // 0x46
96 #define CFC_HOME_POSTAL_CODE 71 // 0x47
97 #define CFC_HOME_COUNTRY 72 // 0x48
98 #define CFC_IMAGE 77 // 0x4D
99 #define CFC_BIRTHDAY 82 // 0x52
100 #define CFC_ANNIVERSARY 83 // 0x53
101 #define CFC_MAYBE_CATEGORYID 84 // 0x54
102 #define CFC_UNIQUEID 85 // 0x55
103 #define CFC_NICKNAME 86 // 0x56
104 #define CFC_INVALID_FIELD 255
106 // Contact code to field table
107 static FieldLink<Contact> ContactFieldLinks[] = {
108 { CFC_NICKNAME, "Nickname", 0,0, &Contact::Nickname, 0, 0, 0, 0, true },
109 { CFC_PHONE, "Phone", 0,0, &Contact::Phone, 0, 0, 0, 0, true },
110 { CFC_FAX, "Fax", "facsimileTelephoneNumber",0, &Contact::Fax, 0, 0, 0, 0, true },
111 { CFC_HOME_FAX, "HomeFax", 0,0, &Contact::HomeFax, 0, 0, 0, 0, true },
112 { CFC_WORK_PHONE, "WorkPhone", "telephoneNumber",0, &Contact::WorkPhone, 0, 0, 0, 0, true },
113 { CFC_HOME_PHONE, "HomePhone", "homePhone",0, &Contact::HomePhone, 0, 0, 0, 0, true },
114 { CFC_MOBILE_PHONE, "MobilePhone","mobile",0, &Contact::MobilePhone, 0, 0, 0, 0, true },
115 { CFC_MOBILE_PHONE_2,"MobilePhone2",0,0, &Contact::MobilePhone2, 0, 0, 0, 0, true },
116 { CFC_PAGER, "Pager", "pager",0, &Contact::Pager, 0, 0, 0, 0, true },
117 { CFC_PIN, "PIN", 0,0, &Contact::PIN, 0, 0, 0, 0, true },
118 { CFC_RADIO, "Radio", 0,0, &Contact::Radio, 0, 0, 0, 0, true },
119 { CFC_WORK_PHONE_2, "WorkPhone2", 0,0, &Contact::WorkPhone2, 0, 0, 0, 0, true },
120 { CFC_HOME_PHONE_2, "HomePhone2", 0,0, &Contact::HomePhone2, 0, 0, 0, 0, true },
121 { CFC_OTHER_PHONE, "OtherPhone", 0,0, &Contact::OtherPhone, 0, 0, 0, 0, true },
122 { CFC_COMPANY, "Company", "o",0, &Contact::Company, 0, 0, 0, 0, true },
123 { CFC_DEFAULT_COMM_METHOD,"DefaultCommMethod",0,0, &Contact::DefaultCommunicationsMethod, 0, 0, 0, 0, true },
124 { CFC_ADDRESS1, "WorkAddress1", 0,0, 0, 0, 0, &Contact::WorkAddress, &PostalAddress::Address1, true },
125 { CFC_ADDRESS2, "WorkAddress2", 0,0, 0, 0, 0, &Contact::WorkAddress, &PostalAddress::Address2, true },
126 { CFC_ADDRESS3, "WorkAddress3", 0,0, 0, 0, 0, &Contact::WorkAddress, &PostalAddress::Address3, true },
127 { CFC_CITY, "WorkCity", "l",0, 0, 0, 0, &Contact::WorkAddress, &PostalAddress::City, true },
128 { CFC_PROVINCE, "WorkProvince", "st",0, 0, 0, 0, &Contact::WorkAddress, &PostalAddress::Province, true },
129 { CFC_POSTAL_CODE, "WorkPostalCode", "postalCode",0, 0, 0, 0, &Contact::WorkAddress, &PostalAddress::PostalCode, true },
130 { CFC_COUNTRY, "WorkCountry", "c", "country", 0, 0, 0, &Contact::WorkAddress, &PostalAddress::Country, true },
131 { CFC_TITLE, "JobTitle", "title",0, &Contact::JobTitle, 0, 0, 0, 0, true },
132 { CFC_PUBLIC_KEY, "PublicKey", 0,0, &Contact::PublicKey, 0, 0, 0, 0, false },
133 { CFC_URL, "URL", 0,0, &Contact::URL, 0, 0, 0, 0, true },
134 { CFC_PREFIX, "Prefix", 0,0, &Contact::Prefix, 0, 0, 0, 0, true },
135 { CFC_HOME_ADDRESS1,"HomeAddress1", 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::Address1, true },
136 { CFC_HOME_ADDRESS2,"HomeAddress2", 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::Address2, true },
137 { CFC_HOME_ADDRESS3,"HomeAddress3", 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::Address3, true },
138 { CFC_NOTES, "Notes", 0,0, &Contact::Notes, 0, 0, 0, 0, true },
139 { CFC_USER_DEFINED_1, "UserDefined1", 0,0, &Contact::UserDefined1, 0, 0, 0, 0, true },
140 { CFC_USER_DEFINED_2, "UserDefined2", 0,0, &Contact::UserDefined2, 0, 0, 0, 0, true },
141 { CFC_USER_DEFINED_3, "UserDefined3", 0,0, &Contact::UserDefined3, 0, 0, 0, 0, true },
142 { CFC_USER_DEFINED_4, "UserDefined4", 0,0, &Contact::UserDefined4, 0, 0, 0, 0, true },
143 { CFC_HOME_CITY, "HomeCity", 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::City, true },
144 { CFC_HOME_PROVINCE,"HomeProvince", 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::Province, true },
145 { CFC_HOME_POSTAL_CODE, "HomePostalCode", 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::PostalCode, true },
146 { CFC_HOME_COUNTRY, "HomeCountry",0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::Country, true },
147 { CFC_IMAGE, "Image", 0,0, &Contact::Image, 0, 0, 0, 0, false },
148 { CFC_INVALID_FIELD,"EndOfList", 0, 0, 0, 0, 0, 0, 0, false }
151 Contact::Contact()
152 : RecType(Contact::GetDefaultRecType()),
153 RecordId(0),
154 m_FirstNameSeen(false)
158 Contact::~Contact()
162 const unsigned char* Contact::ParseField(const unsigned char *begin,
163 const unsigned char *end,
164 const IConverter *ic)
166 const CommonField *field = (const CommonField *) begin;
168 // advance and check size
169 begin += COMMON_FIELD_HEADER_SIZE + btohs(field->size);
170 if( begin > end ) // if begin==end, we are ok
171 return begin;
173 if( !btohs(field->size) ) // if field has no size, something's up
174 return begin;
176 // cycle through the type table
177 for( FieldLink<Contact> *b = ContactFieldLinks;
178 b->type != CFC_INVALID_FIELD;
179 b++ )
181 if( b->type == field->type ) {
182 if( b->strMember ) {
183 std::string &s = this->*(b->strMember);
184 s = ParseFieldString(field);
185 if( b->iconvNeeded && ic )
186 s = ic->FromBB(s);
187 return begin; // done!
189 else if( b->postMember && b->postField ) {
190 std::string &s = (this->*(b->postMember)).*(b->postField);
191 s = ParseFieldString(field);
192 if( b->iconvNeeded && ic )
193 s = ic->FromBB(s);
194 return begin;
196 else {
197 break; // fall through to special handling
202 // if not found in the type table, check for special handling
203 switch( field->type )
205 case CFC_EMAIL: {
206 std::string s = ParseFieldString(field);
207 if( ic )
208 s = ic->FromBB(s);
209 EmailAddresses.push_back( s );
211 return begin;
213 case CFC_NAME: {
214 // can be used multiple times, for first/last names
215 std::string *name;
216 if( FirstName.size() || m_FirstNameSeen ) {
217 // first name already filled, use last name
218 name = &LastName;
219 m_FirstNameSeen = false;
221 else {
222 name = &FirstName;
223 m_FirstNameSeen = true;
226 *name = ParseFieldString(field);
227 if( ic )
228 *name = ic->FromBB(*name);
230 return begin;
232 case CFC_GROUP_LINK:
233 // just add the unique ID to the list
234 GroupLinks.push_back(
235 GroupLink(field->u.link.uniqueId,
236 field->u.link.unknown));
237 return begin;
239 case CFC_GROUP_FLAG:
240 // ignore the group flag... the presense of group link items
241 // behaves as the flag in this class
242 return begin;
244 case CFC_CATEGORY: {
245 std::string catstring = ParseFieldString(field);
246 if( ic )
247 catstring = ic->FromBB(catstring);
248 Categories.CategoryStr2List(catstring);
250 return begin;
252 case CFC_BIRTHDAY: {
253 std::string bstring = ParseFieldString(field);
254 Birthday.FromBBString(bstring);
256 return begin;
258 case CFC_ANNIVERSARY: {
259 std::string astring = ParseFieldString(field);
260 Anniversary.FromBBString(astring);
262 return begin;
264 case CFC_UNIQUEID:
265 // this is a duplicate of the UniqueID that comes from
266 // the envelope part of the protocol... just throw this
267 // away, since when we upload it, we need to use a
268 // consistent UniqueID / RecordID from the API
269 return begin;
272 // if still not handled, add to the Unknowns list
273 UnknownField uf;
274 uf.type = field->type;
275 uf.data.assign((const char*)field->u.raw, btohs(field->size));
276 Unknowns.push_back(uf);
278 // return new pointer for next field
279 return begin;
282 void Contact::ParseHeader(const Data &data, size_t &offset)
284 // no header to parse in Contact records
287 void Contact::ParseFields(const Data &data, size_t &offset, const IConverter *ic)
289 const unsigned char *finish = ParseCommonFields(*this,
290 data.GetData() + offset, data.GetData() + data.GetSize(), ic);
291 offset += finish - (data.GetData() + offset);
294 void Contact::Validate() const
296 if( !GetFullName().size() && !Company.size() ) {
297 throw Barry::ValidationError("A contact record must contain either a First/Last name, or a Company name.");
301 void Contact::BuildHeader(Data &data, size_t &offset) const
303 // no header in Contact records
307 // BuildFields
309 /// Build fields part of record
311 void Contact::BuildFields(Data &data, size_t &offset, const IConverter *ic) const
313 data.Zap();
315 // Sanity check: the Blackberry requires at least a name or
316 // a company name for each address record.
317 if( !GetFullName().size() && !Company.size() )
318 throw BadData("Contact must have name or company name.");
320 // check if this is a group link record, and if so, output
321 // the group flag
322 if( GroupLinks.size() )
323 BuildField(data, offset, CFC_GROUP_FLAG, 'G');
325 // special fields not in type table
326 if( FirstName.size() ) {
327 std::string s = ic ? ic->ToBB(FirstName) : FirstName;
328 BuildField(data, offset, CFC_NAME, s);
330 if( LastName.size() ) {
331 if( !FirstName.size() ) {
332 // order matters with first/last name, and if
333 // last name exists, and first name doesn't,
334 // insert blank first name ahead of it
335 BuildField(data, offset, CFC_NAME, "");
337 BuildField(data, offset, CFC_NAME, ic ? ic->ToBB(LastName) : LastName);
340 // FIXME
341 // // add unknown data
342 // char buffer[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
343 // BuildField(data, offset, 0x54, buffer, 8);
345 // With the BlackBerry Storm, I have to add this entry.
346 // Otherwise the uniqueId of this contact is reseted !
347 // The device seems accept the multiple contact with the same uniqueId,
348 // but the synchronization process uses this uniqueId to identify the contact.
349 // add uniqueId
350 BuildField(data, offset, CFC_UNIQUEID, RecordId);
352 // add all email addresses
353 EmailList::const_iterator eai = EmailAddresses.begin();
354 for( ; eai != EmailAddresses.end(); ++eai ) {
355 if( eai->size() ) {
356 BuildField(data, offset, CFC_EMAIL, ic ? ic->ToBB(*eai) : *eai);
360 // cycle through the type table
361 for( FieldLink<Contact> *b = ContactFieldLinks;
362 b->type != CFC_INVALID_FIELD;
363 b++ )
365 // print only fields with data
366 if( b->strMember ) {
367 const std::string &field = this->*(b->strMember);
368 if( field.size() ) {
369 std::string s = (b->iconvNeeded && ic) ? ic->ToBB(field) : field;
370 BuildField(data, offset, b->type, s);
373 else if( b->postMember && b->postField ) {
374 const std::string &field = (this->*(b->postMember)).*(b->postField);
375 if( field.size() ) {
376 std::string s = (b->iconvNeeded && ic) ? ic->ToBB(field) : field;
377 BuildField(data, offset, b->type, s);
382 // save any group links
383 GroupLinksType::const_iterator
384 gb = GroupLinks.begin(), ge = GroupLinks.end();
385 for( ; gb != ge; gb++ ) {
386 Barry::Protocol::GroupLink link;
387 link.uniqueId = htobl(gb->Link);
388 link.unknown = htobs(gb->Unknown);
389 BuildField(data, offset, CFC_GROUP_LINK, link);
392 // save categories
393 if( Categories.size() ) {
394 string store;
395 Categories.CategoryList2Str(store);
396 BuildField(data, offset, CFC_CATEGORY, ic ? ic->ToBB(store) : store);
399 // save Birthday and Anniversary
400 if( Birthday.HasData() )
401 BuildField(data, offset, CFC_BIRTHDAY, Birthday.ToBBString());
402 if( Anniversary.HasData() )
403 BuildField(data, offset, CFC_ANNIVERSARY, Anniversary.ToBBString());
405 // and finally save unknowns
406 UnknownsType::const_iterator
407 ub = Unknowns.begin(), ue = Unknowns.end();
408 for( ; ub != ue; ub++ ) {
409 BuildField(data, offset, *ub);
412 data.ReleaseBuffer(offset);
415 void Contact::Clear()
417 RecType = GetDefaultRecType();
418 RecordId = 0;
420 EmailAddresses.clear();
421 Phone.clear();
423 Fax.clear();
424 HomeFax.clear();
425 WorkPhone.clear();
426 HomePhone.clear();
427 MobilePhone.clear();
428 MobilePhone2.clear();
429 Pager.clear();
430 PIN.clear();
431 Radio.clear();
432 WorkPhone2.clear();
433 HomePhone2.clear();
434 OtherPhone.clear();
435 FirstName.clear();
436 LastName.clear();
437 Company.clear();
438 DefaultCommunicationsMethod.clear();
439 JobTitle.clear();
440 PublicKey.clear();
441 URL.clear();
442 Prefix.clear();
443 Notes.clear();
444 UserDefined1.clear();
445 UserDefined2.clear();
446 UserDefined3.clear();
447 UserDefined4.clear();
448 Image.clear();
449 Nickname.clear();
451 Birthday.Clear();
452 Anniversary.Clear();
454 WorkAddress.Clear();
455 HomeAddress.Clear();
457 Categories.clear();
459 GroupLinks.clear();
460 Unknowns.clear();
462 m_FirstNameSeen = false;
465 const FieldHandle<Contact>::ListT& Contact::GetFieldHandles()
467 static FieldHandle<Contact>::ListT fhv;
469 if( fhv.size() )
470 return fhv;
472 #undef CONTAINER_OBJECT_NAME
473 #define CONTAINER_OBJECT_NAME fhv
475 #undef RECORD_CLASS_NAME
476 #define RECORD_CLASS_NAME Contact
478 // first number is priority of fields... 0 being most critical fields
479 FHP(RecType, "Record Type Code");
480 FHP(RecordId, "Unique ID");
481 FHP(EmailAddresses, "Email Addresses");
483 FHP(FirstName, "First Name");
484 FHP(LastName, "Last Name");
485 FHL(Company, "Company", CFC_COMPANY, true, "o", 0);
486 FHL(JobTitle, "Job Title", CFC_TITLE, true, "title", 0);
487 FHD(Prefix, "Prefix", CFC_PREFIX, true);
489 FHD(Nickname, "Nickname", CFC_NICKNAME, true);
490 FHD(Phone, "Phone (deprecated)", CFC_PHONE, true);
491 FHL(Fax, "Work Fax", CFC_FAX, true, "facsimileTelephoneNumber", 0);
492 FHD(HomeFax, "Home Fax", CFC_HOME_FAX, true);
493 FHL(WorkPhone, "Work Phone", CFC_WORK_PHONE, true,
494 "telephoneNumber", 0);
495 FHD(WorkPhone2, "Work Phone 2", CFC_WORK_PHONE_2, true);
496 FHL(HomePhone, "Home Phone", CFC_HOME_PHONE, true, "homePhone", 0);
497 FHD(HomePhone2, "Home Phone 2", CFC_HOME_PHONE_2, true);
498 FHL(MobilePhone, "Mobile Phone", CFC_MOBILE_PHONE, true, "mobile", 0);
499 FHD(MobilePhone2, "Mobile Phone 2", CFC_MOBILE_PHONE_2, true);
500 FHD(OtherPhone, "Other Phone", CFC_OTHER_PHONE, true);
501 FHL(Pager, "Pager", CFC_PAGER, true, "pager", 0);
502 FHD(PIN, "PIN", CFC_PIN, true);
503 FHD(Radio, "Radio", CFC_RADIO, true);
504 FHD(DefaultCommunicationsMethod, "Default Communications Method",
505 CFC_DEFAULT_COMM_METHOD, true);
506 FHD(PublicKey, "Public Key", CFC_PUBLIC_KEY, false);
507 FHD(URL, "URL", CFC_URL, true);
508 FHD(Notes, "Notes", CFC_NOTES, true);
509 FHD(UserDefined1, "User Defined Field 1", CFC_USER_DEFINED_1, true);
510 FHD(UserDefined2, "User Defined Field 2", CFC_USER_DEFINED_2, true);
511 FHD(UserDefined3, "User Defined Field 3", CFC_USER_DEFINED_3, true);
512 FHD(UserDefined4, "User Defined Field 4", CFC_USER_DEFINED_4, true);
513 FHD(Image, "Image", CFC_IMAGE, false);
515 FHD(Birthday, "Birthday", CFC_BIRTHDAY, true);
516 FHD(Anniversary, "Anniversary", CFC_ANNIVERSARY, true);
518 FHC(WorkAddress, "Work Address");
519 FHS(WorkAddress, Address1, "Work Address 1",
520 CFC_ADDRESS1, true, 0, 0);
521 FHS(WorkAddress, Address2, "Work Address 2",
522 CFC_ADDRESS2, true, 0, 0);
523 FHS(WorkAddress, Address3, "Work Address 3",
524 CFC_ADDRESS3, true, 0, 0);
525 FHS(WorkAddress, City, "Work City",
526 CFC_CITY, true, "l", 0);
527 FHS(WorkAddress, Province, "Work Province",
528 CFC_PROVINCE, true, "st", 0);
529 FHS(WorkAddress, PostalCode, "Work Postal Code",
530 CFC_POSTAL_CODE, true, "postalCode", 0);
531 FHS(WorkAddress, Country, "Work Country",
532 CFC_COUNTRY, true, "c", "country");
534 FHC(HomeAddress, "Home Address");
535 FHS(HomeAddress, Address1, "Home Address 1",
536 CFC_HOME_ADDRESS1, true, 0, 0);
537 FHS(HomeAddress, Address2, "Home Address 2",
538 CFC_HOME_ADDRESS2, true, 0, 0);
539 FHS(HomeAddress, Address3, "Home Address 3",
540 CFC_HOME_ADDRESS3, true, 0, 0);
541 FHS(HomeAddress, City, "Home City",
542 CFC_HOME_CITY, true, 0, 0);
543 FHS(HomeAddress, Province, "Home Province",
544 CFC_HOME_PROVINCE, true, 0, 0);
545 FHS(HomeAddress, PostalCode, "Home Postal Code",
546 CFC_HOME_POSTAL_CODE, true, 0, 0);
547 FHS(HomeAddress, Country, "Home Country",
548 CFC_HOME_COUNTRY, true, 0, 0);
550 FHP(Categories, "Categories");
551 // FHP(GroupLinks, "Group Links");
552 FHP(Unknowns, "Unknown Fields");
554 return fhv;
557 std::string Contact::GetDescription() const
559 string desc = GetFullName();
560 if( desc.size() == 0 && Company.size() )
561 return Company;
562 return desc;
566 // GetFullName
568 /// Helper function that returns a formatted full name
570 std::string Contact::GetFullName() const
572 std::string Full = FirstName;
573 if( Full.size() && LastName.size() )
574 Full += " ";
575 Full += LastName;
576 return Full;
580 // GetEmail
582 /// Helper function that always returns a valid string. The string
583 /// may be empty if there is no address at the specified index.
585 const std::string& Contact::GetEmail(unsigned int index) const
587 static const std::string blank;
588 if( index < EmailAddresses.size() )
589 return EmailAddresses[index];
590 return blank;
593 void Contact::Dump(std::ostream &os) const
595 ios_format_state state(os);
597 os.setf(ios::left);
598 os.fill(' ');
600 os << "Contact: 0x" << setbase(16) << GetID()
601 << " (" << (unsigned int)RecType << ")\n";
603 // special fields not in type table
604 os << " " << setw(20) << "FirstName";
605 os << ": " << FirstName << "\n";
606 os << " " << setw(20) << "LastName";
607 os << ": " << LastName << "\n";
609 // cycle through email addresses
610 EmailList::const_iterator eai = EmailAddresses.begin();
611 for( ; eai != EmailAddresses.end(); ++eai ) {
612 if( eai->size() ) {
613 os << " Email : " << *eai << "\n";
617 // cycle through the type table
618 for( FieldLink<Contact> *b = ContactFieldLinks;
619 b->type != CFC_INVALID_FIELD;
620 b++ )
622 // special case: don't dump the raw image data, but
623 // leave that for a special hex dump
624 if( b->type == CFC_IMAGE )
625 continue;
627 const std::string *pField = 0;
628 if( b->strMember ) {
629 pField = &(this->*(b->strMember));
631 else if( b->postMember && b->postField ) {
632 pField = &((this->*(b->postMember)).*(b->postField));
635 // print only fields with data
636 if( pField && pField->size() ) {
637 os << " " << setw(20) << b->name;
638 os << ": " << Cr2LfWrapper(*pField) << "\n";
642 if( Categories.size() ) {
643 string display;
644 Categories.CategoryList2Str(display);
645 os << " Categories : " << display << "\n";
648 // print Birthday and Anniversary
649 if( Birthday.HasData() ) {
650 os << " Birthday : " << Birthday << "\n";
652 if( Anniversary.HasData() ) {
653 os << " Anniversary : " << Anniversary << "\n";
656 // print any group links
657 GroupLinksType::const_iterator
658 gb = GroupLinks.begin(), ge = GroupLinks.end();
659 if( gb != ge )
660 os << " GroupLinks:\n";
661 for( ; gb != ge; gb++ ) {
662 os << " ID: 0x" << setbase(16) << gb->Link << "\n";
665 // print Image in hex dump format, if available
666 if( Image.size() ) {
667 Data image(Image.data(), Image.size());
668 os << " Photo image:\n";
669 os << image << "\n";
672 // and finally print unknowns
673 os << Unknowns;
676 bool Contact::operator<(const Contact &other) const
678 // old sorting mechanism, to put group links at the bottom
679 //return GroupLinks.size() == 0 && other.GroupLinks.size() > 0;
680 // testing - put group links at the top
681 //return GroupLinks.size() > 0 && other.GroupLinks.size() == 0;
683 // usually one of these fields is filled in, so compare
684 // them all in a ( LastName + FirstName + Company ) key style
685 int cmp = LastName.compare(other.LastName);
686 if( cmp == 0 )
687 cmp = FirstName.compare(other.FirstName);
688 if( cmp == 0 )
689 cmp = Company.compare(other.Company);
690 return cmp < 0;
693 void Contact::SplitName(const std::string &full, std::string &first, std::string &last)
695 first.clear();
696 last.clear();
698 string::size_type pos = full.find_last_of(' ');
699 if( pos != string::npos ) {
700 // has space, assume last word is last name
701 last = full.c_str() + pos + 1;
702 first = full.substr(0, pos);
704 else {
705 // no space, assume only first name
706 first = full.substr(0);
710 std::string Contact::Email2CommaString(const EmailList &list)
712 ostringstream oss;
713 for( EmailList::const_iterator i = list.begin(); i!=list.end(); ++i ) {
714 if( i != list.begin() )
715 oss << ", ";
716 oss << *i;
718 return oss.str();
721 /// Replaces the EmailAddresses list with the parsed results of
722 /// list. If list is empty, then EmailAddresses will also be empty.
723 /// Note that incoming addresses need to be in simple format, not
724 /// complex formats like "Name <user@example.com>" but just
725 /// "user@example.com". This is a device limitation.
727 /// Any complex email addresses found in the list will be dropped,
728 /// with a message sent to the debug output stream.
729 void Contact::CommaString2Email(const std::string &list, EmailList &result)
731 // start fresh
732 result.clear();
734 // parse the comma separated list
735 istringstream iss(list);
736 string address;
738 while( iss >> ws && getline(iss, address, ',') ) {
739 // trim any trailing whitespace in the address
740 Inplace::rtrim(address);
742 // is this a complex address? like:
743 // Chris Frey <cdfrey@foursquare.net>
744 // The device only accepts the plain
745 // "cdfrey@foursquare.net" part here
746 if( address.rfind('>') != string::npos ) {
747 dout("Error: Cannot convert complex name+address to a simple contact email address, skipping: " << address);
748 continue;
751 // add to list if anything left
752 if( address.size() ) {
753 result.push_back(address);
758 } // namespace Barry