kpimutils
linklocator.cpp
Go to the documentation of this file.00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
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
00052 class KPIMUtils::LinkLocator::Private
00053 {
00054 public:
00055 int mMaxUrlLen;
00056 int mMaxAddressLen;
00057 };
00058
00059
00060
00061 QMap<QString, QString> *LinkLocator::s_smileyEmoticonNameMap = 0;
00062
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
00072
00073
00074
00075
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 );
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
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
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
00145 bool LinkLocator::atUrl() const
00146 {
00147
00148
00149 const QString allowedSpecialChars = QString( ".!#$%&'*+-/=?^_`{|}~" );
00150
00151
00152
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
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
00200
00201 const QString allowedSpecialChars = QString( ".!#$%&'*+-/=?^_`{|}~" );
00202
00203
00204 int start = mPos - 1;
00205 while ( start >= 0 && mText[start].unicode() < 128 &&
00206 ( mText[start].isLetterOrNumber() ||
00207 mText[start] == '@' ||
00208 allowedSpecialChars.indexOf( mText[start] ) != -1 ) ) {
00209 if ( mText[start] == '@' ) {
00210 return QString();
00211 }
00212 --start;
00213 }
00214 ++start;
00215
00216 while ( ( start < mPos ) && !mText[start].isLetterOrNumber() ) {
00217 ++start;
00218 }
00219 if ( start == mPos ) {
00220 return QString();
00221 }
00222
00223
00224 int dotPos = INT_MAX;
00225 int end = mPos + 1;
00226 while ( end < (int)mText.length() &&
00227 ( mText[end].isLetterOrNumber() ||
00228 mText[end] == '@' ||
00229 mText[end] == '.' ||
00230 mText[end] == '-' ) ) {
00231 if ( mText[end] == '@' ) {
00232 return QString();
00233 }
00234 if ( mText[end] == '.' ) {
00235 dotPos = qMin( dotPos, end );
00236 }
00237 ++end;
00238 }
00239
00240 while ( ( end > mPos ) && !mText[end - 1].isLetterOrNumber() ) {
00241 --end;
00242 }
00243 if ( end == mPos ) {
00244 return QString();
00245 }
00246 if ( dotPos >= end ) {
00247 return QString();
00248 }
00249
00250 if ( end - start > maxAddressLen() ) {
00251 return QString();
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 += " ";
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 += " ";
00289 locator.mPos++, x++;
00290 }
00291 }
00292 locator.mPos--, x--;
00293 continue;
00294 } else if ( ch == '\t' ) {
00295 do
00296 {
00297 result += " ";
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 += "&";
00316 } else if ( ch == '"' ) {
00317 result += """;
00318 } else if ( ch == '<' ) {
00319 result += "<";
00320 } else if ( ch == '>' ) {
00321 result += ">";
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( '&', "&" );
00337 result += "<a href=\"" + hyperlink + "\">" + str + "</a>";
00338 x += locator.mPos - start;
00339 continue;
00340 }
00341 str = locator.getEmailAddress();
00342 if ( !str.isEmpty() ) {
00343
00344 int len = str.indexOf( '@' );
00345 QString localPart = str.left( len );
00346
00347
00348
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
00400 if ( ( mPos > 0 ) && !mText[mPos-1].isSpace() ) {
00401 return QString();
00402 }
00403
00404
00405 const QChar ch = mText[mPos];
00406 if ( ch !=':' && ch != ';' && ch != '(' && ch != '8' ) {
00407 return QString();
00408 }
00409
00410
00411
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();
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
00450
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
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
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 }