menu: added new Keywords tag to .desktop files
[barry.git] / src / r_contact.cc
blobe27cdad35385a7348d6ae49f33d52f47b297dc8a
1 ///
2 /// \file r_contact.cc
3 /// Blackberry database record parser class for contact records.
4 ///
6 /*
7 Copyright (C) 2005-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 "r_contact.h"
24 #include "record-internal.h"
25 #include "protocol.h"
26 #include "protostructs.h"
27 #include "data.h"
28 #include "time.h"
29 #include "error.h"
30 #include "endian.h"
31 #include "iconv.h"
32 #include "trim.h"
33 #include <iostream>
34 #include <iomanip>
35 #include <sstream>
36 #include <time.h>
37 #include <stdexcept>
38 #include "ios_state.h"
40 #define __DEBUG_MODE__
41 #include "debug.h"
43 using namespace std;
44 using namespace Barry::Protocol;
46 namespace Barry {
50 ///////////////////////////////////////////////////////////////////////////////
51 // Contact class
53 // Contact field codes
54 #define CFC_EMAIL 1
55 #define CFC_PHONE 2
56 #define CFC_FAX 3
57 #define CFC_WORK_PHONE 6
58 #define CFC_HOME_PHONE 7
59 #define CFC_MOBILE_PHONE 8
60 #define CFC_PAGER 9
61 #define CFC_PIN 10
62 #define CFC_RADIO 14 // 0x0e
63 #define CFC_WORK_PHONE_2 16 // 0x10
64 #define CFC_HOME_PHONE_2 17 // 0x11
65 #define CFC_OTHER_PHONE 18 // 0x12
66 #define CFC_MOBILE_PHONE_2 19 // 0x13
67 #define CFC_HOME_FAX 20 // 0x14
68 #define CFC_NAME 32 // 0x20 used twice, in first/last name order
69 #define CFC_COMPANY 33
70 #define CFC_DEFAULT_COMM_METHOD 34
71 #define CFC_ADDRESS1 35
72 #define CFC_ADDRESS2 36
73 #define CFC_ADDRESS3 37
74 #define CFC_CITY 38
75 #define CFC_PROVINCE 39
76 #define CFC_POSTAL_CODE 40
77 #define CFC_COUNTRY 41
78 #define CFC_TITLE 42 // 0x2a
79 #define CFC_PUBLIC_KEY 43
80 #define CFC_GROUP_FLAG 44
81 #define CFC_GROUP_LINK 52
82 #define CFC_URL 54 // 0x36
83 #define CFC_PREFIX 55 // 0x37
84 #define CFC_CATEGORY 59 // 0x3B
85 #define CFC_HOME_ADDRESS1 61 // 0x3D
86 #define CFC_HOME_ADDRESS2 62 // 0x3E
87 // If the address 3 isn't mapped then it appears
88 // in the same field as address2 with a space
89 #define CFC_HOME_ADDRESS3 63 // 0x3F
90 #define CFC_NOTES 64 // 0x40
91 #define CFC_USER_DEFINED_1 65 // 0x41
92 #define CFC_USER_DEFINED_2 66 // 0x42
93 #define CFC_USER_DEFINED_3 67 // 0x43
94 #define CFC_USER_DEFINED_4 68 // 0x44
95 #define CFC_HOME_CITY 69 // 0x45
96 #define CFC_HOME_PROVINCE 70 // 0x46
97 #define CFC_HOME_POSTAL_CODE 71 // 0x47
98 #define CFC_HOME_COUNTRY 72 // 0x48
99 #define CFC_IMAGE 77 // 0x4D
100 #define CFC_BIRTHDAY 82 // 0x52
101 #define CFC_ANNIVERSARY 83 // 0x53
102 #define CFC_MAYBE_CATEGORYID 84 // 0x54
103 #define CFC_UNIQUEID 85 // 0x55
104 #define CFC_NICKNAME 86 // 0x56
105 #define CFC_INVALID_FIELD 255
107 // Contact code to field table
108 static FieldLink<Contact> ContactFieldLinks[] = {
109 { CFC_NICKNAME, N_("Nickname"), 0,0, &Contact::Nickname, 0, 0, 0, 0, true },
110 { CFC_PHONE, N_("Phone"), 0,0, &Contact::Phone, 0, 0, 0, 0, true },
111 { CFC_FAX, N_("Fax"), "facsimileTelephoneNumber",0, &Contact::Fax, 0, 0, 0, 0, true },
112 { CFC_HOME_FAX, N_("HomeFax"), 0,0, &Contact::HomeFax, 0, 0, 0, 0, true },
113 { CFC_WORK_PHONE, N_("WorkPhone"), "telephoneNumber",0, &Contact::WorkPhone, 0, 0, 0, 0, true },
114 { CFC_HOME_PHONE, N_("HomePhone"), "homePhone",0, &Contact::HomePhone, 0, 0, 0, 0, true },
115 { CFC_MOBILE_PHONE, N_("MobilePhone"),"mobile",0, &Contact::MobilePhone, 0, 0, 0, 0, true },
116 { CFC_MOBILE_PHONE_2,N_("MobilePhone2"),0,0, &Contact::MobilePhone2, 0, 0, 0, 0, true },
117 { CFC_PAGER, N_("Pager"), "pager",0, &Contact::Pager, 0, 0, 0, 0, true },
118 { CFC_PIN, N_("PIN"), 0,0, &Contact::PIN, 0, 0, 0, 0, true },
119 { CFC_RADIO, N_("Radio"), 0,0, &Contact::Radio, 0, 0, 0, 0, true },
120 { CFC_WORK_PHONE_2, N_("WorkPhone2"), 0,0, &Contact::WorkPhone2, 0, 0, 0, 0, true },
121 { CFC_HOME_PHONE_2, N_("HomePhone2"), 0,0, &Contact::HomePhone2, 0, 0, 0, 0, true },
122 { CFC_OTHER_PHONE, N_("OtherPhone"), 0,0, &Contact::OtherPhone, 0, 0, 0, 0, true },
123 { CFC_COMPANY, N_("Company"), "o",0, &Contact::Company, 0, 0, 0, 0, true },
124 { CFC_DEFAULT_COMM_METHOD,N_("DefaultCommMethod"),0,0, &Contact::DefaultCommunicationsMethod, 0, 0, 0, 0, true },
125 { CFC_ADDRESS1, N_("WorkAddress1"), 0,0, 0, 0, 0, &Contact::WorkAddress, &PostalAddress::Address1, true },
126 { CFC_ADDRESS2, N_("WorkAddress2"), 0,0, 0, 0, 0, &Contact::WorkAddress, &PostalAddress::Address2, true },
127 { CFC_ADDRESS3, N_("WorkAddress3"), 0,0, 0, 0, 0, &Contact::WorkAddress, &PostalAddress::Address3, true },
128 { CFC_CITY, N_("WorkCity"), "l",0, 0, 0, 0, &Contact::WorkAddress, &PostalAddress::City, true },
129 { CFC_PROVINCE, N_("WorkProvince"), "st",0, 0, 0, 0, &Contact::WorkAddress, &PostalAddress::Province, true },
130 { CFC_POSTAL_CODE, N_("WorkPostalCode"), "postalCode",0, 0, 0, 0, &Contact::WorkAddress, &PostalAddress::PostalCode, true },
131 { CFC_COUNTRY, N_("WorkCountry"), "c", "country", 0, 0, 0, &Contact::WorkAddress, &PostalAddress::Country, true },
132 { CFC_TITLE, N_("JobTitle"), "title",0, &Contact::JobTitle, 0, 0, 0, 0, true },
133 { CFC_PUBLIC_KEY, N_("PublicKey"), 0,0, &Contact::PublicKey, 0, 0, 0, 0, false },
134 { CFC_URL, N_("URL"), 0,0, &Contact::URL, 0, 0, 0, 0, true },
135 { CFC_PREFIX, N_("Prefix"), 0,0, &Contact::Prefix, 0, 0, 0, 0, true },
136 { CFC_HOME_ADDRESS1,N_("HomeAddress1"), 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::Address1, true },
137 { CFC_HOME_ADDRESS2,N_("HomeAddress2"), 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::Address2, true },
138 { CFC_HOME_ADDRESS3,N_("HomeAddress3"), 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::Address3, true },
139 { CFC_NOTES, N_("Notes"), 0,0, &Contact::Notes, 0, 0, 0, 0, true },
140 { CFC_USER_DEFINED_1, N_("UserDefined1"), 0,0, &Contact::UserDefined1, 0, 0, 0, 0, true },
141 { CFC_USER_DEFINED_2, N_("UserDefined2"), 0,0, &Contact::UserDefined2, 0, 0, 0, 0, true },
142 { CFC_USER_DEFINED_3, N_("UserDefined3"), 0,0, &Contact::UserDefined3, 0, 0, 0, 0, true },
143 { CFC_USER_DEFINED_4, N_("UserDefined4"), 0,0, &Contact::UserDefined4, 0, 0, 0, 0, true },
144 { CFC_HOME_CITY, N_("HomeCity"), 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::City, true },
145 { CFC_HOME_PROVINCE,N_("HomeProvince"), 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::Province, true },
146 { CFC_HOME_POSTAL_CODE, N_("HomePostalCode"), 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::PostalCode, true },
147 { CFC_HOME_COUNTRY, N_("HomeCountry"),0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::Country, true },
148 { CFC_IMAGE, N_("Image"), 0,0, &Contact::Image, 0, 0, 0, 0, false },
149 { CFC_INVALID_FIELD,N_("EndOfList"), 0, 0, 0, 0, 0, 0, 0, false }
152 Contact::Contact()
153 : RecType(Contact::GetDefaultRecType()),
154 RecordId(0),
155 m_FirstNameSeen(false)
159 Contact::~Contact()
163 const unsigned char* Contact::ParseField(const unsigned char *begin,
164 const unsigned char *end,
165 const IConverter *ic)
167 const CommonField *field = (const CommonField *) begin;
169 // advance and check size
170 begin += COMMON_FIELD_HEADER_SIZE + btohs(field->size);
171 if( begin > end ) // if begin==end, we are ok
172 return begin;
174 if( !btohs(field->size) ) // if field has no size, something's up
175 return begin;
177 // cycle through the type table
178 for( FieldLink<Contact> *b = ContactFieldLinks;
179 b->type != CFC_INVALID_FIELD;
180 b++ )
182 if( b->type == field->type ) {
183 if( b->strMember ) {
184 std::string &s = this->*(b->strMember);
185 s = ParseFieldString(field);
186 if( b->iconvNeeded && ic )
187 s = ic->FromBB(s);
188 return begin; // done!
190 else if( b->postMember && b->postField ) {
191 std::string &s = (this->*(b->postMember)).*(b->postField);
192 s = ParseFieldString(field);
193 if( b->iconvNeeded && ic )
194 s = ic->FromBB(s);
195 return begin;
197 else {
198 break; // fall through to special handling
203 // if not found in the type table, check for special handling
204 switch( field->type )
206 case CFC_EMAIL: {
207 std::string s = ParseFieldString(field);
208 if( ic )
209 s = ic->FromBB(s);
210 EmailAddresses.push_back( s );
212 return begin;
214 case CFC_NAME: {
215 // can be used multiple times, for first/last names
216 std::string *name;
217 if( FirstName.size() || m_FirstNameSeen ) {
218 // first name already filled, use last name
219 name = &LastName;
220 m_FirstNameSeen = false;
222 else {
223 name = &FirstName;
224 m_FirstNameSeen = true;
227 *name = ParseFieldString(field);
228 if( ic )
229 *name = ic->FromBB(*name);
231 return begin;
233 case CFC_GROUP_LINK:
234 // just add the unique ID to the list
235 GroupLinks.push_back(
236 GroupLink(field->u.link.uniqueId,
237 field->u.link.unknown));
238 return begin;
240 case CFC_GROUP_FLAG:
241 // ignore the group flag... the presense of group link items
242 // behaves as the flag in this class
243 return begin;
245 case CFC_CATEGORY: {
246 std::string catstring = ParseFieldString(field);
247 if( ic )
248 catstring = ic->FromBB(catstring);
249 Categories.CategoryStr2List(catstring);
251 return begin;
253 case CFC_BIRTHDAY: {
254 std::string bstring = ParseFieldString(field);
255 Birthday.FromBBString(bstring);
257 return begin;
259 case CFC_ANNIVERSARY: {
260 std::string astring = ParseFieldString(field);
261 Anniversary.FromBBString(astring);
263 return begin;
265 case CFC_UNIQUEID:
266 // this is a duplicate of the UniqueID that comes from
267 // the envelope part of the protocol... just throw this
268 // away, since when we upload it, we need to use a
269 // consistent UniqueID / RecordID from the API
270 return begin;
273 // if still not handled, add to the Unknowns list
274 UnknownField uf;
275 uf.type = field->type;
276 uf.data.assign((const char*)field->u.raw, btohs(field->size));
277 Unknowns.push_back(uf);
279 // return new pointer for next field
280 return begin;
283 void Contact::ParseHeader(const Data &data, size_t &offset)
285 // no header to parse in Contact records
288 void Contact::ParseFields(const Data &data, size_t &offset, const IConverter *ic)
290 const unsigned char *finish = ParseCommonFields(*this,
291 data.GetData() + offset, data.GetData() + data.GetSize(), ic);
292 offset += finish - (data.GetData() + offset);
295 void Contact::Validate() const
297 if( !GetFullName().size() && !Company.size() ) {
298 throw Barry::ValidationError(_("A contact record must contain either a First/Last name, or a Company name."));
302 void Contact::BuildHeader(Data &data, size_t &offset) const
304 // no header in Contact records
308 // BuildFields
310 /// Build fields part of record
312 void Contact::BuildFields(Data &data, size_t &offset, const IConverter *ic) const
314 data.Zap();
316 // Sanity check: the Blackberry requires at least a name or
317 // a company name for each address record.
318 if( !GetFullName().size() && !Company.size() )
319 throw BadData(_("Contact must have name or company name."));
321 // check if this is a group link record, and if so, output
322 // the group flag
323 if( GroupLinks.size() )
324 BuildField(data, offset, CFC_GROUP_FLAG, 'G');
326 // special fields not in type table
327 if( FirstName.size() ) {
328 std::string s = ic ? ic->ToBB(FirstName) : FirstName;
329 BuildField(data, offset, CFC_NAME, s);
331 if( LastName.size() ) {
332 if( !FirstName.size() ) {
333 // order matters with first/last name, and if
334 // last name exists, and first name doesn't,
335 // insert blank first name ahead of it
336 BuildField(data, offset, CFC_NAME, "");
338 BuildField(data, offset, CFC_NAME, ic ? ic->ToBB(LastName) : LastName);
341 // FIXME
342 // // add unknown data
343 // char buffer[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
344 // BuildField(data, offset, 0x54, buffer, 8);
346 // With the BlackBerry Storm, I have to add this entry.
347 // Otherwise the uniqueId of this contact is reseted !
348 // The device seems accept the multiple contact with the same uniqueId,
349 // but the synchronization process uses this uniqueId to identify the contact.
350 // add uniqueId
351 BuildField(data, offset, CFC_UNIQUEID, RecordId);
353 // add all email addresses
354 EmailList::const_iterator eai = EmailAddresses.begin();
355 for( ; eai != EmailAddresses.end(); ++eai ) {
356 if( eai->size() ) {
357 BuildField(data, offset, CFC_EMAIL, ic ? ic->ToBB(*eai) : *eai);
361 // cycle through the type table
362 for( FieldLink<Contact> *b = ContactFieldLinks;
363 b->type != CFC_INVALID_FIELD;
364 b++ )
366 // print only fields with data
367 if( b->strMember ) {
368 const std::string &field = this->*(b->strMember);
369 if( field.size() ) {
370 std::string s = (b->iconvNeeded && ic) ? ic->ToBB(field) : field;
371 BuildField(data, offset, b->type, s);
374 else if( b->postMember && b->postField ) {
375 const std::string &field = (this->*(b->postMember)).*(b->postField);
376 if( field.size() ) {
377 std::string s = (b->iconvNeeded && ic) ? ic->ToBB(field) : field;
378 BuildField(data, offset, b->type, s);
383 // save any group links
384 GroupLinksType::const_iterator
385 gb = GroupLinks.begin(), ge = GroupLinks.end();
386 for( ; gb != ge; gb++ ) {
387 Barry::Protocol::GroupLink link;
388 link.uniqueId = htobl(gb->Link);
389 link.unknown = htobs(gb->Unknown);
390 BuildField(data, offset, CFC_GROUP_LINK, link);
393 // save categories
394 if( Categories.size() ) {
395 string store;
396 Categories.CategoryList2Str(store);
397 BuildField(data, offset, CFC_CATEGORY, ic ? ic->ToBB(store) : store);
400 // save Birthday and Anniversary
401 if( Birthday.HasData() )
402 BuildField(data, offset, CFC_BIRTHDAY, Birthday.ToBBString());
403 if( Anniversary.HasData() )
404 BuildField(data, offset, CFC_ANNIVERSARY, Anniversary.ToBBString());
406 // and finally save unknowns
407 UnknownsType::const_iterator
408 ub = Unknowns.begin(), ue = Unknowns.end();
409 for( ; ub != ue; ub++ ) {
410 BuildField(data, offset, *ub);
413 data.ReleaseBuffer(offset);
416 void Contact::Clear()
418 RecType = GetDefaultRecType();
419 RecordId = 0;
421 EmailAddresses.clear();
422 Phone.clear();
424 Fax.clear();
425 HomeFax.clear();
426 WorkPhone.clear();
427 HomePhone.clear();
428 MobilePhone.clear();
429 MobilePhone2.clear();
430 Pager.clear();
431 PIN.clear();
432 Radio.clear();
433 WorkPhone2.clear();
434 HomePhone2.clear();
435 OtherPhone.clear();
436 FirstName.clear();
437 LastName.clear();
438 Company.clear();
439 DefaultCommunicationsMethod.clear();
440 JobTitle.clear();
441 PublicKey.clear();
442 URL.clear();
443 Prefix.clear();
444 Notes.clear();
445 UserDefined1.clear();
446 UserDefined2.clear();
447 UserDefined3.clear();
448 UserDefined4.clear();
449 Image.clear();
450 Nickname.clear();
452 Birthday.Clear();
453 Anniversary.Clear();
455 WorkAddress.Clear();
456 HomeAddress.Clear();
458 Categories.clear();
460 GroupLinks.clear();
461 Unknowns.clear();
463 m_FirstNameSeen = false;
466 const FieldHandle<Contact>::ListT& Contact::GetFieldHandles()
468 static FieldHandle<Contact>::ListT fhv;
470 if( fhv.size() )
471 return fhv;
473 #undef CONTAINER_OBJECT_NAME
474 #define CONTAINER_OBJECT_NAME fhv
476 #undef RECORD_CLASS_NAME
477 #define RECORD_CLASS_NAME Contact
479 // first number is priority of fields... 0 being most critical fields
480 FHP(RecType, _("Record Type Code"));
481 FHP(RecordId, _("Unique ID"));
482 FHP(EmailAddresses, _("Email Addresses"));
484 FHP(FirstName, _("First Name"));
485 FHP(LastName, _("Last Name"));
486 FHL(Company, _("Company"), CFC_COMPANY, true, "o", 0);
487 FHL(JobTitle, _("Job Title"), CFC_TITLE, true, "title", 0);
488 FHD(Prefix, _("Prefix"), CFC_PREFIX, true);
490 FHD(Nickname, _("Nickname"), CFC_NICKNAME, true);
491 FHD(Phone, _("Phone (deprecated)"), CFC_PHONE, true);
492 FHL(Fax, _("Work Fax"), CFC_FAX, true, "facsimileTelephoneNumber", 0);
493 FHD(HomeFax, _("Home Fax"), CFC_HOME_FAX, true);
494 FHL(WorkPhone, _("Work Phone"), CFC_WORK_PHONE, true,
495 "telephoneNumber", 0);
496 FHD(WorkPhone2, _("Work Phone 2"), CFC_WORK_PHONE_2, true);
497 FHL(HomePhone, _("Home Phone"), CFC_HOME_PHONE, true, "homePhone", 0);
498 FHD(HomePhone2, _("Home Phone 2"), CFC_HOME_PHONE_2, true);
499 FHL(MobilePhone, _("Mobile Phone"), CFC_MOBILE_PHONE, true, "mobile", 0);
500 FHD(MobilePhone2, _("Mobile Phone 2"), CFC_MOBILE_PHONE_2, true);
501 FHD(OtherPhone, _("Other Phone"), CFC_OTHER_PHONE, true);
502 FHL(Pager, _("Pager"), CFC_PAGER, true, "pager", 0);
503 FHD(PIN, _("PIN"), CFC_PIN, true);
504 FHD(Radio, _("Radio"), CFC_RADIO, true);
505 FHD(DefaultCommunicationsMethod, _("Default Communications Method"),
506 CFC_DEFAULT_COMM_METHOD, true);
507 FHD(PublicKey, _("Public Key"), CFC_PUBLIC_KEY, false);
508 FHD(URL, _("URL"), CFC_URL, true);
509 FHD(Notes, _("Notes"), CFC_NOTES, true);
510 FHD(UserDefined1, _("User Defined Field 1"), CFC_USER_DEFINED_1, true);
511 FHD(UserDefined2, _("User Defined Field 2"), CFC_USER_DEFINED_2, true);
512 FHD(UserDefined3, _("User Defined Field 3"), CFC_USER_DEFINED_3, true);
513 FHD(UserDefined4, _("User Defined Field 4"), CFC_USER_DEFINED_4, true);
514 FHD(Image, _("Image"), CFC_IMAGE, false);
516 FHD(Birthday, _("Birthday"), CFC_BIRTHDAY, true);
517 FHD(Anniversary, _("Anniversary"), CFC_ANNIVERSARY, true);
519 FHC(WorkAddress, _("Work Address"));
520 FHS(WorkAddress, Address1, _("Work Address 1"),
521 CFC_ADDRESS1, true, 0, 0);
522 FHS(WorkAddress, Address2, _("Work Address 2"),
523 CFC_ADDRESS2, true, 0, 0);
524 FHS(WorkAddress, Address3, _("Work Address 3"),
525 CFC_ADDRESS3, true, 0, 0);
526 FHS(WorkAddress, City, _("Work City"),
527 CFC_CITY, true, "l", 0);
528 FHS(WorkAddress, Province, _("Work Province"),
529 CFC_PROVINCE, true, "st", 0);
530 FHS(WorkAddress, PostalCode, _("Work Postal Code"),
531 CFC_POSTAL_CODE, true, "postalCode", 0);
532 FHS(WorkAddress, Country, _("Work Country"),
533 CFC_COUNTRY, true, "c", "country");
535 FHC(HomeAddress, _("Home Address"));
536 FHS(HomeAddress, Address1, _("Home Address 1"),
537 CFC_HOME_ADDRESS1, true, 0, 0);
538 FHS(HomeAddress, Address2, _("Home Address 2"),
539 CFC_HOME_ADDRESS2, true, 0, 0);
540 FHS(HomeAddress, Address3, _("Home Address 3"),
541 CFC_HOME_ADDRESS3, true, 0, 0);
542 FHS(HomeAddress, City, _("Home City"),
543 CFC_HOME_CITY, true, 0, 0);
544 FHS(HomeAddress, Province, _("Home Province"),
545 CFC_HOME_PROVINCE, true, 0, 0);
546 FHS(HomeAddress, PostalCode, _("Home Postal Code"),
547 CFC_HOME_POSTAL_CODE, true, 0, 0);
548 FHS(HomeAddress, Country, _("Home Country"),
549 CFC_HOME_COUNTRY, true, 0, 0);
551 FHP(Categories, _("Categories"));
552 // FHP(GroupLinks, _("Group Links"));
553 FHP(Unknowns, _("Unknown Fields"));
555 return fhv;
558 std::string Contact::GetDescription() const
560 string desc = GetFullName();
561 if( desc.size() == 0 && Company.size() )
562 return Company;
563 return desc;
567 // GetFullName
569 /// Helper function that returns a formatted full name
571 std::string Contact::GetFullName() const
573 std::string Full = FirstName;
574 if( Full.size() && LastName.size() )
575 Full += " ";
576 Full += LastName;
577 return Full;
581 // GetEmail
583 /// Helper function that always returns a valid string. The string
584 /// may be empty if there is no address at the specified index.
586 const std::string& Contact::GetEmail(unsigned int index) const
588 static const std::string blank;
589 if( index < EmailAddresses.size() )
590 return EmailAddresses[index];
591 return blank;
594 void Contact::Dump(std::ostream &os) const
596 ios_format_state state(os);
598 os.setf(ios::left);
599 os.fill(' ');
601 os << _("Contact: ") << "0x" << setbase(16) << GetID()
602 << " (" << (unsigned int)RecType << ")\n";
604 // special fields not in type table
605 os << " " << setw(20) << _("FirstName");
606 os << ": " << FirstName << "\n";
607 os << " " << setw(20) << _("LastName");
608 os << ": " << LastName << "\n";
610 // cycle through email addresses
611 EmailList::const_iterator eai = EmailAddresses.begin();
612 for( ; eai != EmailAddresses.end(); ++eai ) {
613 if( eai->size() ) {
614 os << _(" Email : ") << *eai << "\n";
618 // cycle through the type table
619 for( FieldLink<Contact> *b = ContactFieldLinks;
620 b->type != CFC_INVALID_FIELD;
621 b++ )
623 // special case: don't dump the raw image data, but
624 // leave that for a special hex dump
625 if( b->type == CFC_IMAGE )
626 continue;
628 const std::string *pField = 0;
629 if( b->strMember ) {
630 pField = &(this->*(b->strMember));
632 else if( b->postMember && b->postField ) {
633 pField = &((this->*(b->postMember)).*(b->postField));
636 // print only fields with data
637 if( pField && pField->size() ) {
638 os << " " << setw(20) << gettext(b->name);
639 os << ": " << Cr2LfWrapper(*pField) << "\n";
643 if( Categories.size() ) {
644 string display;
645 Categories.CategoryList2Str(display);
646 os << _(" Categories : ") << display << "\n";
649 // print Birthday and Anniversary
650 if( Birthday.HasData() ) {
651 os << _(" Birthday : ") << Birthday << "\n";
653 if( Anniversary.HasData() ) {
654 os << _(" Anniversary : ") << Anniversary << "\n";
657 // print any group links
658 GroupLinksType::const_iterator
659 gb = GroupLinks.begin(), ge = GroupLinks.end();
660 if( gb != ge )
661 os << _(" GroupLinks:\n");
662 for( ; gb != ge; gb++ ) {
663 os << _(" ID: ") << "0x" << setbase(16) << gb->Link << "\n";
666 // print Image in hex dump format, if available
667 if( Image.size() ) {
668 Data image(Image.data(), Image.size());
669 os << _(" Photo image:\n");
670 os << image << "\n";
673 // and finally print unknowns
674 os << Unknowns;
677 bool Contact::operator<(const Contact &other) const
679 // old sorting mechanism, to put group links at the bottom
680 //return GroupLinks.size() == 0 && other.GroupLinks.size() > 0;
681 // testing - put group links at the top
682 //return GroupLinks.size() > 0 && other.GroupLinks.size() == 0;
684 // usually one of these fields is filled in, so compare
685 // them all in a ( LastName + FirstName + Company ) key style
686 int cmp = LastName.compare(other.LastName);
687 if( cmp == 0 )
688 cmp = FirstName.compare(other.FirstName);
689 if( cmp == 0 )
690 cmp = Company.compare(other.Company);
691 return cmp < 0;
694 void Contact::SplitName(const std::string &full, std::string &first, std::string &last)
696 first.clear();
697 last.clear();
699 string::size_type pos = full.find_last_of(' ');
700 if( pos != string::npos ) {
701 // has space, assume last word is last name
702 last = full.c_str() + pos + 1;
703 first = full.substr(0, pos);
705 else {
706 // no space, assume only first name
707 first = full.substr(0);
711 std::string Contact::Email2CommaString(const EmailList &list)
713 ostringstream oss;
714 for( EmailList::const_iterator i = list.begin(); i!=list.end(); ++i ) {
715 if( i != list.begin() )
716 oss << ", ";
717 oss << *i;
719 return oss.str();
722 /// Replaces the EmailAddresses list with the parsed results of
723 /// list. If list is empty, then EmailAddresses will also be empty.
724 /// Note that incoming addresses need to be in simple format, not
725 /// complex formats like "Name <user@example.com>" but just
726 /// "user@example.com". This is a device limitation.
728 /// Any complex email addresses found in the list will be dropped,
729 /// with a message sent to the debug output stream.
730 void Contact::CommaString2Email(const std::string &list, EmailList &result)
732 // start fresh
733 result.clear();
735 // parse the comma separated list
736 istringstream iss(list);
737 string address;
739 while( iss >> ws && getline(iss, address, ',') ) {
740 // trim any trailing whitespace in the address
741 Inplace::rtrim(address);
743 // is this a complex address? like:
744 // Chris Frey <cdfrey@foursquare.net>
745 // The device only accepts the plain
746 // "cdfrey@foursquare.net" part here
747 if( address.rfind('>') != string::npos ) {
748 dout(_("Error: Cannot convert complex name+address to a simple contact email address, skipping: ") << address);
749 continue;
752 // add to list if anything left
753 if( address.size() ) {
754 result.push_back(address);
759 } // namespace Barry