/* This file is part of libkabc. Copyright (c) 2001 Cornelius Schumacher Copyright (c) 2003 Carsten Pfeiffer Copyright (c) 2005 Ingo Kloecker This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include "addresseehelper.h" #include "field.h" #include "resource.h" #include "sortmode.h" #include "addressee.h" using namespace KABC; static bool matchBinaryPattern( int value, int pattern ); template static bool listEquals( const QValueList&, const QValueList& ); static bool emailsEquals( const QStringList&, const QStringList& ); KABC::SortMode *Addressee::mSortMode = 0; struct Addressee::AddresseeData : public KShared { QString uid; --VARIABLES-- PhoneNumber::List phoneNumbers; Address::List addresses; Key::List keys; QStringList emails; QStringList categories; QStringList custom; Resource *resource; bool empty :1; bool changed :1; }; Addressee::AddresseeData* Addressee::shared_null = 0; Addressee::AddresseeData* Addressee::makeSharedNull() { Addressee::shared_null = new AddresseeData; shared_null->_KShared_ref(); //just in case (we should add KSD) shared_null->empty = true; shared_null->changed = false; shared_null->resource = 0; return shared_null; } Addressee::Addressee() { mData = shared_null ? shared_null : makeSharedNull(); } Addressee::~Addressee() { } Addressee::Addressee( const Addressee &a ) { mData = a.mData; } Addressee &Addressee::operator=( const Addressee &a ) { if ( this == &a ) return (*this); mData = a.mData; return (*this); } void Addressee::detach() { if ( mData.data() == shared_null ) { mData = new AddresseeData; mData->empty = true; mData->changed = false; mData->resource = 0; mData->uid = KApplication::randomString( 10 ); return; } else if ( mData.count() == 1 ) return; AddresseeData data = *mData; mData = new AddresseeData( data ); } bool Addressee::operator==( const Addressee &a ) const { if ( uid() != a.uid() ) { kdDebug(5700) << "uid differs" << endl; return false; } --EQUALSTEST-- if ( ( mData->url.isValid() || a.mData->url.isValid() ) && ( mData->url != a.mData->url ) ) { kdDebug(5700) << "url differs" << endl; return false; } if ( !listEquals( mData->phoneNumbers, a.mData->phoneNumbers ) ) { kdDebug(5700) << "phoneNumbers differs" << endl; return false; } if ( !listEquals( mData->addresses, a.mData->addresses ) ) { kdDebug(5700) << "addresses differs" << endl; return false; } if ( !listEquals( mData->keys, a.mData->keys ) ) { kdDebug(5700) << "keys differs" << endl; return false; } if ( !emailsEquals( mData->emails, a.mData->emails ) ) { kdDebug(5700) << "emails differs" << endl; return false; } if ( !listEquals( mData->categories, a.mData->categories ) ) { kdDebug(5700) << "categories differs" << endl; return false; } if ( !listEquals( mData->custom, a.mData->custom ) ) { kdDebug(5700) << "custom differs" << endl; return false; } return true; } bool Addressee::operator!=( const Addressee &a ) const { return !( a == *this ); } bool Addressee::isEmpty() const { return mData->empty; } void Addressee::setUid( const QString &id ) { if ( id == mData->uid ) return; detach(); mData->empty = false; mData->uid = id; } QString Addressee::uid() const { return mData->uid; } QString Addressee::uidLabel() { return i18n("Unique Identifier"); } --DEFINITIONS-- void Addressee::setNameFromString( const QString &s ) { QString str = s; //remove enclosing quotes from string if ( str.length() > 1 && s[ 0 ] == '"' && s[ s.length() - 1 ] == '"' ) str = s.mid( 1, s.length() - 2 ); setFormattedName( str ); setName( str ); // clear all name parts setPrefix( QString::null ); setGivenName( QString::null ); setAdditionalName( QString::null ); setFamilyName( QString::null ); setSuffix( QString::null ); if ( str.isEmpty() ) return; QString spaceStr = " "; QString emptyStr = ""; AddresseeHelper *helper = AddresseeHelper::self(); int i = str.find( ',' ); if( i < 0 ) { QStringList parts = QStringList::split( spaceStr, str ); int leftOffset = 0; int rightOffset = parts.count() - 1; QString suffix; while ( rightOffset >= 0 ) { if ( helper->containsSuffix( parts[ rightOffset ] ) ) { suffix.prepend(parts[ rightOffset ] + (suffix.isEmpty() ? emptyStr : spaceStr)); rightOffset--; } else break; } setSuffix( suffix ); if ( rightOffset < 0 ) return; QStringList inclusionList; for ( int n = 1; (rightOffset - n >= 0) && (n < 4); ++n ) { if ( helper->containsPrefix( parts[ rightOffset - n ].lower() ) ) { inclusionList.prepend( parts[ rightOffset - n ] ); } else break; } if ( !inclusionList.isEmpty() ) { setFamilyName( inclusionList.join( " " ) + spaceStr + parts[ rightOffset ] ); rightOffset -= inclusionList.count(); } else { if ( helper->tradeAsFamilyName() ) setFamilyName( parts[ rightOffset ] ); else setGivenName( parts[ rightOffset ] ); } QString prefix; while ( leftOffset < rightOffset ) { if ( helper->containsTitle( parts[ leftOffset ] ) ) { prefix.append( ( prefix.isEmpty() ? emptyStr : spaceStr) + parts[ leftOffset ] ); leftOffset++; } else break; } setPrefix( prefix ); if ( leftOffset < rightOffset ) { setGivenName( parts[ leftOffset ] ); leftOffset++; } QString additionalName; while ( leftOffset < rightOffset ) { additionalName.append( ( additionalName.isEmpty() ? emptyStr : spaceStr) + parts[ leftOffset ] ); leftOffset++; } setAdditionalName( additionalName ); } else { QString part1 = str.left( i ); QString part2 = str.mid( i + 1 ); QStringList parts = QStringList::split( spaceStr, part1 ); int leftOffset = 0; int rightOffset = parts.count() - 1; if ( parts.count() > 0 ) { QString suffix; while ( rightOffset >= 0 ) { if ( helper->containsSuffix( parts[ rightOffset ] ) ) { suffix.prepend(parts[ rightOffset ] + (suffix.isEmpty() ? emptyStr : spaceStr)); rightOffset--; } else break; } setSuffix( suffix ); if ( rightOffset - 1 >= 0 && helper->containsPrefix( parts[ rightOffset - 1 ].lower() ) ) { setFamilyName( parts[ rightOffset - 1 ] + spaceStr + parts[ rightOffset ] ); rightOffset--; } else setFamilyName( parts[ rightOffset ] ); QString prefix; while ( leftOffset < rightOffset ) { if ( helper->containsTitle( parts[ leftOffset ] ) ) { prefix.append( ( prefix.isEmpty() ? emptyStr : spaceStr) + parts[ leftOffset ] ); leftOffset++; } else break; } } else { setPrefix( "" ); setFamilyName( "" ); setSuffix( "" ); } parts = QStringList::split( spaceStr, part2 ); leftOffset = 0; rightOffset = parts.count(); if ( parts.count() > 0 ) { QString prefix; while ( leftOffset < rightOffset ) { if ( helper->containsTitle( parts[ leftOffset ] ) ) { prefix.append( ( prefix.isEmpty() ? emptyStr : spaceStr) + parts[ leftOffset ] ); leftOffset++; } else break; } setPrefix( prefix ); if ( leftOffset < rightOffset ) { setGivenName( parts[ leftOffset ] ); leftOffset++; } QString additionalName; while ( leftOffset < rightOffset ) { additionalName.append( ( additionalName.isEmpty() ? emptyStr : spaceStr) + parts[ leftOffset ] ); leftOffset++; } setAdditionalName( additionalName ); } else { setGivenName( "" ); setAdditionalName( "" ); } } } QString Addressee::realName() const { QString n( formattedName() ); if ( !n.isEmpty() ) return n; n = assembledName(); if ( !n.isEmpty() ) return n; n = name(); if ( !n.isEmpty() ) return n; return organization(); } QString Addressee::assembledName() const { QString name = prefix() + " " + givenName() + " " + additionalName() + " " + familyName() + " " + suffix(); return name.simplifyWhiteSpace(); } QString Addressee::fullEmail( const QString &email ) const { QString e; if ( email.isNull() ) { e = preferredEmail(); } else { e = email; } if ( e.isEmpty() ) return QString::null; QString text; if ( realName().isEmpty() ) text = e; else { QRegExp needQuotes( "[^ 0-9A-Za-z\\x0080-\\xFFFF]" ); if ( realName().find( needQuotes ) != -1 ) { QString name = realName(); name.replace( "\"", "\\\"" ); text = "\"" + name + "\" <" + e + ">"; } else text = realName() + " <" + e + ">"; } return text; } void Addressee::insertEmail( const QString &email, bool preferred ) { if ( email.simplifyWhiteSpace().isEmpty() ) return; detach(); mData->empty = false; QStringList::Iterator it = mData->emails.find( email ); if ( it != mData->emails.end() ) { if ( !preferred || it == mData->emails.begin() ) return; mData->emails.remove( it ); mData->emails.prepend( email ); } else { if ( preferred ) { mData->emails.prepend( email ); } else { mData->emails.append( email ); } } } void Addressee::removeEmail( const QString &email ) { detach(); QStringList::Iterator it = mData->emails.find( email ); if ( it == mData->emails.end() ) return; mData->emails.remove( it ); } QString Addressee::preferredEmail() const { if ( mData->emails.count() == 0 ) return QString::null; else return mData->emails.first(); } QStringList Addressee::emails() const { return mData->emails; } void Addressee::setEmails( const QStringList& emails ) { detach(); mData->emails = emails; } void Addressee::insertPhoneNumber( const PhoneNumber &phoneNumber ) { detach(); mData->empty = false; PhoneNumber::List::Iterator it; for( it = mData->phoneNumbers.begin(); it != mData->phoneNumbers.end(); ++it ) { if ( (*it).id() == phoneNumber.id() ) { *it = phoneNumber; return; } } if ( !phoneNumber.number().simplifyWhiteSpace().isEmpty() ) mData->phoneNumbers.append( phoneNumber ); } void Addressee::removePhoneNumber( const PhoneNumber &phoneNumber ) { detach(); PhoneNumber::List::Iterator it; for( it = mData->phoneNumbers.begin(); it != mData->phoneNumbers.end(); ++it ) { if ( (*it).id() == phoneNumber.id() ) { mData->phoneNumbers.remove( it ); return; } } } PhoneNumber Addressee::phoneNumber( int type ) const { PhoneNumber phoneNumber( "", type ); PhoneNumber::List::ConstIterator it; for( it = mData->phoneNumbers.constBegin(); it != mData->phoneNumbers.constEnd(); ++it ) { if ( matchBinaryPattern( (*it).type(), type ) ) { if ( (*it).type() & PhoneNumber::Pref ) return (*it); else if ( phoneNumber.number().isEmpty() ) phoneNumber = (*it); } } return phoneNumber; } PhoneNumber::List Addressee::phoneNumbers() const { return mData->phoneNumbers; } PhoneNumber::List Addressee::phoneNumbers( int type ) const { PhoneNumber::List list; PhoneNumber::List::ConstIterator it; for( it = mData->phoneNumbers.constBegin(); it != mData->phoneNumbers.constEnd(); ++it ) { if ( matchBinaryPattern( (*it).type(), type ) ) { list.append( *it ); } } return list; } PhoneNumber Addressee::findPhoneNumber( const QString &id ) const { PhoneNumber::List::ConstIterator it; for( it = mData->phoneNumbers.constBegin(); it != mData->phoneNumbers.constEnd(); ++it ) { if ( (*it).id() == id ) { return *it; } } return PhoneNumber(); } void Addressee::insertKey( const Key &key ) { detach(); mData->empty = false; Key::List::Iterator it; for( it = mData->keys.begin(); it != mData->keys.end(); ++it ) { if ( (*it).id() == key.id() ) { *it = key; return; } } mData->keys.append( key ); } void Addressee::removeKey( const Key &key ) { detach(); Key::List::Iterator it; for( it = mData->keys.begin(); it != mData->keys.end(); ++it ) { if ( (*it).id() == key.id() ) { mData->keys.remove( key ); return; } } } Key Addressee::key( int type, QString customTypeString ) const { Key::List::ConstIterator it; for( it = mData->keys.constBegin(); it != mData->keys.constEnd(); ++it ) { if ( (*it).type() == type ) { if ( type == Key::Custom ) { if ( customTypeString.isEmpty() ) { return *it; } else { if ( (*it).customTypeString() == customTypeString ) return (*it); } } else { return *it; } } } return Key( QString(), type ); } void Addressee::setKeys( const Key::List& list ) { detach(); mData->keys = list; } Key::List Addressee::keys() const { return mData->keys; } Key::List Addressee::keys( int type, QString customTypeString ) const { Key::List list; Key::List::ConstIterator it; for( it = mData->keys.constBegin(); it != mData->keys.constEnd(); ++it ) { if ( (*it).type() == type ) { if ( type == Key::Custom ) { if ( customTypeString.isEmpty() ) { list.append( *it ); } else { if ( (*it).customTypeString() == customTypeString ) list.append( *it ); } } else { list.append( *it ); } } } return list; } Key Addressee::findKey( const QString &id ) const { Key::List::ConstIterator it; for( it = mData->keys.constBegin(); it != mData->keys.constEnd(); ++it ) { if ( (*it).id() == id ) { return *it; } } return Key(); } QString Addressee::asString() const { return "Smith, agent Smith..."; } void Addressee::dump() const { kdDebug(5700) << "Addressee {" << endl; kdDebug(5700) << " Uid: '" << uid() << "'" << endl; --DEBUG-- kdDebug(5700) << " Emails {" << endl; const QStringList e = emails(); QStringList::ConstIterator it; for( it = e.begin(); it != e.end(); ++it ) { kdDebug(5700) << " " << (*it) << endl; } kdDebug(5700) << " }" << endl; kdDebug(5700) << " PhoneNumbers {" << endl; const PhoneNumber::List p = phoneNumbers(); PhoneNumber::List::ConstIterator it2; for( it2 = p.begin(); it2 != p.end(); ++it2 ) { kdDebug(5700) << " Type: " << int((*it2).type()) << " Number: " << (*it2).number() << endl; } kdDebug(5700) << " }" << endl; const Address::List a = addresses(); Address::List::ConstIterator it3; for( it3 = a.begin(); it3 != a.end(); ++it3 ) { (*it3).dump(); } kdDebug(5700) << " Keys {" << endl; const Key::List k = keys(); Key::List::ConstIterator it4; for( it4 = k.begin(); it4 != k.end(); ++it4 ) { kdDebug(5700) << " Type: " << int((*it4).type()) << " Key: " << (*it4).textData() << " CustomString: " << (*it4).customTypeString() << endl; } kdDebug(5700) << " }" << endl; kdDebug(5700) << "}" << endl; } void Addressee::insertAddress( const Address &address ) { if ( address.isEmpty() ) return; detach(); mData->empty = false; Address::List::Iterator it; for( it = mData->addresses.begin(); it != mData->addresses.end(); ++it ) { if ( (*it).id() == address.id() ) { *it = address; return; } } mData->addresses.append( address ); } void Addressee::removeAddress( const Address &address ) { detach(); Address::List::Iterator it; for( it = mData->addresses.begin(); it != mData->addresses.end(); ++it ) { if ( (*it).id() == address.id() ) { mData->addresses.remove( it ); return; } } } Address Addressee::address( int type ) const { Address address( type ); Address::List::ConstIterator it; for( it = mData->addresses.constBegin(); it != mData->addresses.constEnd(); ++it ) { if ( matchBinaryPattern( (*it).type(), type ) ) { if ( (*it).type() & Address::Pref ) return (*it); else if ( address.isEmpty() ) address = (*it); } } return address; } Address::List Addressee::addresses() const { return mData->addresses; } Address::List Addressee::addresses( int type ) const { Address::List list; Address::List::ConstIterator it; for( it = mData->addresses.constBegin(); it != mData->addresses.constEnd(); ++it ) { if ( matchBinaryPattern( (*it).type(), type ) ) { list.append( *it ); } } return list; } Address Addressee::findAddress( const QString &id ) const { Address::List::ConstIterator it; for( it = mData->addresses.constBegin(); it != mData->addresses.constEnd(); ++it ) { if ( (*it).id() == id ) { return *it; } } return Address(); } void Addressee::insertCategory( const QString &c ) { detach(); mData->empty = false; if ( mData->categories.findIndex( c ) != -1 ) return; mData->categories.append( c ); } void Addressee::removeCategory( const QString &c ) { detach(); QStringList::Iterator it = mData->categories.find( c ); if ( it == mData->categories.end() ) return; mData->categories.remove( it ); } bool Addressee::hasCategory( const QString &c ) const { return ( mData->categories.findIndex( c ) != -1 ); } void Addressee::setCategories( const QStringList &c ) { detach(); mData->empty = false; mData->categories = c; } QStringList Addressee::categories() const { return mData->categories; } void Addressee::insertCustom( const QString &app, const QString &name, const QString &value ) { if ( value.isEmpty() || name.isEmpty() || app.isEmpty() ) return; detach(); mData->empty = false; QString qualifiedName = app + "-" + name + ":"; QStringList::Iterator it; for( it = mData->custom.begin(); it != mData->custom.end(); ++it ) { if ( (*it).startsWith( qualifiedName ) ) { (*it) = qualifiedName + value; return; } } mData->custom.append( qualifiedName + value ); } void Addressee::removeCustom( const QString &app, const QString &name) { detach(); QString qualifiedName = app + "-" + name + ":"; QStringList::Iterator it; for( it = mData->custom.begin(); it != mData->custom.end(); ++it ) { if ( (*it).startsWith( qualifiedName ) ) { mData->custom.remove( it ); return; } } } QString Addressee::custom( const QString &app, const QString &name ) const { QString qualifiedName = app + "-" + name + ":"; QString value; QStringList::ConstIterator it; for( it = mData->custom.constBegin(); it != mData->custom.constEnd(); ++it ) { if ( (*it).startsWith( qualifiedName ) ) { value = (*it).mid( (*it).find( ":" ) + 1 ); break; } } return value; } void Addressee::setCustoms( const QStringList &l ) { detach(); mData->empty = false; mData->custom = l; } QStringList Addressee::customs() const { return mData->custom; } void Addressee::parseEmailAddress( const QString &rawEmail, QString &fullName, QString &email) { // This is a simplified version of KPIM::splitAddress(). fullName = ""; email = ""; if ( rawEmail.isEmpty() ) return; // KPIM::AddressEmpty; // The code works on 8-bit strings, so convert the input to UTF-8. QCString address = rawEmail.utf8(); QCString displayName; QCString addrSpec; QCString comment; // The following is a primitive parser for a mailbox-list (cf. RFC 2822). // The purpose is to extract a displayable string from the mailboxes. // Comments in the addr-spec are not handled. No error checking is done. enum { TopLevel, InComment, InAngleAddress } context = TopLevel; bool inQuotedString = false; int commentLevel = 0; bool stop = false; for ( char* p = address.data(); *p && !stop; ++p ) { switch ( context ) { case TopLevel : { switch ( *p ) { case '"' : inQuotedString = !inQuotedString; displayName += *p; break; case '(' : if ( !inQuotedString ) { context = InComment; commentLevel = 1; } else displayName += *p; break; case '<' : if ( !inQuotedString ) { context = InAngleAddress; } else displayName += *p; break; case '\\' : // quoted character displayName += *p; ++p; // skip the '\' if ( *p ) displayName += *p; else //return KPIM::UnexpectedEnd; goto ABORT_PARSING; break; case ',' : if ( !inQuotedString ) { //if ( allowMultipleAddresses ) // stop = true; //else // return KPIM::UnexpectedComma; goto ABORT_PARSING; } else displayName += *p; break; default : displayName += *p; } break; } case InComment : { switch ( *p ) { case '(' : ++commentLevel; comment += *p; break; case ')' : --commentLevel; if ( commentLevel == 0 ) { context = TopLevel; comment += ' '; // separate the text of several comments } else comment += *p; break; case '\\' : // quoted character comment += *p; ++p; // skip the '\' if ( *p ) comment += *p; else //return KPIM::UnexpectedEnd; goto ABORT_PARSING; break; default : comment += *p; } break; } case InAngleAddress : { switch ( *p ) { case '"' : inQuotedString = !inQuotedString; addrSpec += *p; break; case '>' : if ( !inQuotedString ) { context = TopLevel; } else addrSpec += *p; break; case '\\' : // quoted character addrSpec += *p; ++p; // skip the '\' if ( *p ) addrSpec += *p; else //return KPIM::UnexpectedEnd; goto ABORT_PARSING; break; default : addrSpec += *p; } break; } } // switch ( context ) } ABORT_PARSING: displayName = displayName.stripWhiteSpace(); comment = comment.stripWhiteSpace(); addrSpec = addrSpec.stripWhiteSpace(); fullName = QString::fromUtf8( displayName ); email = QString::fromUtf8( addrSpec ); // check for errors if ( inQuotedString ) return; // KPIM::UnbalancedQuote; if ( context == InComment ) return; // KPIM::UnbalancedParens; if ( context == InAngleAddress ) return; // KPIM::UnclosedAngleAddr; if ( addrSpec.isEmpty() ) { if ( displayName.isEmpty() ) return; // KPIM::NoAddressSpec; else { //addrSpec = displayName; //displayName.truncate( 0 ); // Address of the form "foo@bar" or "foo@bar (Name)". email = fullName; fullName = QString::fromUtf8( comment ); } } // Check that we do not have any extra characters on the end of the // strings unsigned int len = fullName.length(); if ( fullName[ 0 ] == '"' && fullName[ len - 1 ] == '"' ) fullName = fullName.mid( 1, len - 2 ); } void Addressee::setResource( Resource *resource ) { detach(); mData->resource = resource; } Resource *Addressee::resource() const { return mData->resource; } void Addressee::setChanged( bool value ) { detach(); mData->changed = value; } bool Addressee::changed() const { return mData->changed; } void Addressee::setSortMode( KABC::SortMode *mode ) { mSortMode = mode; } bool Addressee::operator< ( const Addressee &addr ) { if ( !mSortMode ) return false; else return mSortMode->lesser( *this, addr ); } QDataStream &KABC::operator<<( QDataStream &s, const Addressee &a ) { if (!a.mData) return s; s << a.uid(); --STREAMOUT-- s << a.mData->phoneNumbers; s << a.mData->addresses; s << a.mData->emails; s << a.mData->categories; s << a.mData->custom; s << a.mData->keys; return s; } QDataStream &KABC::operator>>( QDataStream &s, Addressee &a ) { if (!a.mData) return s; a.detach(); s >> a.mData->uid; --STREAMIN-- s >> a.mData->phoneNumbers; s >> a.mData->addresses; s >> a.mData->emails; s >> a.mData->categories; s >> a.mData->custom; s >> a.mData->keys; a.mData->empty = false; return s; } bool matchBinaryPattern( int value, int pattern ) { /** We want to match all telephonnumbers/addresses which have the bits in the pattern set. More are allowed. if pattern == 0 we have a special handling, then we want only those with exactly no bit set. */ if ( pattern == 0 ) return ( value == 0 ); else return ( pattern == ( pattern & value ) ); } template bool listEquals( const QValueList &list, const QValueList &pattern ) { if ( list.count() != pattern.count() ) return false; for ( uint i = 0; i < list.count(); ++i ) if ( pattern.find( list[ i ] ) == pattern.end() ) return false; return true; } bool emailsEquals( const QStringList &list, const QStringList &pattern ) { if ( list.count() != pattern.count() ) return false; if ( list.first() != pattern.first() ) return false; QStringList::ConstIterator it; for ( it = list.begin(); it != list.end(); ++it ) if ( pattern.find( *it ) == pattern.end() ) return false; return true; }