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_UNIQUEID            85      // 0x55
00099 #define CFC_NICKNAME            86      // 0x56
00100 #define CFC_INVALID_FIELD       255
00101 
00102 // Contact code to field table
00103 static FieldLink<Contact> ContactFieldLinks[] = {
00104    { CFC_NICKNAME,     "Nickname",   0,0,                 &Contact::Nickname, 0, 0, 0, 0, true },
00105    { CFC_PHONE,        "Phone",      0,0,                 &Contact::Phone, 0, 0, 0, 0, true },
00106    { CFC_FAX,          "Fax",        "facsimileTelephoneNumber",0, &Contact::Fax, 0, 0, 0, 0, true },
00107    { CFC_HOME_FAX,     "HomeFax",    0,0,                 &Contact::HomeFax, 0, 0, 0, 0, true },
00108    { CFC_WORK_PHONE,   "WorkPhone",  "telephoneNumber",0, &Contact::WorkPhone, 0, 0, 0, 0, true },
00109    { CFC_HOME_PHONE,   "HomePhone",  "homePhone",0,       &Contact::HomePhone, 0, 0, 0, 0, true },
00110    { CFC_MOBILE_PHONE, "MobilePhone","mobile",0,          &Contact::MobilePhone, 0, 0, 0, 0, true },
00111    { CFC_MOBILE_PHONE_2,"MobilePhone2",0,0,               &Contact::MobilePhone2, 0, 0, 0, 0, true },
00112    { CFC_PAGER,        "Pager",      "pager",0,           &Contact::Pager, 0, 0, 0, 0, true },
00113    { CFC_PIN,          "PIN",        0,0,                 &Contact::PIN, 0, 0, 0, 0, true },
00114    { CFC_RADIO,        "Radio",      0,0,                 &Contact::Radio, 0, 0, 0, 0, true },
00115    { CFC_WORK_PHONE_2, "WorkPhone2", 0,0,                 &Contact::WorkPhone2, 0, 0, 0, 0, true },
00116    { CFC_HOME_PHONE_2, "HomePhone2", 0,0,                 &Contact::HomePhone2, 0, 0, 0, 0, true },
00117    { CFC_OTHER_PHONE,  "OtherPhone", 0,0,                 &Contact::OtherPhone, 0, 0, 0, 0, true },
00118    { CFC_COMPANY,      "Company",    "o",0,               &Contact::Company, 0, 0, 0, 0, true },
00119    { CFC_DEFAULT_COMM_METHOD,"DefaultCommMethod",0,0,     &Contact::DefaultCommunicationsMethod, 0, 0, 0, 0, true },
00120    { CFC_ADDRESS1,     "WorkAddress1",   0,0,             0, 0, 0, &Contact::WorkAddress, &PostalAddress::Address1, true },
00121    { CFC_ADDRESS2,     "WorkAddress2",   0,0,             0, 0, 0, &Contact::WorkAddress, &PostalAddress::Address2, true },
00122    { CFC_ADDRESS3,     "WorkAddress3",   0,0,             0, 0, 0, &Contact::WorkAddress, &PostalAddress::Address3, true },
00123    { CFC_CITY,         "WorkCity",       "l",0,           0, 0, 0, &Contact::WorkAddress, &PostalAddress::City, true },
00124    { CFC_PROVINCE,     "WorkProvince",   "st",0,          0, 0, 0, &Contact::WorkAddress, &PostalAddress::Province, true },
00125    { CFC_POSTAL_CODE,  "WorkPostalCode", "postalCode",0,  0, 0, 0, &Contact::WorkAddress, &PostalAddress::PostalCode, true },
00126    { CFC_COUNTRY,      "WorkCountry",    "c", "country",  0, 0, 0, &Contact::WorkAddress, &PostalAddress::Country, true },
00127    { CFC_TITLE,        "JobTitle",   "title",0,           &Contact::JobTitle, 0, 0, 0, 0, true },
00128    { CFC_PUBLIC_KEY,   "PublicKey",  0,0,                 &Contact::PublicKey, 0, 0, 0, 0, false },
00129    { CFC_URL,          "URL",        0,0,                 &Contact::URL, 0, 0, 0, 0, true },
00130    { CFC_PREFIX,       "Prefix",     0,0,                 &Contact::Prefix, 0, 0, 0, 0, true },
00131    { CFC_HOME_ADDRESS1,"HomeAddress1", 0,0,               0, 0, 0, &Contact::HomeAddress, &PostalAddress::Address1, true },
00132    { CFC_HOME_ADDRESS2,"HomeAddress2", 0,0,               0, 0, 0, &Contact::HomeAddress, &PostalAddress::Address2, true },
00133    { CFC_HOME_ADDRESS3,"HomeAddress3", 0,0,               0, 0, 0, &Contact::HomeAddress, &PostalAddress::Address3, true },
00134    { CFC_NOTES,        "Notes",      0,0,                 &Contact::Notes, 0, 0, 0, 0, true },
00135    { CFC_USER_DEFINED_1, "UserDefined1", 0,0,             &Contact::UserDefined1, 0, 0, 0, 0, true },
00136    { CFC_USER_DEFINED_2, "UserDefined2", 0,0,             &Contact::UserDefined2, 0, 0, 0, 0, true },
00137    { CFC_USER_DEFINED_3, "UserDefined3", 0,0,             &Contact::UserDefined3, 0, 0, 0, 0, true },
00138    { CFC_USER_DEFINED_4, "UserDefined4", 0,0,             &Contact::UserDefined4, 0, 0, 0, 0, true },
00139    { CFC_HOME_CITY,    "HomeCity",   0,0,                 0, 0, 0, &Contact::HomeAddress, &PostalAddress::City, true },
00140    { CFC_HOME_PROVINCE,"HomeProvince", 0,0,               0, 0, 0, &Contact::HomeAddress, &PostalAddress::Province, true },
00141    { CFC_HOME_POSTAL_CODE, "HomePostalCode", 0,0,         0, 0, 0, &Contact::HomeAddress, &PostalAddress::PostalCode, true },
00142    { CFC_HOME_COUNTRY, "HomeCountry",0,0,                 0, 0, 0, &Contact::HomeAddress, &PostalAddress::Country, true },
00143    { CFC_IMAGE,        "Image",      0,0,                 &Contact::Image, 0, 0, 0, 0, false },
00144    { CFC_INVALID_FIELD,"EndOfList",  0, 0, 0, 0, 0, 0, 0, false }
00145 };
00146 
00147 Contact::Contact()
00148         : RecType(Contact::GetDefaultRecType()),
00149         RecordId(0),
00150         m_FirstNameSeen(false)
00151 {
00152 }
00153 
00154 Contact::~Contact()
00155 {
00156 }
00157 
00158 const unsigned char* Contact::ParseField(const unsigned char *begin,
00159                                          const unsigned char *end,
00160                                          const IConverter *ic)
00161 {
00162         const CommonField *field = (const CommonField *) begin;
00163 
00164         // advance and check size
00165         begin += COMMON_FIELD_HEADER_SIZE + btohs(field->size);
00166         if( begin > end )               // if begin==end, we are ok
00167                 return begin;
00168 
00169         if( !btohs(field->size) )       // if field has no size, something's up
00170                 return begin;
00171 
00172         // cycle through the type table
00173         for(    FieldLink<Contact> *b = ContactFieldLinks;
00174                 b->type != CFC_INVALID_FIELD;
00175                 b++ )
00176         {
00177                 if( b->type == field->type ) {
00178                         if( b->strMember ) {
00179                                 std::string &s = this->*(b->strMember);
00180                                 s = ParseFieldString(field);
00181                                 if( b->iconvNeeded && ic )
00182                                         s = ic->FromBB(s);
00183                                 return begin;   // done!
00184                         }
00185                         else if( b->postMember && b->postField ) {
00186                                 std::string &s = (this->*(b->postMember)).*(b->postField);
00187                                 s = ParseFieldString(field);
00188                                 if( b->iconvNeeded && ic )
00189                                         s = ic->FromBB(s);
00190                                 return begin;
00191                         }
00192                         else {
00193                                 break;  // fall through to special handling
00194                         }
00195                 }
00196         }
00197 
00198         // if not found in the type table, check for special handling
00199         switch( field->type )
00200         {
00201         case CFC_EMAIL: {
00202                 std::string s = ParseFieldString(field);
00203                 if( ic )
00204                         s = ic->FromBB(s);
00205                 EmailAddresses.push_back( s );
00206                 }
00207                 return begin;
00208 
00209         case CFC_NAME: {
00210                 // can be used multiple times, for first/last names
00211                 std::string *name;
00212                 if( FirstName.size() || m_FirstNameSeen ) {
00213                         // first name already filled, use last name
00214                         name = &LastName;
00215                         m_FirstNameSeen = false;
00216                 }
00217                 else {
00218                         name = &FirstName;
00219                         m_FirstNameSeen = true;
00220                 }
00221 
00222                 *name = ParseFieldString(field);
00223                 if( ic )
00224                         *name = ic->FromBB(*name);
00225                 }
00226                 return begin;
00227 
00228         case CFC_GROUP_LINK:
00229                 // just add the unique ID to the list
00230                 GroupLinks.push_back(
00231                         GroupLink(field->u.link.uniqueId,
00232                                 field->u.link.unknown));
00233                 return begin;
00234 
00235         case CFC_GROUP_FLAG:
00236                 // ignore the group flag... the presense of group link items
00237                 // behaves as the flag in this class
00238                 return begin;
00239 
00240         case CFC_CATEGORY: {
00241                 std::string catstring = ParseFieldString(field);
00242                 if( ic )
00243                         catstring = ic->FromBB(catstring);
00244                 Categories.CategoryStr2List(catstring);
00245                 }
00246                 return begin;
00247 
00248         case CFC_BIRTHDAY: {
00249                 std::string bstring = ParseFieldString(field);
00250                 Birthday.FromBBString(bstring);
00251                 }
00252                 return begin;
00253 
00254         case CFC_ANNIVERSARY: {
00255                 std::string astring = ParseFieldString(field);
00256                 Anniversary.FromBBString(astring);
00257                 }
00258                 return begin;
00259         }
00260 
00261         // if still not handled, add to the Unknowns list
00262         UnknownField uf;
00263         uf.type = field->type;
00264         uf.data.assign((const char*)field->u.raw, btohs(field->size));
00265         Unknowns.push_back(uf);
00266 
00267         // return new pointer for next field
00268         return begin;
00269 }
00270 
00271 void Contact::ParseHeader(const Data &data, size_t &offset)
00272 {
00273         // no header to parse in Contact records
00274 }
00275 
00276 void Contact::ParseFields(const Data &data, size_t &offset, const IConverter *ic)
00277 {
00278         const unsigned char *finish = ParseCommonFields(*this,
00279                 data.GetData() + offset, data.GetData() + data.GetSize(), ic);
00280         offset += finish - (data.GetData() + offset);
00281 }
00282 
00283 void Contact::BuildHeader(Data &data, size_t &offset) const
00284 {
00285         // no header in Contact records
00286 }
00287 
00288 //
00289 // BuildFields
00290 //
00291 /// Build fields part of record
00292 ///
00293 void Contact::BuildFields(Data &data, size_t &offset, const IConverter *ic) const
00294 {
00295         data.Zap();
00296 
00297         // Sanity check: the Blackberry requires at least a name or
00298         // a company name for each address record.
00299         if( !GetFullName().size() && !Company.size() )
00300                 throw BadData("Contact must have name or company name.");
00301 
00302         // check if this is a group link record, and if so, output
00303         // the group flag
00304         if( GroupLinks.size() )
00305                 BuildField(data, offset, CFC_GROUP_FLAG, 'G');
00306 
00307         // special fields not in type table
00308         if( FirstName.size() ) {
00309                 std::string s = ic ? ic->ToBB(FirstName) : FirstName;
00310                 BuildField(data, offset, CFC_NAME, s);
00311         }
00312         if( LastName.size() ) {
00313                 if( !FirstName.size() ) {
00314                         // order matters with first/last name, and if
00315                         // last name exists, and first name doesn't,
00316                         // insert blank first name ahead of it
00317                         BuildField(data, offset, CFC_NAME, "");
00318                 }
00319                 BuildField(data, offset, CFC_NAME, ic ? ic->ToBB(LastName) : LastName);
00320         }
00321 
00322 //      FIXME
00323 //      // add unknown data
00324 //      char buffer[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
00325 //      BuildField(data, offset, 0x54, buffer, 8);
00326 
00327         // With the BlackBerry Storm, I have to add this entry.
00328         // Otherwise the uniqueId of this contact is reseted !
00329         // The device seems accept the multiple contact with the same uniqueId,
00330         // but the synchronization process uses this uniqueId to identify the contact.
00331         // add uniqueId
00332         BuildField(data, offset, CFC_UNIQUEID, RecordId);
00333 
00334         // add all email addresses
00335         EmailList::const_iterator eai = EmailAddresses.begin();
00336         for( ; eai != EmailAddresses.end(); ++eai ) {
00337                 if( eai->size() ) {
00338                         BuildField(data, offset, CFC_EMAIL, ic ? ic->ToBB(*eai) : *eai);
00339                 }
00340         }
00341 
00342         // cycle through the type table
00343         for(    FieldLink<Contact> *b = ContactFieldLinks;
00344                 b->type != CFC_INVALID_FIELD;
00345                 b++ )
00346         {
00347                 // print only fields with data
00348                 if( b->strMember ) {
00349                         const std::string &field = this->*(b->strMember);
00350                         if( field.size() ) {
00351                                 std::string s = (b->iconvNeeded && ic) ? ic->ToBB(field) : field;
00352                                 BuildField(data, offset, b->type, s);
00353                         }
00354                 }
00355                 else if( b->postMember && b->postField ) {
00356                         const std::string &field = (this->*(b->postMember)).*(b->postField);
00357                         if( field.size() ) {
00358                                 std::string s = (b->iconvNeeded && ic) ? ic->ToBB(field) : field;
00359                                 BuildField(data, offset, b->type, s);
00360                         }
00361                 }
00362         }
00363 
00364         // save any group links
00365         GroupLinksType::const_iterator
00366                 gb = GroupLinks.begin(), ge = GroupLinks.end();
00367         for( ; gb != ge; gb++ ) {
00368                 Barry::Protocol::GroupLink link;
00369                 link.uniqueId = htobl(gb->Link);
00370                 link.unknown = htobs(gb->Unknown);
00371                 BuildField(data, offset, CFC_GROUP_LINK, link);
00372         }
00373 
00374         // save categories
00375         if( Categories.size() ) {
00376                 string store;
00377                 Categories.CategoryList2Str(store);
00378                 BuildField(data, offset, CFC_CATEGORY, ic ? ic->ToBB(store) : store);
00379         }
00380 
00381         // save Birthday and Anniversary
00382         if( Birthday.HasData() )
00383                 BuildField(data, offset, CFC_BIRTHDAY, Birthday.ToBBString());
00384         if( Anniversary.HasData() )
00385                 BuildField(data, offset, CFC_ANNIVERSARY, Anniversary.ToBBString());
00386 
00387         // and finally save unknowns
00388         UnknownsType::const_iterator
00389                 ub = Unknowns.begin(), ue = Unknowns.end();
00390         for( ; ub != ue; ub++ ) {
00391                 BuildField(data, offset, *ub);
00392         }
00393 
00394         data.ReleaseBuffer(offset);
00395 }
00396 
00397 void Contact::Clear()
00398 {
00399         RecType = GetDefaultRecType();
00400         RecordId = 0;
00401 
00402         EmailAddresses.clear();
00403         Phone.clear();
00404 
00405         Fax.clear();
00406         HomeFax.clear();
00407         WorkPhone.clear();
00408         HomePhone.clear();
00409         MobilePhone.clear();
00410         MobilePhone2.clear();
00411         Pager.clear();
00412         PIN.clear();
00413         Radio.clear();
00414         WorkPhone2.clear();
00415         HomePhone2.clear();
00416         OtherPhone.clear();
00417         FirstName.clear();
00418         LastName.clear();
00419         Company.clear();
00420         DefaultCommunicationsMethod.clear();
00421         JobTitle.clear();
00422         PublicKey.clear();
00423         URL.clear();
00424         Prefix.clear();
00425         Notes.clear();
00426         UserDefined1.clear();
00427         UserDefined2.clear();
00428         UserDefined3.clear();
00429         UserDefined4.clear();
00430         Image.clear();
00431         Nickname.clear();
00432 
00433         Birthday.Clear();
00434         Anniversary.Clear();
00435 
00436         WorkAddress.Clear();
00437         HomeAddress.Clear();
00438 
00439         Categories.clear();
00440 
00441         GroupLinks.clear();
00442         Unknowns.clear();
00443 
00444         m_FirstNameSeen = false;
00445 }
00446 
00447 //
00448 // GetFullName
00449 //
00450 /// Helper function that returns a formatted full name
00451 ///
00452 std::string Contact::GetFullName() const
00453 {
00454         std::string Full = FirstName;
00455         if( Full.size() && LastName.size() )
00456                 Full += " ";
00457         Full += LastName;
00458         return Full;
00459 }
00460 
00461 //
00462 // GetEmail
00463 //
00464 /// Helper function that always returns a valid string.  The string
00465 /// may be empty if there is no address at the specified index.
00466 ///
00467 const std::string& Contact::GetEmail(unsigned int index) const
00468 {
00469         static const std::string blank;
00470         if( index < EmailAddresses.size() )
00471                 return EmailAddresses[index];
00472         return blank;
00473 }
00474 
00475 void Contact::Dump(std::ostream &os) const
00476 {
00477         ios::fmtflags oldflags = os.setf(ios::left);
00478         char fill = os.fill(' ');
00479 
00480         os << "Contact: 0x" << setbase(16) << GetID()
00481                 << " (" << (unsigned int)RecType << ")\n";
00482 
00483         // special fields not in type table
00484         os << "    " << setw(20) << "FirstName";
00485         os << ": " << FirstName << "\n";
00486         os << "    " << setw(20) << "LastName";
00487         os << ": " << LastName << "\n";
00488 
00489         // cycle through email addresses
00490         EmailList::const_iterator eai = EmailAddresses.begin();
00491         for( ; eai != EmailAddresses.end(); ++eai ) {
00492                 if( eai->size() ) {
00493                         os << "    Email               : " << *eai << "\n";
00494                 }
00495         }
00496 
00497         // cycle through the type table
00498         for(    FieldLink<Contact> *b = ContactFieldLinks;
00499                 b->type != CFC_INVALID_FIELD;
00500                 b++ )
00501         {
00502                 // special case: don't dump the raw image data, but
00503                 // leave that for a special hex dump
00504                 if( b->type == CFC_IMAGE )
00505                         continue;
00506 
00507                 const std::string *pField = 0;
00508                 if( b->strMember ) {
00509                         pField = &(this->*(b->strMember));
00510                 }
00511                 else if( b->postMember && b->postField ) {
00512                         pField = &((this->*(b->postMember)).*(b->postField));
00513                 }
00514 
00515                 // print only fields with data
00516                 if( pField && pField->size() ) {
00517                         os << "    " << setw(20) << b->name;
00518                         os << ": " << Cr2LfWrapper(*pField) << "\n";
00519                 }
00520         }
00521 
00522         if( Categories.size() ) {
00523                 string display;
00524                 Categories.CategoryList2Str(display);
00525                 os << "    Categories          : " << display << "\n";
00526         }
00527 
00528         // print Birthday and Anniversary
00529         if( Birthday.HasData() ) {
00530                 os << "    Birthday            : " << Birthday << "\n";
00531         }
00532         if( Anniversary.HasData() ) {
00533                 os << "    Anniversary         : " << Anniversary << "\n";
00534         }
00535 
00536         // print any group links
00537         GroupLinksType::const_iterator
00538                 gb = GroupLinks.begin(), ge = GroupLinks.end();
00539         if( gb != ge )
00540                 os << "    GroupLinks:\n";
00541         for( ; gb != ge; gb++ ) {
00542                 os << "        ID: 0x" << setbase(16) << gb->Link << "\n";
00543         }
00544 
00545         // print Image in hex dump format, if available
00546         if( Image.size() ) {
00547                 Data image(Image.data(), Image.size());
00548                 os << "    Photo image:\n";
00549                 os << image << "\n";
00550         }
00551 
00552         // and finally print unknowns
00553         os << Unknowns;
00554 
00555         // cleanup the stream
00556         os.flags(oldflags);
00557         os.fill(fill);
00558 }
00559 
00560 bool Contact::operator<(const Contact &other) const
00561 {
00562         // old sorting mechanism, to put group links at the bottom
00563         //return GroupLinks.size() == 0 && other.GroupLinks.size() > 0;
00564         // testing - put group links at the top
00565         //return GroupLinks.size() > 0 && other.GroupLinks.size() == 0;
00566 
00567         // usually one of these fields is filled in, so compare
00568         // them all in a ( LastName + FirstName + Company ) key style
00569         int cmp = LastName.compare(other.LastName);
00570         if( cmp == 0 )
00571                 cmp = FirstName.compare(other.FirstName);
00572         if( cmp == 0 )
00573                 cmp = Company.compare(other.Company);
00574         return cmp < 0;
00575 }
00576 
00577 void Contact::SplitName(const std::string &full, std::string &first, std::string &last)
00578 {
00579         first.clear();
00580         last.clear();
00581 
00582         string::size_type pos = full.find_last_of(' ');
00583         if( pos != string::npos ) {
00584                 // has space, assume last word is last name
00585                 last = full.c_str() + pos + 1;
00586                 first = full.substr(0, pos);
00587         }
00588         else {
00589                 // no space, assume only first name
00590                 first = full.substr(0);
00591         }
00592 }
00593 
00594 } // namespace Barry
00595