• Skip to content
  • Skip to link menu
KDE 4.0 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 #include "pimemoticons.h"
00032 
00033 #include <kglobal.h>
00034 #include <kstandarddirs.h>
00035 #include <kcodecs.h>
00036 #include <kdebug.h>
00037 
00038 #include <QtCore/QCoreApplication>
00039 #include <QtCore/QFile>
00040 #include <QtCore/QRegExp>
00041 #include <QtGui/QTextDocument>
00042 
00043 #include <limits.h>
00044 
00045 using namespace KPIMUtils;
00046 
00051 //@cond PRIVATE
00052 class KPIMUtils::LinkLocator::Private
00053 {
00054   public:
00055     int mMaxUrlLen;
00056     int mMaxAddressLen;
00057 };
00058 //@endcond
00059 
00060 // maps the smiley text to the corresponding emoticon name
00061 QMap<QString, QString> *LinkLocator::s_smileyEmoticonNameMap = 0;
00062 // cache for the HTML representation of a smiley
00063 QMap<QString, QString> *LinkLocator::s_smileyEmoticonHTMLCache = 0;
00064 
00065 LinkLocator::LinkLocator( const QString &text, int pos )
00066   : mText( text ), mPos( pos ), d( new KPIMUtils::LinkLocator::Private )
00067 {
00068   d->mMaxUrlLen = 4096;
00069   d->mMaxAddressLen = 255;
00070 
00071   // If you change either of the above values for maxUrlLen or
00072   // maxAddressLen, then please also update the documentation for
00073   // setMaxUrlLen()/setMaxAddressLen() in the header file AND the
00074   // default values used for the maxUrlLen/maxAddressLen parameters
00075   // of convertToHtml().
00076 
00077   if ( !s_smileyEmoticonNameMap ) {
00078     s_smileyEmoticonNameMap = new QMap<QString, QString>();
00079     s_smileyEmoticonHTMLCache = new QMap<QString, QString>();
00080     qAddPostRoutine(cleanupLinkLocator);
00081     for ( int i = 0; i < EmotIcons::EnumSindex::COUNT; ++i ) {
00082       QString imageName( EmotIcons::EnumSindex::enumToString[i] );
00083       imageName.truncate( imageName.length() - 2 ); //remove the _0 bit
00084       s_smileyEmoticonNameMap->insert( EmotIcons::smiley( i ), imageName );
00085     }
00086   }
00087 }
00088 
00089 LinkLocator::~LinkLocator()
00090 {
00091   delete d;
00092 }
00093 
00094 void LinkLocator::setMaxUrlLen( int length )
00095 {
00096   d->mMaxUrlLen = length;
00097 }
00098 
00099 int LinkLocator::maxUrlLen() const
00100 {
00101   return d->mMaxUrlLen;
00102 }
00103 
00104 void LinkLocator::setMaxAddressLen( int length )
00105 {
00106   d->mMaxAddressLen = length;
00107 }
00108 
00109 int LinkLocator::maxAddressLen() const
00110 {
00111   return d->mMaxAddressLen;
00112 }
00113 
00114 QString LinkLocator::getUrl()
00115 {
00116   QString url;
00117   if ( atUrl() ) {
00118     // handle cases like this: <link>http://foobar.org/</link>
00119     int start = mPos;
00120     while ( mPos < (int)mText.length() &&
00121             mText[mPos] > ' ' && mText[mPos] != '"' &&
00122             QString( "<>()[]" ).indexOf( mText[mPos] ) == -1 ) {
00123       ++mPos;
00124     }
00125 
00126     /* some URLs really end with:  # / & - _    */
00127     const QString allowedSpecialChars = QString( "#/&-_" );
00128     while ( mPos > start && mText[mPos-1].isPunct() &&
00129             allowedSpecialChars.indexOf( mText[mPos-1] ) == -1 ) {
00130       --mPos;
00131     }
00132 
00133     url = mText.mid( start, mPos - start );
00134     if ( isEmptyUrl(url) || mPos - start > maxUrlLen() ) {
00135       mPos = start;
00136       url = "";
00137     } else {
00138       --mPos;
00139     }
00140   }
00141   return url;
00142 }
00143 
00144 // keep this in sync with KMMainWin::slotUrlClicked()
00145 bool LinkLocator::atUrl() const
00146 {
00147   // the following characters are allowed in a dot-atom (RFC 2822):
00148   // a-z A-Z 0-9 . ! # $ % & ' * + - / = ? ^ _ ` { | } ~
00149   const QString allowedSpecialChars = QString( ".!#$%&'*+-/=?^_`{|}~" );
00150 
00151   // the character directly before the URL must not be a letter, a number or
00152   // any other character allowed in a dot-atom (RFC 2822).
00153   if ( ( mPos > 0 ) &&
00154        ( mText[mPos-1].isLetterOrNumber() ||
00155          ( allowedSpecialChars.indexOf( mText[mPos-1] ) != -1 ) ) ) {
00156     return false;
00157   }
00158 
00159   QChar ch = mText[mPos];
00160   return
00161     ( ch == 'h' && ( mText.mid( mPos, 7 ) == "http://" ||
00162                      mText.mid( mPos, 8 ) == "https://" ) ) ||
00163     ( ch == 'v' && mText.mid( mPos, 6 ) == "vnc://" ) ||
00164     ( ch == 'f' && ( mText.mid( mPos, 7 ) == "fish://" ||
00165                      mText.mid( mPos, 6 ) == "ftp://" ||
00166                      mText.mid( mPos, 7 ) == "ftps://") ) ||
00167     ( ch == 's' && ( mText.mid( mPos, 7 ) == "sftp://" ||
00168                      mText.mid( mPos, 6 ) == "smb://" ) ) ||
00169     ( ch == 'm' && mText.mid( mPos, 7 ) == "mailto:" ) ||
00170     ( ch == 'w' && mText.mid( mPos, 4 ) == "www." ) ||
00171     ( ch == 'f' && mText.mid( mPos, 4 ) == "ftp." ) ||
00172     ( ch == 'n' && mText.mid( mPos, 5 ) == "news:" );
00173   // note: no "file:" for security reasons
00174 }
00175 
00176 bool LinkLocator::isEmptyUrl( const QString &url ) const
00177 {
00178   return url.isEmpty() ||
00179     url == "http://" ||
00180     url == "https://" ||
00181     url == "fish://" ||
00182     url == "ftp://" ||
00183     url == "ftps://" ||
00184     url == "sftp://" ||
00185     url == "smb://" ||
00186     url == "vnc://" ||
00187     url == "mailto" ||
00188     url == "www" ||
00189     url == "ftp" ||
00190     url == "news" ||
00191     url == "news://";
00192 }
00193 
00194 QString LinkLocator::getEmailAddress()
00195 {
00196   QString address;
00197 
00198   if ( mText[mPos] == '@' ) {
00199     // the following characters are allowed in a dot-atom (RFC 2822):
00200     // a-z A-Z 0-9 . ! # $ % & ' * + - / = ? ^ _ ` { | } ~
00201     const QString allowedSpecialChars = QString( ".!#$%&'*+-/=?^_`{|}~" );
00202 
00203     // determine the local part of the email address
00204     int start = mPos - 1;
00205     while ( start >= 0 && mText[start].unicode() < 128 &&
00206             ( mText[start].isLetterOrNumber() ||
00207               mText[start] == '@' || // allow @ to find invalid email addresses
00208               allowedSpecialChars.indexOf( mText[start] ) != -1 ) ) {
00209       if ( mText[start] == '@' ) {
00210         return QString(); // local part contains '@' -> no email address
00211       }
00212       --start;
00213     }
00214     ++start;
00215     // we assume that an email address starts with a letter or a digit
00216     while ( ( start < mPos ) && !mText[start].isLetterOrNumber() ) {
00217       ++start;
00218     }
00219     if ( start == mPos ) {
00220       return QString(); // local part is empty -> no email address
00221     }
00222 
00223     // determine the domain part of the email address
00224     int dotPos = INT_MAX;
00225     int end = mPos + 1;
00226     while ( end < (int)mText.length() &&
00227             ( mText[end].isLetterOrNumber() ||
00228               mText[end] == '@' || // allow @ to find invalid email addresses
00229               mText[end] == '.' ||
00230               mText[end] == '-' ) ) {
00231       if ( mText[end] == '@' ) {
00232         return QString(); // domain part contains '@' -> no email address
00233       }
00234       if ( mText[end] == '.' ) {
00235         dotPos = qMin( dotPos, end ); // remember index of first dot in domain
00236       }
00237       ++end;
00238     }
00239     // we assume that an email address ends with a letter or a digit
00240     while ( ( end > mPos ) && !mText[end - 1].isLetterOrNumber() ) {
00241       --end;
00242     }
00243     if ( end == mPos ) {
00244       return QString(); // domain part is empty -> no email address
00245     }
00246     if ( dotPos >= end ) {
00247       return QString(); // domain part doesn't contain a dot
00248     }
00249 
00250     if ( end - start > maxAddressLen() ) {
00251       return QString(); // too long -> most likely no email address
00252     }
00253     address = mText.mid( start, end - start );
00254 
00255     mPos = end - 1;
00256   }
00257   return address;
00258 }
00259 
00260 QString LinkLocator::convertToHtml( const QString &plainText, int flags,
00261                                     int maxUrlLen, int maxAddressLen )
00262 {
00263   LinkLocator locator( plainText );
00264   locator.setMaxUrlLen( maxUrlLen );
00265   locator.setMaxAddressLen( maxAddressLen );
00266 
00267   QString str;
00268   QString result( (QChar*)0, (int)locator.mText.length() * 2 );
00269   QChar ch;
00270   int x;
00271   bool startOfLine = true;
00272   QString emoticon;
00273 
00274   for ( locator.mPos = 0, x = 0; locator.mPos < (int)locator.mText.length();
00275         locator.mPos++, x++ ) {
00276     ch = locator.mText[locator.mPos];
00277     if ( flags & PreserveSpaces ) {
00278       if ( ch == ' ' ) {
00279         if ( startOfLine ) {
00280           result += "&nbsp;";
00281           locator.mPos++, x++;
00282           startOfLine = false;
00283         }
00284         while ( locator.mText[locator.mPos] == ' ' ) {
00285           result += ' ';
00286           locator.mPos++, x++;
00287           if ( locator.mText[locator.mPos] == ' ' ) {
00288             result += "&nbsp;";
00289             locator.mPos++, x++;
00290           }
00291         }
00292         locator.mPos--, x--;
00293         continue;
00294       } else if ( ch == '\t' ) {
00295         do
00296         {
00297           result += "&nbsp;";
00298           x++;
00299         }
00300         while ( ( x & 7 ) != 0 );
00301         x--;
00302         startOfLine = false;
00303         continue;
00304       }
00305     }
00306     if ( ch == '\n' ) {
00307       result += "<br />";
00308       startOfLine = true;
00309       x = -1;
00310       continue;
00311     }
00312 
00313     startOfLine = false;
00314     if ( ch == '&' ) {
00315       result += "&amp;";
00316     } else if ( ch == '"' ) {
00317       result += "&quot;";
00318     } else if ( ch == '<' ) {
00319       result += "&lt;";
00320     } else if ( ch == '>' ) {
00321       result += "&gt;";
00322     } else {
00323       const int start = locator.mPos;
00324       if ( !( flags & IgnoreUrls ) ) {
00325         str = locator.getUrl();
00326         if ( !str.isEmpty() ) {
00327           QString hyperlink;
00328           if ( str.left( 4 ) == "www." ) {
00329             hyperlink = "http://" + str;
00330           } else if ( str.left( 4 ) == "ftp." ) {
00331             hyperlink = "ftp://" + str;
00332           } else {
00333             hyperlink = str;
00334           }
00335 
00336           str = str.replace( '&', "&amp;" );
00337           result += "<a href=\"" + hyperlink + "\">" + str + "</a>";
00338           x += locator.mPos - start;
00339           continue;
00340         }
00341         str = locator.getEmailAddress();
00342         if ( !str.isEmpty() ) {
00343           // len is the length of the local part
00344           int len = str.indexOf( '@' );
00345           QString localPart = str.left( len );
00346 
00347           // remove the local part from the result (as '&'s have been expanded to
00348           // &amp; we have to take care of the 4 additional characters per '&')
00349           result.truncate( result.length() -
00350                            len - ( localPart.count( '&' ) * 4 ) );
00351           x -= len;
00352 
00353           result += "<a href=\"mailto:" + str + "\">" + str + "</a>";
00354           x += str.length() - 1;
00355           continue;
00356         }
00357       }
00358       if ( flags & ReplaceSmileys ) {
00359         str = locator.getEmoticon();
00360         if ( ! str.isEmpty() ) {
00361           result += str;
00362           x += locator.mPos - start;
00363           continue;
00364         }
00365       }
00366       if ( flags & HighlightText ) {
00367         str = locator.highlightedText();
00368         if ( !str.isEmpty() ) {
00369           result += str;
00370           x += locator.mPos - start;
00371           continue;
00372         }
00373       }
00374       result += ch;
00375     }
00376   }
00377 
00378   return result;
00379 }
00380 
00381 QString LinkLocator::pngToDataUrl( const QString &iconPath )
00382 {
00383   if ( iconPath.isEmpty() ) {
00384     return QString();
00385   }
00386 
00387   QFile pngFile( iconPath );
00388   if ( !pngFile.open( QIODevice::ReadOnly | QIODevice::Unbuffered ) ) {
00389     return QString();
00390   }
00391 
00392   QByteArray ba = pngFile.readAll();
00393   pngFile.close();
00394   return QString::fromLatin1( "data:image/png;base64,%1" ).arg( ba.toBase64().constData() );
00395 }
00396 
00397 QString LinkLocator::getEmoticon()
00398 {
00399   // smileys have to be prepended by whitespace
00400   if ( ( mPos > 0 ) && !mText[mPos-1].isSpace() ) {
00401     return QString();
00402   }
00403 
00404   // since smileys start with ':', ';', '(' or '8' short circuit method
00405   const QChar ch = mText[mPos];
00406   if ( ch !=':' && ch != ';' && ch != '(' && ch != '8' ) {
00407     return QString();
00408   }
00409 
00410   // find the end of the smiley (a smiley is at most 4 chars long and ends at
00411   // lineend or whitespace)
00412   const int MinSmileyLen = 2;
00413   const int MaxSmileyLen = 4;
00414   int smileyLen = 1;
00415   while ( ( smileyLen <= MaxSmileyLen ) &&
00416           ( mPos + smileyLen < (int)mText.length() ) &&
00417           !mText[mPos + smileyLen].isSpace() ) {
00418     smileyLen++;
00419   }
00420   if ( smileyLen < MinSmileyLen || smileyLen > MaxSmileyLen ) {
00421     return QString();
00422   }
00423 
00424   const QString smiley = mText.mid( mPos, smileyLen );
00425   if ( !s_smileyEmoticonNameMap->contains( smiley ) ) {
00426     return QString(); // that's not a (known) smiley
00427   }
00428 
00429   QString htmlRep;
00430   if ( s_smileyEmoticonHTMLCache->contains( smiley ) ) {
00431     htmlRep = (*s_smileyEmoticonHTMLCache)[smiley];
00432   } else {
00433     const QString imageName = (*s_smileyEmoticonNameMap)[smiley];
00434 
00435     QString emotIconTheme = EmotIcons::theme();
00436     if (emotIconTheme == "Default")
00437         emotIconTheme = "kde4";
00438 
00439     const QString iconPath =
00440       KStandardDirs::locate( "emoticons",
00441                              emotIconTheme +
00442                              QString::fromLatin1( "/" ) +
00443                              imageName + QString::fromLatin1( ".png" ) );
00444 
00445     const QString dataUrl = pngToDataUrl( iconPath );
00446     if ( dataUrl.isEmpty() ) {
00447       htmlRep.clear();
00448     } else {
00449       // create an image tag (the text in attribute alt is used
00450       // for copy & paste) representing the smiley
00451       htmlRep = QString( "<img class=\"pimsmileyimg\" src=\"%1\" "
00452                          "alt=\"%2\" title=\"%3\" width=\"16\" height=\"16\"/>" )
00453                 .arg( dataUrl,
00454                       Qt::escape( smiley ),
00455                       Qt::escape( smiley ) );
00456     }
00457     s_smileyEmoticonHTMLCache->insert( smiley, htmlRep );
00458   }
00459 
00460   if ( !htmlRep.isEmpty() ) {
00461     mPos += smileyLen - 1;
00462   }
00463 
00464   return htmlRep;
00465 }
00466 
00467 QString LinkLocator::highlightedText()
00468 {
00469   // formating symbols must be prepended with a whitespace
00470   if ( ( mPos > 0 ) && !mText[mPos-1].isSpace() ) {
00471     return QString();
00472   }
00473 
00474   const QChar ch = mText[mPos];
00475   if ( ch != '/' && ch != '*' && ch != '_' ) {
00476     return QString();
00477   }
00478 
00479   QRegExp re =
00480     QRegExp( QString( "\\%1([0-9A-Za-z]+)\\%2" ).arg( ch ).arg( ch ) );
00481   if ( re.indexIn( mText, mPos ) == mPos ) {
00482     int length = re.matchedLength();
00483     // there must be a whitespace after the closing formating symbol
00484     if ( mPos + length < mText.length() && !mText[mPos + length].isSpace() ) {
00485       return QString();
00486     }
00487     mPos += length - 1;
00488     switch ( ch.toLatin1() ) {
00489     case '*':
00490       return "<b>" + re.cap( 1 ) + "</b>";
00491     case '_':
00492       return "<u>" + re.cap( 1 ) + "</u>";
00493     case '/':
00494       return "<i>" + re.cap( 1 ) + "</i>";
00495     }
00496   }
00497   return QString();
00498 }
00499 
00500 void LinkLocator::cleanupLinkLocator()
00501 {
00502     delete s_smileyEmoticonNameMap;
00503     delete s_smileyEmoticonHTMLCache;
00504 }

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"
  • 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.5
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