• Skip to content
  • Skip to link menu
KDE 4.1 API Reference
  • KDE API Reference
  • KDE-PIM Libraries
  • Sitemap
  • Contact Us
 

kpimutils

linklocator.cpp

Go to the documentation of this file.
00001 /*
00002   Copyright (c) 2002 Dave Corrie <kde@davecorrie.com>
00003 
00004   This library is free software; you can redistribute it and/or
00005   modify it under the terms of the GNU Library General Public
00006   License as published by the Free Software Foundation; either
00007   version 2 of the License, or (at your option) any later version.
00008 
00009   This library is distributed in the hope that it will be useful,
00010   but WITHOUT ANY WARRANTY; without even the implied warranty of
00011   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012   Library General Public License for more details.
00013 
00014   You should have received a copy of the GNU Library General Public License
00015   along with this library; see the file COPYING.LIB.  If not, write to
00016   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00017   Boston, MA 02110-1301, USA.
00018 */
00030 #include "linklocator.h"
00031 
00032 #include <kglobal.h>
00033 #include <kstandarddirs.h>
00034 #include <kcodecs.h>
00035 #include <kdebug.h>
00036 #include <kdeversion.h>
00037 #if KDE_IS_VERSION( 4, 0, 95 )
00038 #include <kemoticons.h>
00039 #endif
00040 
00041 #include <QtCore/QCoreApplication>
00042 #include <QtCore/QFile>
00043 #include <QtCore/QRegExp>
00044 #include <QtGui/QTextDocument>
00045 
00046 #include <limits.h>
00047 
00048 using namespace KPIMUtils;
00049 
00054 //@cond PRIVATE
00055 class KPIMUtils::LinkLocator::Private
00056 {
00057   public:
00058     int mMaxUrlLen;
00059     int mMaxAddressLen;
00060 };
00061 //@endcond
00062 
00063 #if KDE_IS_VERSION( 4, 0, 95 )
00064 // Use a static for this as calls to KEmoticons::theme() are expensive
00065 K_GLOBAL_STATIC( KEmoticons, sEmoticons )
00066 #endif
00067 
00068 LinkLocator::LinkLocator( const QString &text, int pos )
00069   : mText( text ), mPos( pos ), d( new KPIMUtils::LinkLocator::Private )
00070 {
00071   d->mMaxUrlLen = 4096;
00072   d->mMaxAddressLen = 255;
00073 
00074   // If you change either of the above values for maxUrlLen or
00075   // maxAddressLen, then please also update the documentation for
00076   // setMaxUrlLen()/setMaxAddressLen() in the header file AND the
00077   // default values used for the maxUrlLen/maxAddressLen parameters
00078   // of convertToHtml().
00079 }
00080 
00081 LinkLocator::~LinkLocator()
00082 {
00083   delete d;
00084 }
00085 
00086 void LinkLocator::setMaxUrlLen( int length )
00087 {
00088   d->mMaxUrlLen = length;
00089 }
00090 
00091 int LinkLocator::maxUrlLen() const
00092 {
00093   return d->mMaxUrlLen;
00094 }
00095 
00096 void LinkLocator::setMaxAddressLen( int length )
00097 {
00098   d->mMaxAddressLen = length;
00099 }
00100 
00101 int LinkLocator::maxAddressLen() const
00102 {
00103   return d->mMaxAddressLen;
00104 }
00105 
00106 QString LinkLocator::getUrl()
00107 {
00108   QString url;
00109   if ( atUrl() ) {
00110     // handle cases like this: <link>http://foobar.org/</link>
00111     int start = mPos;
00112     while ( mPos < (int)mText.length() &&
00113             mText[mPos] > ' ' && mText[mPos] != '"' &&
00114             QString( "<>()[]" ).indexOf( mText[mPos] ) == -1 ) {
00115       ++mPos;
00116     }
00117 
00118     /* some URLs really end with:  # / & - _    */
00119     const QString allowedSpecialChars = QString( "#/&-_" );
00120     while ( mPos > start && mText[mPos-1].isPunct() &&
00121             allowedSpecialChars.indexOf( mText[mPos-1] ) == -1 ) {
00122       --mPos;
00123     }
00124 
00125     url = mText.mid( start, mPos - start );
00126     if ( isEmptyUrl(url) || mPos - start > maxUrlLen() ) {
00127       mPos = start;
00128       url = "";
00129     } else {
00130       --mPos;
00131     }
00132   }
00133   return url;
00134 }
00135 
00136 // keep this in sync with KMMainWin::slotUrlClicked()
00137 bool LinkLocator::atUrl() const
00138 {
00139   // the following characters are allowed in a dot-atom (RFC 2822):
00140   // a-z A-Z 0-9 . ! # $ % & ' * + - / = ? ^ _ ` { | } ~
00141   const QString allowedSpecialChars = QString( ".!#$%&'*+-/=?^_`{|}~" );
00142 
00143   // the character directly before the URL must not be a letter, a number or
00144   // any other character allowed in a dot-atom (RFC 2822).
00145   if ( ( mPos > 0 ) &&
00146        ( mText[mPos-1].isLetterOrNumber() ||
00147          ( allowedSpecialChars.indexOf( mText[mPos-1] ) != -1 ) ) ) {
00148     return false;
00149   }
00150 
00151   QChar ch = mText[mPos];
00152   return
00153     ( ch == 'h' && ( mText.mid( mPos, 7 ) == "http://" ||
00154                      mText.mid( mPos, 8 ) == "https://" ) ) ||
00155     ( ch == 'v' && mText.mid( mPos, 6 ) == "vnc://" ) ||
00156     ( ch == 'f' && ( mText.mid( mPos, 7 ) == "fish://" ||
00157                      mText.mid( mPos, 6 ) == "ftp://" ||
00158                      mText.mid( mPos, 7 ) == "ftps://") ) ||
00159     ( ch == 's' && ( mText.mid( mPos, 7 ) == "sftp://" ||
00160                      mText.mid( mPos, 6 ) == "smb://" ) ) ||
00161     ( ch == 'm' && mText.mid( mPos, 7 ) == "mailto:" ) ||
00162     ( ch == 'w' && mText.mid( mPos, 4 ) == "www." ) ||
00163     ( ch == 'f' && ( mText.mid( mPos, 4 ) == "ftp." ||
00164                      mText.mid( mPos, 7 ) == "file://" ) ) ||
00165     ( ch == 'n' && mText.mid( mPos, 5 ) == "news:" );
00166 }
00167 
00168 bool LinkLocator::isEmptyUrl( const QString &url ) const
00169 {
00170   return url.isEmpty() ||
00171     url == "http://" ||
00172     url == "https://" ||
00173     url == "fish://" ||
00174     url == "ftp://" ||
00175     url == "ftps://" ||
00176     url == "sftp://" ||
00177     url == "smb://" ||
00178     url == "vnc://" ||
00179     url == "mailto" ||
00180     url == "www" ||
00181     url == "ftp" ||
00182     url == "news" ||
00183     url == "news://";
00184 }
00185 
00186 QString LinkLocator::getEmailAddress()
00187 {
00188   QString address;
00189 
00190   if ( mText[mPos] == '@' ) {
00191     // the following characters are allowed in a dot-atom (RFC 2822):
00192     // a-z A-Z 0-9 . ! # $ % & ' * + - / = ? ^ _ ` { | } ~
00193     const QString allowedSpecialChars = QString( ".!#$%&'*+-/=?^_`{|}~" );
00194 
00195     // determine the local part of the email address
00196     int start = mPos - 1;
00197     while ( start >= 0 && mText[start].unicode() < 128 &&
00198             ( mText[start].isLetterOrNumber() ||
00199               mText[start] == '@' || // allow @ to find invalid email addresses
00200               allowedSpecialChars.indexOf( mText[start] ) != -1 ) ) {
00201       if ( mText[start] == '@' ) {
00202         return QString(); // local part contains '@' -> no email address
00203       }
00204       --start;
00205     }
00206     ++start;
00207     // we assume that an email address starts with a letter or a digit
00208     while ( ( start < mPos ) && !mText[start].isLetterOrNumber() ) {
00209       ++start;
00210     }
00211     if ( start == mPos ) {
00212       return QString(); // local part is empty -> no email address
00213     }
00214 
00215     // determine the domain part of the email address
00216     int dotPos = INT_MAX;
00217     int end = mPos + 1;
00218     while ( end < (int)mText.length() &&
00219             ( mText[end].isLetterOrNumber() ||
00220               mText[end] == '@' || // allow @ to find invalid email addresses
00221               mText[end] == '.' ||
00222               mText[end] == '-' ) ) {
00223       if ( mText[end] == '@' ) {
00224         return QString(); // domain part contains '@' -> no email address
00225       }
00226       if ( mText[end] == '.' ) {
00227         dotPos = qMin( dotPos, end ); // remember index of first dot in domain
00228       }
00229       ++end;
00230     }
00231     // we assume that an email address ends with a letter or a digit
00232     while ( ( end > mPos ) && !mText[end - 1].isLetterOrNumber() ) {
00233       --end;
00234     }
00235     if ( end == mPos ) {
00236       return QString(); // domain part is empty -> no email address
00237     }
00238     if ( dotPos >= end ) {
00239       return QString(); // domain part doesn't contain a dot
00240     }
00241 
00242     if ( end - start > maxAddressLen() ) {
00243       return QString(); // too long -> most likely no email address
00244     }
00245     address = mText.mid( start, end - start );
00246 
00247     mPos = end - 1;
00248   }
00249   return address;
00250 }
00251 
00252 QString LinkLocator::convertToHtml( const QString &plainText, int flags,
00253                                     int maxUrlLen, int maxAddressLen )
00254 {
00255   LinkLocator locator( plainText );
00256   locator.setMaxUrlLen( maxUrlLen );
00257   locator.setMaxAddressLen( maxAddressLen );
00258 
00259   QString str;
00260   QString result( (QChar*)0, (int)locator.mText.length() * 2 );
00261   QChar ch;
00262   int x;
00263   bool startOfLine = true;
00264   QString emoticon;
00265 
00266   for ( locator.mPos = 0, x = 0; locator.mPos < (int)locator.mText.length();
00267         locator.mPos++, x++ ) {
00268     ch = locator.mText[locator.mPos];
00269     if ( flags & PreserveSpaces ) {
00270       if ( ch == ' ' ) {
00271         if ( startOfLine ) {
00272           result += "&nbsp;";
00273           locator.mPos++, x++;
00274           startOfLine = false;
00275         }
00276         while ( locator.mText[locator.mPos] == ' ' ) {
00277           result += ' ';
00278           locator.mPos++, x++;
00279           if ( locator.mText[locator.mPos] == ' ' ) {
00280             result += "&nbsp;";
00281             locator.mPos++, x++;
00282           }
00283         }
00284         locator.mPos--, x--;
00285         continue;
00286       } else if ( ch == '\t' ) {
00287         do
00288         {
00289           result += "&nbsp;";
00290           x++;
00291         }
00292         while ( ( x & 7 ) != 0 );
00293         x--;
00294         startOfLine = false;
00295         continue;
00296       }
00297     }
00298     if ( ch == '\n' ) {
00299       result += "<br />\n"; // Keep the \n, so apps can figure out the quoting levels correctly.
00300       startOfLine = true;
00301       x = -1;
00302       continue;
00303     }
00304 
00305     startOfLine = false;
00306     if ( ch == '&' ) {
00307       result += "&amp;";
00308     } else if ( ch == '"' ) {
00309       result += "&quot;";
00310     } else if ( ch == '<' ) {
00311       result += "&lt;";
00312     } else if ( ch == '>' ) {
00313       result += "&gt;";
00314     } else {
00315       const int start = locator.mPos;
00316       if ( !( flags & IgnoreUrls ) ) {
00317         str = locator.getUrl();
00318         if ( !str.isEmpty() ) {
00319           QString hyperlink;
00320           if ( str.left( 4 ) == "www." ) {
00321             hyperlink = "http://" + str;
00322           } else if ( str.left( 4 ) == "ftp." ) {
00323             hyperlink = "ftp://" + str;
00324           } else {
00325             hyperlink = str;
00326           }
00327 
00328           str = str.replace( '&', "&amp;" );
00329           result += "<a href=\"" + hyperlink + "\">" + str + "</a>";
00330           x += locator.mPos - start;
00331           continue;
00332         }
00333         str = locator.getEmailAddress();
00334         if ( !str.isEmpty() ) {
00335           // len is the length of the local part
00336           int len = str.indexOf( '@' );
00337           QString localPart = str.left( len );
00338 
00339           // remove the local part from the result (as '&'s have been expanded to
00340           // &amp; we have to take care of the 4 additional characters per '&')
00341           result.truncate( result.length() -
00342                            len - ( localPart.count( '&' ) * 4 ) );
00343           x -= len;
00344 
00345           result += "<a href=\"mailto:" + str + "\">" + str + "</a>";
00346           x += str.length() - 1;
00347           continue;
00348         }
00349       }
00350       if ( flags & HighlightText ) {
00351         str = locator.highlightedText();
00352         if ( !str.isEmpty() ) {
00353           result += str;
00354           x += locator.mPos - start;
00355           continue;
00356         }
00357       }
00358       result += ch;
00359     }
00360   }
00361 
00362 #if KDE_IS_VERSION( 4, 0, 95 )
00363   if ( flags & ReplaceSmileys ) {
00364     QStringList exclude;
00365     exclude << "(c)" << "(C)" << "&gt;:-(" << "&gt;:(" << "(B)" << "(b)" << "(P)" << "(p)";
00366     exclude << "(O)" << "(o)" << "(D)" << "(d)" << "(E)" << "(e)" << "(K)" << "(k)" << "(I)" << "(i)";
00367     exclude << "(L)" << "(l)" << "(8)" << "(T)" << "(t)" << "(G)" << "(g)" << "(F)" << "(f)" << "(H)";
00368     exclude << "8)" << "(N)" << "(n)" << "(Y)" << "(y)" << "(U)" << "(u)" << "(W)" << "(w)";
00369     result = sEmoticons->theme().parseEmoticons( result, KEmoticonsTheme::StrictParse |
00370                                                          KEmoticonsTheme::SkipHTML,
00371                                                  exclude );
00372   }
00373 #endif
00374 
00375   return result;
00376 }
00377 
00378 QString LinkLocator::pngToDataUrl( const QString &iconPath )
00379 {
00380   if ( iconPath.isEmpty() ) {
00381     return QString();
00382   }
00383 
00384   QFile pngFile( iconPath );
00385   if ( !pngFile.open( QIODevice::ReadOnly | QIODevice::Unbuffered ) ) {
00386     return QString();
00387   }
00388 
00389   QByteArray ba = pngFile.readAll();
00390   pngFile.close();
00391   return QString::fromLatin1( "data:image/png;base64,%1" ).arg( ba.toBase64().constData() );
00392 }
00393 
00394 QString LinkLocator::highlightedText()
00395 {
00396   // formating symbols must be prepended with a whitespace
00397   if ( ( mPos > 0 ) && !mText[mPos-1].isSpace() ) {
00398     return QString();
00399   }
00400 
00401   const QChar ch = mText[mPos];
00402   if ( ch != '/' && ch != '*' && ch != '_' ) {
00403     return QString();
00404   }
00405 
00406   QRegExp re =
00407     QRegExp( QString( "\\%1([0-9A-Za-z]+)\\%2" ).arg( ch ).arg( ch ) );
00408   if ( re.indexIn( mText, mPos ) == mPos ) {
00409     int length = re.matchedLength();
00410     // there must be a whitespace after the closing formating symbol
00411     if ( mPos + length < mText.length() && !mText[mPos + length].isSpace() ) {
00412       return QString();
00413     }
00414     mPos += length - 1;
00415     switch ( ch.toLatin1() ) {
00416     case '*':
00417       return "<b>" + re.cap( 1 ) + "</b>";
00418     case '_':
00419       return "<u>" + re.cap( 1 ) + "</u>";
00420     case '/':
00421       return "<i>" + re.cap( 1 ) + "</i>";
00422     }
00423   }
00424   return QString();
00425 }

kpimutils

Skip menu "kpimutils"
  • Main Page
  • Modules
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Class Members

KDE-PIM Libraries

Skip menu "KDE-PIM Libraries"
  • akonadi
  • kabc
  • kblog
  • kcal
  • kimap
  • kioslave
  •   imap4
  •   mbox
  • kldap
  • kmime
  • kpimidentities
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.5.6
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal