# r_contact.cc

Go to the documentation of this file.

    00001 ///
    00002 /// \file       r_contact.cc
    00003 ///             Blackberry database record parser class for contact records.
    00004 ///
    00005
    00006 /*
    00007     Copyright (C) 2005-2011, Net Direct Inc. (http://www.netdirect.ca/)
    00008
    00009     This program is free software; you can redistribute it and/or modify
    00010     it under the terms of the GNU General Public License as published by
    00011     the Free Software Foundation; either version 2 of the License, or
    00012     (at your option) any later version.
    00013
    00014     This program is distributed in the hope that it will be useful,
    00015     but WITHOUT ANY WARRANTY; without even the implied warranty of
    00016     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    00017
    00018     See the GNU General Public License in the COPYING file at the
    00019     root directory of this project for more details.
    00020 */
    00021
    00022 #include "r_contact.h"
    00023 #include "record-internal.h"
    00024 #include "protocol.h"
    00025 #include "protostructs.h"
    00026 #include "data.h"
    00027 #include "time.h"
    00028 #include "error.h"
    00029 #include "endian.h"
    00030 #include "iconv.h"
    00031 #include <ostream>
    00032 #include <iomanip>
    00033 #include <time.h>
    00034 #include <stdexcept>
    00035
    00036 #define __DEBUG_MODE__
    00037 #include "debug.h"
    00038
    00039 using namespace std;
    00040 using namespace Barry::Protocol;
    00041
    00042 namespace Barry {
    00043
    00044
    00045
    00046 ///////////////////////////////////////////////////////////////////////////////
    00047 // Contact class
    00048
    00049 // Contact field codes
    00050 #define CFC_EMAIL               1
    00051 #define CFC_PHONE               2
    00052 #define CFC_FAX                 3
    00053 #define CFC_WORK_PHONE          6
    00054 #define CFC_HOME_PHONE          7
    00055 #define CFC_MOBILE_PHONE        8
    00056 #define CFC_PAGER               9
    00057 #define CFC_PIN                 10
    00058 #define CFC_RADIO               14      // 0x0e
    00059 #define CFC_WORK_PHONE_2        16      // 0x10
    00060 #define CFC_HOME_PHONE_2        17      // 0x11
    00061 #define CFC_OTHER_PHONE         18      // 0x12
    00062 #define CFC_MOBILE_PHONE_2      19      // 0x13
    00063 #define CFC_HOME_FAX            20      // 0x14
    00064 #define CFC_NAME                32      // 0x20 used twice, in first/last name order
    00065 #define CFC_COMPANY             33
    00066 #define CFC_DEFAULT_COMM_METHOD 34
    00067 #define CFC_ADDRESS1            35
    00068 #define CFC_ADDRESS2            36
    00069 #define CFC_ADDRESS3            37
    00070 #define CFC_CITY                38
    00071 #define CFC_PROVINCE            39
    00072 #define CFC_POSTAL_CODE         40
    00073 #define CFC_COUNTRY             41
    00074 #define CFC_TITLE               42      // 0x2a
    00075 #define CFC_PUBLIC_KEY          43
    00076 #define CFC_GROUP_FLAG          44
    00077 #define CFC_GROUP_LINK          52
    00078 #define CFC_URL                 54      // 0x36
    00079 #define CFC_PREFIX              55      // 0x37
    00080 #define CFC_CATEGORY            59      // 0x3B
    00081 #define CFC_HOME_ADDRESS1       61      // 0x3D
    00082 #define CFC_HOME_ADDRESS2       62      // 0x3E
    00083   // If the address 3 isn't mapped then it appears
    00084   // in the same field as address2 with a space
    00085 #define CFC_HOME_ADDRESS3       63      // 0x3F
    00086 #define CFC_NOTES               64      // 0x40
    00087 #define CFC_USER_DEFINED_1      65      // 0x41
    00088 #define CFC_USER_DEFINED_2      66      // 0x42
    00089 #define CFC_USER_DEFINED_3      67      // 0x43
    00090 #define CFC_USER_DEFINED_4      68      // 0x44
    00091 #define CFC_HOME_CITY           69      // 0x45
    00092 #define CFC_HOME_PROVINCE       70      // 0x46
    00093 #define CFC_HOME_POSTAL_CODE    71      // 0x47
    00094 #define CFC_HOME_COUNTRY        72      // 0x48
    00095 #define CFC_IMAGE               77      // 0x4D
    00096 #define CFC_BIRTHDAY            82      // 0x52
    00097 #define CFC_ANNIVERSARY         83      // 0x53
    00098 #define CFC_MAYBE_CATEGORYID    84      // 0x54
    00099 #define CFC_UNIQUEID            85      // 0x55
    00100 #define CFC_NICKNAME            86      // 0x56
    00101 #define CFC_INVALID_FIELD       255
    00102
    00103 // Contact code to field table
    00104 static FieldLink<Contact> ContactFieldLinks[] = {
    00105    { CFC_NICKNAME,     "Nickname",   0,0,                 &Contact::Nickname, 0, 0, 0, 0, true },
    00106    { CFC_PHONE,        "Phone",      0,0,                 &Contact::Phone, 0, 0, 0, 0, true },
    00107    { CFC_FAX,          "Fax",        "facsimileTelephoneNumber",0, &Contact::Fax, 0, 0, 0, 0, true },
    00108    { CFC_HOME_FAX,     "HomeFax",    0,0,                 &Contact::HomeFax, 0, 0, 0, 0, true },
    00109    { CFC_WORK_PHONE,   "WorkPhone",  "telephoneNumber",0, &Contact::WorkPhone, 0, 0, 0, 0, true },
    00110    { CFC_HOME_PHONE,   "HomePhone",  "homePhone",0,       &Contact::HomePhone, 0, 0, 0, 0, true },
    00111    { CFC_MOBILE_PHONE, "MobilePhone","mobile",0,          &Contact::MobilePhone, 0, 0, 0, 0, true },
    00112    { CFC_MOBILE_PHONE_2,"MobilePhone2",0,0,               &Contact::MobilePhone2, 0, 0, 0, 0, true },
    00113    { CFC_PAGER,        "Pager",      "pager",0,           &Contact::Pager, 0, 0, 0, 0, true },
    00114    { CFC_PIN,          "PIN",        0,0,                 &Contact::PIN, 0, 0, 0, 0, true },
    00115    { CFC_RADIO,        "Radio",      0,0,                 &Contact::Radio, 0, 0, 0, 0, true },
    00116    { CFC_WORK_PHONE_2, "WorkPhone2", 0,0,                 &Contact::WorkPhone2, 0, 0, 0, 0, true },
    00117    { CFC_HOME_PHONE_2, "HomePhone2", 0,0,                 &Contact::HomePhone2, 0, 0, 0, 0, true },
    00118    { CFC_OTHER_PHONE,  "OtherPhone", 0,0,                 &Contact::OtherPhone, 0, 0, 0, 0, true },
    00119    { CFC_COMPANY,      "Company",    "o",0,               &Contact::Company, 0, 0, 0, 0, true },
    00120    { CFC_DEFAULT_COMM_METHOD,"DefaultCommMethod",0,0,     &Contact::DefaultCommunicationsMethod, 0, 0, 0, 0, true },
    00121    { CFC_ADDRESS1,     "WorkAddress1",   0,0,             0, 0, 0, &Contact::WorkAddress, &PostalAddress::Address1, true },
    00122    { CFC_ADDRESS2,     "WorkAddress2",   0,0,             0, 0, 0, &Contact::WorkAddress, &PostalAddress::Address2, true },
    00123    { CFC_ADDRESS3,     "WorkAddress3",   0,0,             0, 0, 0, &Contact::WorkAddress, &PostalAddress::Address3, true },
    00124    { CFC_CITY,         "WorkCity",       "l",0,           0, 0, 0, &Contact::WorkAddress, &PostalAddress::City, true },
    00125    { CFC_PROVINCE,     "WorkProvince",   "st",0,          0, 0, 0, &Contact::WorkAddress, &PostalAddress::Province, true },
    00126    { CFC_POSTAL_CODE,  "WorkPostalCode", "postalCode",0,  0, 0, 0, &Contact::WorkAddress, &PostalAddress::PostalCode, true },
    00127    { CFC_COUNTRY,      "WorkCountry",    "c", "country",  0, 0, 0, &Contact::WorkAddress, &PostalAddress::Country, true },
    00128    { CFC_TITLE,        "JobTitle",   "title",0,           &Contact::JobTitle, 0, 0, 0, 0, true },
    00129    { CFC_PUBLIC_KEY,   "PublicKey",  0,0,                 &Contact::PublicKey, 0, 0, 0, 0, false },
    00130    { CFC_URL,          "URL",        0,0,                 &Contact::URL, 0, 0, 0, 0, true },
    00131    { CFC_PREFIX,       "Prefix",     0,0,                 &Contact::Prefix, 0, 0, 0, 0, true },
    00132    { CFC_HOME_ADDRESS1,"HomeAddress1", 0,0,               0, 0, 0, &Contact::HomeAddress, &PostalAddress::Address1, true },
    00133    { CFC_HOME_ADDRESS2,"HomeAddress2", 0,0,               0, 0, 0, &Contact::HomeAddress, &PostalAddress::Address2, true },
    00134    { CFC_HOME_ADDRESS3,"HomeAddress3", 0,0,               0, 0, 0, &Contact::HomeAddress, &PostalAddress::Address3, true },
    00135    { CFC_NOTES,        "Notes",      0,0,                 &Contact::Notes, 0, 0, 0, 0, true },
    00136    { CFC_USER_DEFINED_1, "UserDefined1", 0,0,             &Contact::UserDefined1, 0, 0, 0, 0, true },
    00137    { CFC_USER_DEFINED_2, "UserDefined2", 0,0,             &Contact::UserDefined2, 0, 0, 0, 0, true },
    00138    { CFC_USER_DEFINED_3, "UserDefined3", 0,0,             &Contact::UserDefined3, 0, 0, 0, 0, true },
    00139    { CFC_USER_DEFINED_4, "UserDefined4", 0,0,             &Contact::UserDefined4, 0, 0, 0, 0, true },
    00140    { CFC_HOME_CITY,    "HomeCity",   0,0,                 0, 0, 0, &Contact::HomeAddress, &PostalAddress::City, true },
    00141    { CFC_HOME_PROVINCE,"HomeProvince", 0,0,               0, 0, 0, &Contact::HomeAddress, &PostalAddress::Province, true },
    00142    { CFC_HOME_POSTAL_CODE, "HomePostalCode", 0,0,         0, 0, 0, &Contact::HomeAddress, &PostalAddress::PostalCode, true },
    00143    { CFC_HOME_COUNTRY, "HomeCountry",0,0,                 0, 0, 0, &Contact::HomeAddress, &PostalAddress::Country, true },
    00144    { CFC_IMAGE,        "Image",      0,0,                 &Contact::Image, 0, 0, 0, 0, false },
    00145    { CFC_INVALID_FIELD,"EndOfList",  0, 0, 0, 0, 0, 0, 0, false }
    00146 };
    00147
    00148 Contact::Contact()
    00149         : RecType(Contact::GetDefaultRecType()),
    00150         RecordId(0),
    00151         m_FirstNameSeen(false)
    00152 {
    00153 }
    00154
    00155 Contact::~Contact()
    00156 {
    00157 }
    00158
    00159 const unsigned char* Contact::ParseField(const unsigned char *begin,
    00160                                          const unsigned char *end,
    00161                                          const IConverter *ic)
    00162 {
    00163         const CommonField *field = (const CommonField *) begin;
    00164
    00165         // advance and check size
    00166         begin += COMMON_FIELD_HEADER_SIZE + btohs(field->size);
    00167         if( begin > end )               // if begin==end, we are ok
    00168                 return begin;
    00169
    00170         if( !btohs(field->size) )       // if field has no size, something's up
    00171                 return begin;
    00172
    00173         // cycle through the type table
    00174         for(    FieldLink<Contact> *b = ContactFieldLinks;
    00175                 b->type != CFC_INVALID_FIELD;
    00176                 b++ )
    00177         {
    00178                 if( b->type == field->type ) {
    00179                         if( b->strMember ) {
    00180                                 std::string &s = this->*(b->strMember);
    00181                                 s = ParseFieldString(field);
    00182                                 if( b->iconvNeeded && ic )
    00183                                         s = ic->FromBB(s);
    00184                                 return begin;   // done!
    00185                         }
    00186                         else if( b->postMember && b->postField ) {
    00187                                 std::string &s = (this->*(b->postMember)).*(b->postField);
    00188                                 s = ParseFieldString(field);
    00189                                 if( b->iconvNeeded && ic )
    00190                                         s = ic->FromBB(s);
    00191                                 return begin;
    00192                         }
    00193                         else {
    00194                                 break;  // fall through to special handling
    00195                         }
    00196                 }
    00197         }
    00198
    00199         // if not found in the type table, check for special handling
    00200         switch( field->type )
    00201         {
    00202         case CFC_EMAIL: {
    00203                 std::string s = ParseFieldString(field);
    00204                 if( ic )
    00205                         s = ic->FromBB(s);
    00206                 EmailAddresses.push_back( s );
    00207                 }
    00208                 return begin;
    00209
    00210         case CFC_NAME: {
    00211                 // can be used multiple times, for first/last names
    00212                 std::string *name;
    00213                 if( FirstName.size() || m_FirstNameSeen ) {
    00214                         // first name already filled, use last name
    00215                         name = &LastName;
    00216                         m_FirstNameSeen = false;
    00217                 }
    00218                 else {
    00219                         name = &FirstName;
    00220                         m_FirstNameSeen = true;
    00221                 }
    00222
    00223                 *name = ParseFieldString(field);
    00224                 if( ic )
    00225                         *name = ic->FromBB(*name);
    00226                 }
    00227                 return begin;
    00228
    00229         case CFC_GROUP_LINK:
    00230                 // just add the unique ID to the list
    00231                 GroupLinks.push_back(
    00232                         GroupLink(field->u.link.uniqueId,
    00233                                 field->u.link.unknown));
    00234                 return begin;
    00235
    00236         case CFC_GROUP_FLAG:
    00237                 // ignore the group flag... the presense of group link items
    00238                 // behaves as the flag in this class
    00239                 return begin;
    00240
    00241         case CFC_CATEGORY: {
    00242                 std::string catstring = ParseFieldString(field);
    00243                 if( ic )
    00244                         catstring = ic->FromBB(catstring);
    00245                 Categories.CategoryStr2List(catstring);
    00246                 }
    00247                 return begin;
    00248
    00249         case CFC_BIRTHDAY: {
    00250                 std::string bstring = ParseFieldString(field);
    00251                 Birthday.FromBBString(bstring);
    00252                 }
    00253                 return begin;
    00254
    00255         case CFC_ANNIVERSARY: {
    00256                 std::string astring = ParseFieldString(field);
    00257                 Anniversary.FromBBString(astring);
    00258                 }
    00259                 return begin;
    00260
    00261         case CFC_UNIQUEID:
    00262                 // this is a duplicate of the UniqueID that comes from
    00263                 // the envelope part of the protocol... just throw this
    00264                 // away, since when we upload it, we need to use a
    00265                 // consisten UniqueID / RecordID from the API
    00266                 return begin;
    00267         }
    00268
    00269         // if still not handled, add to the Unknowns list
    00270         UnknownField uf;
    00271         uf.type = field->type;
    00272         uf.data.assign((const char*)field->u.raw, btohs(field->size));
    00273         Unknowns.push_back(uf);
    00274
    00275         // return new pointer for next field
    00276         return begin;
    00277 }
    00278
    00279 void Contact::ParseHeader(const Data &data, size_t &offset)
    00280 {
    00281         // no header to parse in Contact records
    00282 }
    00283
    00284 void Contact::ParseFields(const Data &data, size_t &offset, const IConverter *ic)
    00285 {
    00286         const unsigned char *finish = ParseCommonFields(*this,
    00287                 data.GetData() + offset, data.GetData() + data.GetSize(), ic);
    00288         offset += finish - (data.GetData() + offset);
    00289 }
    00290
    00291 void Contact::BuildHeader(Data &data, size_t &offset) const
    00292 {
    00293         // no header in Contact records
    00294 }
    00295
    00296 //
    00297 // BuildFields
    00298 //
    00299 /// Build fields part of record
    00300 ///
    00301 void Contact::BuildFields(Data &data, size_t &offset, const IConverter *ic) const
    00302 {
    00303         data.Zap();
    00304
    00305         // Sanity check: the Blackberry requires at least a name or
    00306         // a company name for each address record.
    00307         if( !GetFullName().size() && !Company.size() )
    00308                 throw BadData("Contact must have name or company name.");
    00309
    00310         // check if this is a group link record, and if so, output
    00311         // the group flag
    00312         if( GroupLinks.size() )
    00313                 BuildField(data, offset, CFC_GROUP_FLAG, 'G');
    00314
    00315         // special fields not in type table
    00316         if( FirstName.size() ) {
    00317                 std::string s = ic ? ic->ToBB(FirstName) : FirstName;
    00318                 BuildField(data, offset, CFC_NAME, s);
    00319         }
    00320         if( LastName.size() ) {
    00321                 if( !FirstName.size() ) {
    00322                         // order matters with first/last name, and if
    00323                         // last name exists, and first name doesn't,
    00324                         // insert blank first name ahead of it
    00325                         BuildField(data, offset, CFC_NAME, "");
    00326                 }
    00327                 BuildField(data, offset, CFC_NAME, ic ? ic->ToBB(LastName) : LastName);
    00328         }
    00329
    00330 //      FIXME
    00331 //      // add unknown data
    00332 //      char buffer[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
    00333 //      BuildField(data, offset, 0x54, buffer, 8);
    00334
    00335         // With the BlackBerry Storm, I have to add this entry.
    00336         // Otherwise the uniqueId of this contact is reseted !
    00337         // The device seems accept the multiple contact with the same uniqueId,
    00338         // but the synchronization process uses this uniqueId to identify the contact.
    00339         // add uniqueId
    00340         BuildField(data, offset, CFC_UNIQUEID, RecordId);
    00341
    00342         // add all email addresses
    00343         EmailList::const_iterator eai = EmailAddresses.begin();
    00344         for( ; eai != EmailAddresses.end(); ++eai ) {
    00345                 if( eai->size() ) {
    00346                         BuildField(data, offset, CFC_EMAIL, ic ? ic->ToBB(*eai) : *eai);
    00347                 }
    00348         }
    00349
    00350         // cycle through the type table
    00351         for(    FieldLink<Contact> *b = ContactFieldLinks;
    00352                 b->type != CFC_INVALID_FIELD;
    00353                 b++ )
    00354         {
    00355                 // print only fields with data
    00356                 if( b->strMember ) {
    00357                         const std::string &field = this->*(b->strMember);
    00358                         if( field.size() ) {
    00359                                 std::string s = (b->iconvNeeded && ic) ? ic->ToBB(field) : field;
    00360                                 BuildField(data, offset, b->type, s);
    00361                         }
    00362                 }
    00363                 else if( b->postMember && b->postField ) {
    00364                         const std::string &field = (this->*(b->postMember)).*(b->postField);
    00365                         if( field.size() ) {
    00366                                 std::string s = (b->iconvNeeded && ic) ? ic->ToBB(field) : field;
    00367                                 BuildField(data, offset, b->type, s);
    00368                         }
    00369                 }
    00370         }
    00371
    00372         // save any group links
    00373         GroupLinksType::const_iterator
    00374                 gb = GroupLinks.begin(), ge = GroupLinks.end();
    00375         for( ; gb != ge; gb++ ) {
    00376                 Barry::Protocol::GroupLink link;
    00377                 link.uniqueId = htobl(gb->Link);
    00378                 link.unknown = htobs(gb->Unknown);
    00379                 BuildField(data, offset, CFC_GROUP_LINK, link);
    00380         }
    00381
    00382         // save categories
    00383         if( Categories.size() ) {
    00384                 string store;
    00385                 Categories.CategoryList2Str(store);
    00386                 BuildField(data, offset, CFC_CATEGORY, ic ? ic->ToBB(store) : store);
    00387         }
    00388
    00389         // save Birthday and Anniversary
    00390         if( Birthday.HasData() )
    00391                 BuildField(data, offset, CFC_BIRTHDAY, Birthday.ToBBString());
    00392         if( Anniversary.HasData() )
    00393                 BuildField(data, offset, CFC_ANNIVERSARY, Anniversary.ToBBString());
    00394
    00395         // and finally save unknowns
    00396         UnknownsType::const_iterator
    00397                 ub = Unknowns.begin(), ue = Unknowns.end();
    00398         for( ; ub != ue; ub++ ) {
    00399                 BuildField(data, offset, *ub);
    00400         }
    00401
    00402         data.ReleaseBuffer(offset);
    00403 }
    00404
    00405 void Contact::Clear()
    00406 {
    00407         RecType = GetDefaultRecType();
    00408         RecordId = 0;
    00409
    00410         EmailAddresses.clear();
    00411         Phone.clear();
    00412
    00413         Fax.clear();
    00414         HomeFax.clear();
    00415         WorkPhone.clear();
    00416         HomePhone.clear();
    00417         MobilePhone.clear();
    00418         MobilePhone2.clear();
    00419         Pager.clear();
    00420         PIN.clear();
    00421         Radio.clear();
    00422         WorkPhone2.clear();
    00423         HomePhone2.clear();
    00424         OtherPhone.clear();
    00425         FirstName.clear();
    00426         LastName.clear();
    00427         Company.clear();
    00428         DefaultCommunicationsMethod.clear();
    00429         JobTitle.clear();
    00430         PublicKey.clear();
    00431         URL.clear();
    00432         Prefix.clear();
    00433         Notes.clear();
    00434         UserDefined1.clear();
    00435         UserDefined2.clear();
    00436         UserDefined3.clear();
    00437         UserDefined4.clear();
    00438         Image.clear();
    00439         Nickname.clear();
    00440
    00441         Birthday.Clear();
    00442         Anniversary.Clear();
    00443
    00444         WorkAddress.Clear();
    00445         HomeAddress.Clear();
    00446
    00447         Categories.clear();
    00448
    00449         GroupLinks.clear();
    00450         Unknowns.clear();
    00451
    00452         m_FirstNameSeen = false;
    00453 }
    00454
    00455 std::string Contact::GetDescription() const
    00456 {
    00457         return GetFullName();
    00458 }
    00459
    00460 //
    00461 // GetFullName
    00462 //
    00463 /// Helper function that returns a formatted full name
    00464 ///
    00465 std::string Contact::GetFullName() const
    00466 {
    00467         std::string Full = FirstName;
    00468         if( Full.size() && LastName.size() )
    00469                 Full += " ";
    00470         Full += LastName;
    00471         return Full;
    00472 }
    00473
    00474 //
    00475 // GetEmail
    00476 //
    00477 /// Helper function that always returns a valid string.  The string
    00478 /// may be empty if there is no address at the specified index.
    00479 ///
    00480 const std::string& Contact::GetEmail(unsigned int index) const
    00481 {
    00482         static const std::string blank;
    00483         if( index < EmailAddresses.size() )
    00484                 return EmailAddresses[index];
    00485         return blank;
    00486 }
    00487
    00488 void Contact::Dump(std::ostream &os) const
    00489 {
    00490         ios::fmtflags oldflags = os.setf(ios::left);
    00491         char fill = os.fill(' ');
    00492
    00493         os << "Contact: 0x" << setbase(16) << GetID()
    00494                 << " (" << (unsigned int)RecType << ")\n";
    00495
    00496         // special fields not in type table
    00497         os << "    " << setw(20) << "FirstName";
    00498         os << ": " << FirstName << "\n";
    00499         os << "    " << setw(20) << "LastName";
    00500         os << ": " << LastName << "\n";
    00501
    00502         // cycle through email addresses
    00503         EmailList::const_iterator eai = EmailAddresses.begin();
    00504         for( ; eai != EmailAddresses.end(); ++eai ) {
    00505                 if( eai->size() ) {
    00506                         os << "    Email               : " << *eai << "\n";
    00507                 }
    00508         }
    00509
    00510         // cycle through the type table
    00511         for(    FieldLink<Contact> *b = ContactFieldLinks;
    00512                 b->type != CFC_INVALID_FIELD;
    00513                 b++ )
    00514         {
    00515                 // special case: don't dump the raw image data, but
    00516                 // leave that for a special hex dump
    00517                 if( b->type == CFC_IMAGE )
    00518                         continue;
    00519
    00520                 const std::string *pField = 0;
    00521                 if( b->strMember ) {
    00522                         pField = &(this->*(b->strMember));
    00523                 }
    00524                 else if( b->postMember && b->postField ) {
    00525                         pField = &((this->*(b->postMember)).*(b->postField));
    00526                 }
    00527
    00528                 // print only fields with data
    00529                 if( pField && pField->size() ) {
    00530                         os << "    " << setw(20) << b->name;
    00531                         os << ": " << Cr2LfWrapper(*pField) << "\n";
    00532                 }
    00533         }
    00534
    00535         if( Categories.size() ) {
    00536                 string display;
    00537                 Categories.CategoryList2Str(display);
    00538                 os << "    Categories          : " << display << "\n";
    00539         }
    00540
    00541         // print Birthday and Anniversary
    00542         if( Birthday.HasData() ) {
    00543                 os << "    Birthday            : " << Birthday << "\n";
    00544         }
    00545         if( Anniversary.HasData() ) {
    00546                 os << "    Anniversary         : " << Anniversary << "\n";
    00547         }
    00548
    00549         // print any group links
    00550         GroupLinksType::const_iterator
    00551                 gb = GroupLinks.begin(), ge = GroupLinks.end();
    00552         if( gb != ge )
    00553                 os << "    GroupLinks:\n";
    00554         for( ; gb != ge; gb++ ) {
    00555                 os << "        ID: 0x" << setbase(16) << gb->Link << "\n";
    00556         }
    00557
    00558         // print Image in hex dump format, if available
    00559         if( Image.size() ) {
    00560                 Data image(Image.data(), Image.size());
    00561                 os << "    Photo image:\n";
    00562                 os << image << "\n";
    00563         }
    00564
    00565         // and finally print unknowns
    00566         os << Unknowns;
    00567
    00568         // cleanup the stream
    00569         os.flags(oldflags);
    00570         os.fill(fill);
    00571 }
    00572
    00573 bool Contact::operator<(const Contact &other) const
    00574 {
    00575         // old sorting mechanism, to put group links at the bottom
    00576         //return GroupLinks.size() == 0 && other.GroupLinks.size() > 0;
    00577         // testing - put group links at the top
    00578         //return GroupLinks.size() > 0 && other.GroupLinks.size() == 0;
    00579
    00580         // usually one of these fields is filled in, so compare
    00581         // them all in a ( LastName + FirstName + Company ) key style
    00582         int cmp = LastName.compare(other.LastName);
    00583         if( cmp == 0 )
    00584                 cmp = FirstName.compare(other.FirstName);
    00585         if( cmp == 0 )
    00586                 cmp = Company.compare(other.Company);
    00587         return cmp < 0;
    00588 }
    00589
    00590 void Contact::SplitName(const std::string &full, std::string &first, std::string &last)
    00591 {
    00592         first.clear();
    00593         last.clear();
    00594
    00595         string::size_type pos = full.find_last_of(' ');
    00596         if( pos != string::npos ) {
    00597                 // has space, assume last word is last name
    00598                 last = full.c_str() + pos + 1;
    00599                 first = full.substr(0, pos);
    00600         }
    00601         else {
    00602                 // no space, assume only first name
    00603                 first = full.substr(0);
    00604         }
    00605 }
    00606
    00607 } // namespace Barry
    00608