kabc Library API Documentation

ldifconverter.cpp

00001 /*
00002     This file is part of libkabc.
00003     Copyright (c) 2003  Helge Deller <deller@kde.org>
00004 
00005     This library is free software; you can redistribute it and/or
00006     modify it under the terms of the GNU Library General Public
00007     License as published by the Free Software Foundation; either
00008     version 2 of the License, or (at your option) any later version.
00009 
00010     This library is distributed in the hope that it will be useful,
00011     but WITHOUT ANY WARRANTY; without even the implied warranty of
00012     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013     Library General Public License for more details.
00014 
00015     You should have received a copy of the GNU Library General Public License
00016     along with this library; see the file COPYING.LIB.  If not, write to
00017     the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00018     Boston, MA 02111-1307, USA.
00019 */
00020 
00021 
00022 /*
00023     Useful links:
00024         - http://tldp.org/HOWTO/LDAP-Implementation-HOWTO/schemas.html
00025         - http://www.faqs.org/rfcs/rfc2849.html
00026 
00027     Not yet handled items:
00028         - objectclass microsoftaddressbook
00029                 - info,
00030                 - initials,
00031                 - otherfacsimiletelephonenumber,
00032                 - otherpager,
00033                 - physicaldeliveryofficename,
00034 */
00035 
00036 #include <qstring.h>
00037 #include <qstringlist.h>
00038 #include <qregexp.h>
00039 #include <qtextstream.h>
00040 
00041 #include <klocale.h>
00042 #include <kdebug.h>
00043 #include <kmdcodec.h>
00044 
00045 #include "addressee.h"
00046 #include "address.h"
00047 
00048 #include "ldifconverter.h"
00049 #include "vcardconverter.h"
00050 
00051 using namespace KABC;
00052 
00053 /* generate LDIF stream */
00054 
00055 bool LDIFConverter::addresseeToLDIF( const AddresseeList &addrList, QString &str )
00056 {
00057   AddresseeList::ConstIterator it;
00058   for ( it = addrList.begin(); it != addrList.end(); ++it ) {
00059     addresseeToLDIF( *it, str );
00060   }
00061   return true;
00062 }
00063 
00064 QString LDIFConverter::makeLDIFfieldString( QString formatStr, QString value, bool allowEncode )
00065 {
00066   if ( value.isEmpty() )
00067     return QString();
00068 
00069   // append format if not given
00070   if (formatStr.find(':') == -1)
00071     formatStr.append(": %1\n");
00072 
00073   // check if base64-encoding is needed 
00074   bool printable = true;
00075   unsigned int i, len;
00076   len = value.length();
00077   for (i = 0; i<len; ++i ) {
00078      if (!value[i].isPrint()) {
00079         printable = false;
00080         break;
00081      }
00082   }
00083 
00084   if (printable) // always encode if we find special chars...
00085     printable = (value.find('\n') == -1);
00086 
00087   if (!printable && allowEncode) { 
00088     // encode to base64
00089     value = KCodecs::base64Encode( value.utf8() );
00090     int p = formatStr.find(':');
00091     if (p>=0)
00092       formatStr.insert(p, ':');
00093   }
00094 
00095   // generate the new string and split it to 72 chars/line
00096   QCString txt = (formatStr.arg(value)).utf8();
00097 
00098   if (allowEncode) {
00099     len = txt.length();
00100     if (len && txt[len-1] == '\n')
00101       --len;
00102     i = 72;
00103     while (i < len) {
00104       txt.insert(i, "\n ");
00105       i += 72+1;
00106       len += 2;
00107     }
00108   }
00109 
00110   return QString::fromUtf8(txt);
00111 }
00112 
00113 
00114 
00115 static void ldif_out( QTextStream &t, QString formatStr, QString value, bool allowEncode = true )
00116 {
00117   if ( value.isEmpty() )
00118     return;
00119 
00120   QString txt = LDIFConverter::makeLDIFfieldString( formatStr, value, allowEncode );
00121 
00122   // write the string
00123   t << txt;
00124 }
00125 
00126 
00127 bool LDIFConverter::addresseeToLDIF( const Addressee &addr, QString &str )
00128 {
00129   if ( addr.isEmpty() )
00130       return false;
00131 
00132   QTextStream t( str, IO_WriteOnly|IO_Append );
00133   t.setEncoding( QTextStream::UnicodeUTF8 );
00134 
00135   const Address homeAddr = addr.address( Address::Home );
00136   const Address workAddr = addr.address( Address::Work );
00137 
00138   ldif_out( t, "dn: %1", QString( "cn=%1,mail=%2\n" )
00139             .arg( addr.formattedName().simplifyWhiteSpace() )
00140             .arg( addr.preferredEmail() ), false /*never encode!*/ );
00141   ldif_out( t, "givenname: %1\n", addr.givenName() );
00142   ldif_out( t, "sn: %1\n", addr.familyName() );
00143   ldif_out( t, "cn: %1\n", addr.formattedName().simplifyWhiteSpace() );
00144   ldif_out( t, "uid: %1\n", addr.uid() );
00145   ldif_out( t, "nickname: %1\n", addr.nickName() );
00146   ldif_out( t, "xmozillanickname: %1\n", addr.nickName() );
00147 
00148   ldif_out( t, "mail: %1\n", addr.preferredEmail() );
00149   if ( addr.emails().count() > 1 )
00150     ldif_out( t, "mozillasecondemail: %1\n", addr.emails()[ 1 ] );
00151 //ldif_out( t, "mozilla_AIMScreenName: %1\n", "screen_name" );
00152 
00153   ldif_out( t, "telephonenumber: %1\n", addr.phoneNumber( PhoneNumber::Work ).number() );
00154   ldif_out( t, "facsimiletelephonenumber: %1\n", addr.phoneNumber( PhoneNumber::Fax ).number() );
00155   ldif_out( t, "homephone: %1\n", addr.phoneNumber( PhoneNumber::Home ).number() );
00156   ldif_out( t, "mobile: %1\n", addr.phoneNumber( PhoneNumber::Cell ).number() ); // Netscape 7
00157   ldif_out( t, "cellphone: %1\n", addr.phoneNumber( PhoneNumber::Cell ).number() ); // Netscape 4.x
00158   ldif_out( t, "pager: %1\n", addr.phoneNumber( PhoneNumber::Pager ).number() );
00159   ldif_out( t, "pagerphone: %1\n", addr.phoneNumber( PhoneNumber::Pager ).number() );
00160 
00161   ldif_out( t, "streethomeaddress: %1\n", homeAddr.street() );
00162   ldif_out( t, "postalcode: %1\n", workAddr.postalCode() );
00163   ldif_out( t, "postofficebox: %1\n", workAddr.postOfficeBox() );
00164 
00165   QStringList streets = QStringList::split( '\n', homeAddr.street() );
00166   if ( streets.count() > 0 )
00167     ldif_out( t, "homepostaladdress: %1\n", streets[ 0 ] ); // Netscape 7
00168   if ( streets.count() > 1 )
00169     ldif_out( t, "mozillahomepostaladdress2: %1\n", streets[ 1 ] ); // Netscape 7
00170   ldif_out( t, "mozillahomelocalityname: %1\n", homeAddr.locality() ); // Netscape 7
00171   ldif_out( t, "mozillahomestate: %1\n", homeAddr.region() );
00172   ldif_out( t, "mozillahomepostalcode: %1\n", homeAddr.postalCode() );
00173   ldif_out( t, "mozillahomecountryname: %1\n", Address::ISOtoCountry(homeAddr.country()) );
00174   ldif_out( t, "locality: %1\n", workAddr.locality() );
00175   ldif_out( t, "streetaddress: %1\n", workAddr.street() ); // Netscape 4.x
00176 
00177   streets = QStringList::split( '\n', workAddr.street() );
00178   if ( streets.count() > 0 )
00179     ldif_out( t, "postaladdress: %1\n", streets[ 0 ] );
00180   if ( streets.count() > 1 )
00181     ldif_out( t, "mozillapostaladdress2: %1\n", streets[ 1 ] );
00182   ldif_out( t, "countryname: %1\n", Address::ISOtoCountry(workAddr.country()) );
00183   ldif_out( t, "l: %1\n", workAddr.locality() );
00184   ldif_out( t, "c: %1\n", Address::ISOtoCountry(workAddr.country()) );
00185   ldif_out( t, "st: %1\n", workAddr.region() );
00186 
00187   ldif_out( t, "title: %1\n", addr.title() );
00188   ldif_out( t, "vocation: %1\n", addr.prefix() );
00189   ldif_out( t, "ou: %1\n", addr.role() );
00190   ldif_out( t, "o: %1\n", addr.organization() );
00191   ldif_out( t, "organization: %1\n", addr.organization() );
00192   ldif_out( t, "organizationname: %1\n", addr.organization() );
00193   ldif_out( t, "department: %1\n", addr.custom("KADDRESSBOOK", "X-Department") );
00194   ldif_out( t, "workurl: %1\n", addr.url().prettyURL() );
00195   ldif_out( t, "homeurl: %1\n", addr.url().prettyURL() );
00196   ldif_out( t, "description: %1\n", addr.note() );
00197   if (addr.revision().isValid())
00198     ldif_out(t, "modifytimestamp: %1\n", dateToVCardString( addr.revision()) );
00199 
00200   t << "objectclass: top\n";
00201   t << "objectclass: person\n";
00202   t << "objectclass: organizationalPerson\n";
00203 
00204   t << "\n";
00205 
00206   return true;
00207 }
00208 
00209 
00210 /* convert from LDIF stream */
00211 
00212 bool LDIFConverter::LDIFToAddressee( const QString &str, AddresseeList &addrList, QDateTime dt )
00213 {
00214   QStringList lines;
00215 
00216   if (!dt.isValid())
00217     dt = QDateTime::currentDateTime();
00218 
00219   lines = QStringList::split( QRegExp("[\x0d\x0a]"), str, false );
00220 
00221   // clean up comments and prepare folded entries and multi-line BASE64 encoded lines
00222   QStringList::Iterator last = lines.end();
00223   for ( QStringList::Iterator it = lines.begin(); it != lines.end(); ++it ) {
00224     if ( (*it).startsWith("#") ) { // comment ?
00225       it = lines.remove(it);
00226       it--;
00227       continue;
00228     }
00229     if ( last == lines.end() ) {
00230       last = it;
00231       continue;
00232     }
00233     if ((*last).find("::")!=-1 && (*it).find(":")==-1) { // this is a multi-line BASE64
00234       *last += (*it);
00235       lines.remove(it);
00236       it = last;
00237       continue;
00238     }
00239     if ((*last).find(":")!=-1 && (*it).startsWith(" ")) { // this is a folded item
00240       *last += (*it).mid(1);
00241       lines.remove(it);
00242       it = last;
00243       continue;
00244     }
00245     last = it;
00246   }
00247 
00248   // variables
00249   addrList = AddresseeList();
00250 
00251   Addressee a;
00252   Address homeAddr, workAddr;
00253   bool cont;
00254   QStringList dnList;
00255   QString dnEntry;
00256 
00257   // do the loop...
00258   for ( QStringList::Iterator it = lines.begin(); it != lines.end(); ++it ) {
00259 
00260     // create a new (empty) address entry
00261     a = Addressee();
00262     a.setRevision(dt);
00263     homeAddr = Address( Address::Home );
00264     workAddr = Address( Address::Work );
00265 
00266     // evaluate previous "dn: *" header values
00267     if (dnList.count()) {
00268       for ( QStringList::Iterator dne = dnList.begin(); dne != dnList.end(); ++dne ) {
00269          parseSingleLine( a, homeAddr, workAddr, *dne );
00270       }
00271     }
00272 
00273     // evaluate until we find another "dn: *" entry or until end of list
00274     do {
00275       cont = parseSingleLine( a, homeAddr, workAddr, *it );
00276       if (cont && it!=lines.end()) {
00277         ++it;
00278       }
00279     } while (cont && it!=lines.end());
00280 
00281     // if the new address is not empty, append it
00282     if ( !a.formattedName().isEmpty() || !a.name().isEmpty() || 
00283          !a.familyName().isEmpty() ) {
00284       if ( !homeAddr.isEmpty() )
00285         a.insertAddress( homeAddr );
00286       if ( !workAddr.isEmpty() )
00287         a.insertAddress( workAddr );
00288       addrList.append( a );
00289     }
00290 
00291     // did we reached the end of the list
00292     if ( it == lines.end() )
00293       break;
00294 
00295     // we found the "dn: cn=.." entry (e.g. "n: cn=Engelhardt Gerald,l=Frankfurt,ou=BKG,o=Bund,c=DE").
00296     // Split it now and parse it later.
00297     dnEntry = (*it).replace( '=', ": " );
00298     dnList = QStringList::split( ',', dnEntry, false );
00299     dnList.pop_front();
00300 
00301   } // for()...
00302 
00303   return true;
00304 }
00305 
00306 bool LDIFConverter::parseSingleLine( Addressee &a, Address &homeAddr,
00307                                      Address &workAddr, QString &line )
00308 {
00309   if ( line.isEmpty() )
00310     return true;
00311 
00312   QString fieldname, value;
00313   splitLine( line, fieldname, value);
00314   return evaluatePair( a, homeAddr, workAddr, fieldname, value);
00315 }
00316 
00317 
00318 bool LDIFConverter::splitLine( QString &line, QString &fieldname, QString &value)
00319 {
00320   int position;
00321 
00322   position = line.find( "::" );
00323   if ( position != -1 ) {
00324     // String is BASE64 encoded -> decode it now.
00325     fieldname = line.left( position ).lower();
00326     value = QString::fromUtf8( KCodecs::base64Decode(
00327               line.mid( position + 3, line.length() - position - 2 ).latin1() ) )
00328               .simplifyWhiteSpace();
00329     return true;
00330   }
00331 
00332   position = line.find( ":" );
00333   if ( position != -1 ) {
00334     fieldname = line.left( position ).lower();
00335     // Convert Utf8 string to unicode so special characters are preserved
00336     // We need this since we are reading normal strings from the file
00337     // which are not converted automatically
00338     value = QString::fromUtf8( line.mid( position + 2, line.length() - position - 2 ).latin1() );
00339     return true;
00340   }
00341 
00342   // strange: we did not find a fieldname
00343   fieldname = "";
00344   value = line;
00345   return true;
00346 }
00347 
00348 
00349 bool LDIFConverter::evaluatePair( Addressee &a, Address &homeAddr,
00350                                   Address &workAddr,
00351                                   QString &fieldname, QString &value )
00352 {
00353   if ( fieldname == QString::fromLatin1( "dn" ) ) // ignore & return false!
00354     return false;
00355 
00356   if ( fieldname.startsWith("#") ) {
00357     return true;
00358   }
00359 
00360   if ( fieldname.isEmpty() && !a.note().isEmpty() ) {
00361     // some LDIF export filters are borken and add additional
00362     // comments on stand-alone lines. Just add them to the notes for now.
00363     a.setNote( a.note() + "\n" + value );
00364     return true;
00365   }
00366 
00367   if ( fieldname == QString::fromLatin1( "givenname" ) ) {
00368     a.setGivenName( value );
00369     return true;
00370   }
00371 
00372   if ( fieldname == QString::fromLatin1( "xmozillanickname") || 
00373        fieldname == QString::fromLatin1( "nickname") ) {
00374     a.setNickName( value );
00375     return true;
00376   }
00377 
00378   if ( fieldname == QString::fromLatin1( "sn" ) ) {
00379     a.setFamilyName( value );
00380     return true;
00381   }
00382 
00383   if ( fieldname == QString::fromLatin1( "uid" ) ) {
00384     a.setUid( value );
00385     return true;
00386   }
00387   if ( fieldname == QString::fromLatin1( "mail" ) ||
00388        fieldname == QString::fromLatin1( "mozillasecondemail" ) ) { // mozilla
00389     if ( a.emails().findIndex( value ) == -1 )
00390       a.insertEmail( value );
00391     return true;
00392   }
00393 
00394   if ( fieldname == QString::fromLatin1( "title" ) ) {
00395     a.setTitle( value );
00396     return true;
00397   }
00398 
00399   if ( fieldname == QString::fromLatin1( "vocation" ) ) {
00400     a.setPrefix( value );
00401     return true;
00402   }
00403 
00404   if ( fieldname == QString::fromLatin1( "cn" ) ) {
00405     a.setFormattedName( value );
00406     return true;
00407   }
00408 
00409   if ( fieldname == QString::fromLatin1( "o" ) || 
00410        fieldname == QString::fromLatin1( "organization" ) ||      // Exchange
00411        fieldname == QString::fromLatin1( "organizationname" ) ) { // Exchange
00412     a.setOrganization( value );
00413     return true;
00414   }
00415 
00416   if ( fieldname == QString::fromLatin1( "description" ) ) {
00417 addComment:
00418     if ( !a.note().isEmpty() )
00419       a.setNote( a.note() + "\n" );
00420     a.setNote( a.note() + value );
00421     return true;
00422   }
00423 
00424   if ( fieldname == QString::fromLatin1( "custom1" ) ||
00425        fieldname == QString::fromLatin1( "custom2" ) ||
00426        fieldname == QString::fromLatin1( "custom3" ) ||
00427        fieldname == QString::fromLatin1( "custom4" ) ) {
00428     goto addComment;
00429   }
00430 
00431   if ( fieldname == QString::fromLatin1( "homeurl" ) ||
00432        fieldname == QString::fromLatin1( "workurl" ) ) {
00433     if (a.url().isEmpty()) {
00434       a.setUrl( value );
00435       return true;
00436     }
00437     if ( a.url().prettyURL() == KURL(value).prettyURL() )
00438       return true;
00439     // TODO: current version of kabc only supports one URL.
00440     // TODO: change this with KDE 4
00441   }
00442 
00443   if ( fieldname == QString::fromLatin1( "homephone" ) ) { 
00444     a.insertPhoneNumber( PhoneNumber( value, PhoneNumber::Home ) );
00445     return true;
00446   }
00447 
00448   if ( fieldname == QString::fromLatin1( "telephonenumber" ) ) {
00449     a.insertPhoneNumber( PhoneNumber( value, PhoneNumber::Work ) );
00450     return true;
00451   }
00452 
00453   if ( fieldname == QString::fromLatin1( "mobile" ) ) {     // mozilla/Netscape 7
00454     a.insertPhoneNumber( PhoneNumber( value, PhoneNumber::Cell ) );
00455     return true;
00456   }
00457 
00458   if ( fieldname == QString::fromLatin1( "cellphone" ) ) {
00459     a.insertPhoneNumber( PhoneNumber( value, PhoneNumber::Cell ) );
00460     return true;
00461   }
00462 
00463   if ( fieldname == QString::fromLatin1( "pager" )  ||       // mozilla
00464        fieldname == QString::fromLatin1( "pagerphone" ) ) {  // mozilla
00465     a.insertPhoneNumber( PhoneNumber( value, PhoneNumber::Pager ) );
00466     return true;
00467   }
00468 
00469   if ( fieldname == QString::fromLatin1( "facsimiletelephonenumber" ) ) {
00470     a.insertPhoneNumber( PhoneNumber( value, PhoneNumber::Fax ) );
00471     return true;
00472   }
00473 
00474   if ( fieldname == QString::fromLatin1( "xmozillaanyphone" ) ) { // mozilla
00475     a.insertPhoneNumber( PhoneNumber( value, PhoneNumber::Work ) );
00476     return true;
00477   }
00478 
00479   if ( fieldname == QString::fromLatin1( "street" ) ||
00480        fieldname == QString::fromLatin1( "streethomeaddress" ) ) {
00481     homeAddr.setStreet( value );
00482     return true;
00483   }
00484 
00485   if ( fieldname == QString::fromLatin1( "postaladdress" ) ) {  // mozilla
00486     workAddr.setStreet( value );
00487     return true;
00488   }
00489 
00490   if ( fieldname == QString::fromLatin1( "mozillapostaladdress2" ) ) {  // mozilla
00491     workAddr.setStreet( workAddr.street() + QString::fromLatin1( "\n" ) + value );
00492     return true;
00493   }
00494 
00495   if ( fieldname == QString::fromLatin1( "postalcode" ) ) {
00496     workAddr.setPostalCode( value );
00497     return true;
00498   }
00499 
00500   if ( fieldname == QString::fromLatin1( "postofficebox" ) ) {
00501     workAddr.setPostOfficeBox( value );
00502     return true;
00503   }
00504 
00505   if ( fieldname == QString::fromLatin1( "homepostaladdress" ) ) {  // Netscape 7
00506     homeAddr.setStreet( value );
00507     return true;
00508   }
00509 
00510   if ( fieldname == QString::fromLatin1( "mozillahomepostaladdress2" ) ) {  // mozilla
00511     homeAddr.setStreet( homeAddr.street() + QString::fromLatin1( "\n" ) + value );
00512     return true;
00513   }
00514 
00515   if ( fieldname == QString::fromLatin1( "mozillahomelocalityname" ) ) {  // mozilla
00516     homeAddr.setLocality( value );
00517     return true;
00518   }
00519 
00520   if ( fieldname == QString::fromLatin1( "mozillahomestate" )   ) { // mozilla
00521     homeAddr.setRegion( value );
00522     return true;
00523   }
00524 
00525   if ( fieldname == QString::fromLatin1( "mozillahomepostalcode" ) ) {  // mozilla
00526     homeAddr.setPostalCode( value );
00527     return true;
00528   }
00529 
00530   if ( fieldname == QString::fromLatin1( "mozillahomecountryname" ) ) { // mozilla
00531     if ( value.length() <= 2 )
00532       value = Address::ISOtoCountry(value);
00533     homeAddr.setCountry( value );
00534     return true;
00535   }
00536 
00537   if ( fieldname == QString::fromLatin1( "locality" ) ) {
00538     workAddr.setLocality( value );
00539     return true;
00540   }
00541 
00542   if ( fieldname == QString::fromLatin1( "streetaddress" ) ) { // Netscape 4.x
00543     workAddr.setStreet( value );
00544     return true;
00545   }
00546 
00547   if ( fieldname == QString::fromLatin1( "countryname" ) ||
00548        fieldname == QString::fromLatin1( "c" ) ) {  // mozilla
00549     if ( value.length() <= 2 )
00550       value = Address::ISOtoCountry(value);
00551     workAddr.setCountry( value );
00552     return true;
00553   }
00554 
00555   if ( fieldname == QString::fromLatin1( "l" ) ) {  // mozilla
00556     workAddr.setLocality( value );
00557     return true;
00558   }
00559 
00560   if ( fieldname == QString::fromLatin1( "st" ) ) {
00561     workAddr.setRegion( value );
00562     return true;
00563   }
00564 
00565   if ( fieldname == QString::fromLatin1( "ou" ) ) {
00566     a.setRole( value );
00567     return true;
00568   }
00569 
00570   if ( fieldname == QString::fromLatin1( "department" ) ) {
00571     a.insertCustom( "KADDRESSBOOK", "X-Department", value );
00572     return true;
00573   }
00574 
00575   if ( fieldname == QString::fromLatin1( "member" ) ) {
00576     // this is a mozilla list member (cn=xxx, mail=yyy)
00577     QStringList list( QStringList::split( ',', value ) );
00578     QString name, email;
00579 
00580     QStringList::Iterator it;
00581     for ( it = list.begin(); it != list.end(); ++it ) {
00582       if ( (*it).startsWith( "cn=" ) )
00583         name = (*it).mid( 3 ).stripWhiteSpace();
00584       if ( (*it).startsWith( "mail=" ) )
00585         email = (*it).mid( 5 ).stripWhiteSpace();
00586     }
00587     if ( !name.isEmpty() && !email.isEmpty() )
00588       email = " <" + email + ">";
00589     a.insertEmail( name + email );
00590     a.insertCategory( i18n( "List of Emails" ) );
00591     return true;
00592   }
00593 
00594   if ( fieldname == QString::fromLatin1( "modifytimestamp" ) ) {
00595     if (value == QString::fromLatin1("0Z")) // ignore
00596       return true;
00597     QDateTime dt = VCardStringToDate( value );
00598     if ( dt.isValid() ) {
00599       a.setRevision(dt);
00600       return true;
00601     }
00602   }
00603 
00604   if ( fieldname == QString::fromLatin1( "objectclass" ) ) // ignore 
00605     return true;
00606 
00607   kdWarning() << QString("LDIFConverter: Unknown field for '%1': '%2=%3'\n")
00608                              .arg(a.formattedName()).arg(fieldname).arg(value);
00609 
00610   return true;
00611 }
00612 
KDE Logo
This file is part of the documentation for kabc Library Version 3.2.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Sun May 16 22:05:37 2004 by doxygen 1.2.15 written by Dimitri van Heesch, © 1997-2003