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
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
00055 class KPIMUtils::LinkLocator::Private
00056 {
00057 public:
00058 int mMaxUrlLen;
00059 int mMaxAddressLen;
00060 };
00061
00062
00063 #if KDE_IS_VERSION( 4, 0, 95 )
00064
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
00075
00076
00077
00078
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
00111
00112
00113
00114
00115
00116
00117
00118
00119
00120
00121 QChar beforeUrl, afterUrl;
00122
00123
00124 if ( mPos > 0 ) {
00125 beforeUrl = mText[mPos - 1];
00126
00127 if ( beforeUrl == '(' ) {
00128 afterUrl = ')';
00129 } else if ( beforeUrl == '[' ) {
00130 afterUrl = ']';
00131 } else if ( beforeUrl == '<' ) {
00132 afterUrl = '>';
00133 } else if ( beforeUrl == '>' ) {
00134 afterUrl = '<';
00135 } else if ( beforeUrl == '"' ) {
00136 afterUrl = '"';
00137 }
00138 }
00139
00140 url.reserve( maxUrlLen() );
00141 int start = mPos;
00142 while ( ( mPos < (int)mText.length() ) &&
00143 ( mText[mPos].isPrint() || mText[mPos].isSpace() ) &&
00144 ( ( afterUrl.isNull() && !mText[mPos].isSpace() ) ||
00145 ( !afterUrl.isNull() && mText[mPos] != afterUrl ) ) ) {
00146 if ( !mText[mPos].isSpace() ) {
00147 url.append( mText[mPos] );
00148 if ( url.length() > maxUrlLen() ) {
00149 break;
00150 }
00151 }
00152
00153 mPos++;
00154 }
00155
00156 if ( isEmptyUrl(url) || ( url.length() > maxUrlLen() ) ) {
00157 mPos = start;
00158 url = "";
00159 } else {
00160 --mPos;
00161 }
00162 }
00163
00164
00165
00166
00167
00168
00169
00170 if ( url.length() > 1 ) {
00171 QList<QChar> wordBoundaries;
00172 wordBoundaries << '.' << ',' << ':' << '!' << '?';
00173 if ( wordBoundaries.contains( url.at( url.length() - 1 ) ) ) {
00174 url.chop( 1 );
00175 --mPos;
00176 }
00177 }
00178
00179 return url;
00180 }
00181
00182
00183 bool LinkLocator::atUrl() const
00184 {
00185
00186
00187 const QString allowedSpecialChars = QString( ".!#$%&'*+-/=?^_`{|}~" );
00188
00189
00190
00191 if ( ( mPos > 0 ) &&
00192 ( mText[mPos-1].isLetterOrNumber() ||
00193 ( allowedSpecialChars.indexOf( mText[mPos-1] ) != -1 ) ) ) {
00194 return false;
00195 }
00196
00197 QChar ch = mText[mPos];
00198 return
00199 ( ch == 'h' && ( mText.mid( mPos, 7 ) == "http://" ||
00200 mText.mid( mPos, 8 ) == "https://" ) ) ||
00201 ( ch == 'v' && mText.mid( mPos, 6 ) == "vnc://" ) ||
00202 ( ch == 'f' && ( mText.mid( mPos, 7 ) == "fish://" ||
00203 mText.mid( mPos, 6 ) == "ftp://" ||
00204 mText.mid( mPos, 7 ) == "ftps://" ) ) ||
00205 ( ch == 's' && ( mText.mid( mPos, 7 ) == "sftp://" ||
00206 mText.mid( mPos, 6 ) == "smb://" ) ) ||
00207 ( ch == 'm' && mText.mid( mPos, 7 ) == "mailto:" ) ||
00208 ( ch == 'w' && mText.mid( mPos, 4 ) == "www." ) ||
00209 ( ch == 'f' && ( mText.mid( mPos, 4 ) == "ftp." ||
00210 mText.mid( mPos, 7 ) == "file://" ) ) ||
00211 ( ch == 'n' && mText.mid( mPos, 5 ) == "news:" );
00212 }
00213
00214 bool LinkLocator::isEmptyUrl( const QString &url ) const
00215 {
00216 return url.isEmpty() ||
00217 url == "http://" ||
00218 url == "https://" ||
00219 url == "fish://" ||
00220 url == "ftp://" ||
00221 url == "ftps://" ||
00222 url == "sftp://" ||
00223 url == "smb://" ||
00224 url == "vnc://" ||
00225 url == "mailto" ||
00226 url == "www" ||
00227 url == "ftp" ||
00228 url == "news" ||
00229 url == "news://";
00230 }
00231
00232 QString LinkLocator::getEmailAddress()
00233 {
00234 QString address;
00235
00236 if ( mText[mPos] == '@' ) {
00237
00238
00239 const QString allowedSpecialChars = QString( ".!#$%&'*+-/=?^_`{|}~" );
00240
00241
00242 int start = mPos - 1;
00243 while ( start >= 0 && mText[start].unicode() < 128 &&
00244 ( mText[start].isLetterOrNumber() ||
00245 mText[start] == '@' ||
00246 allowedSpecialChars.indexOf( mText[start] ) != -1 ) ) {
00247 if ( mText[start] == '@' ) {
00248 return QString();
00249 }
00250 --start;
00251 }
00252 ++start;
00253
00254 while ( ( start < mPos ) && !mText[start].isLetterOrNumber() ) {
00255 ++start;
00256 }
00257 if ( start == mPos ) {
00258 return QString();
00259 }
00260
00261
00262 int dotPos = INT_MAX;
00263 int end = mPos + 1;
00264 while ( end < (int)mText.length() &&
00265 ( mText[end].isLetterOrNumber() ||
00266 mText[end] == '@' ||
00267 mText[end] == '.' ||
00268 mText[end] == '-' ) ) {
00269 if ( mText[end] == '@' ) {
00270 return QString();
00271 }
00272 if ( mText[end] == '.' ) {
00273 dotPos = qMin( dotPos, end );
00274 }
00275 ++end;
00276 }
00277
00278 while ( ( end > mPos ) && !mText[end - 1].isLetterOrNumber() ) {
00279 --end;
00280 }
00281 if ( end == mPos ) {
00282 return QString();
00283 }
00284 if ( dotPos >= end ) {
00285 return QString();
00286 }
00287
00288 if ( end - start > maxAddressLen() ) {
00289 return QString();
00290 }
00291 address = mText.mid( start, end - start );
00292
00293 mPos = end - 1;
00294 }
00295 return address;
00296 }
00297
00298 QString LinkLocator::convertToHtml( const QString &plainText, int flags,
00299 int maxUrlLen, int maxAddressLen )
00300 {
00301 LinkLocator locator( plainText );
00302 locator.setMaxUrlLen( maxUrlLen );
00303 locator.setMaxAddressLen( maxAddressLen );
00304
00305 QString str;
00306 QString result( (QChar*)0, (int)locator.mText.length() * 2 );
00307 QChar ch;
00308 int x;
00309 bool startOfLine = true;
00310 QString emoticon;
00311
00312 for ( locator.mPos = 0, x = 0; locator.mPos < (int)locator.mText.length();
00313 locator.mPos++, x++ ) {
00314 ch = locator.mText[locator.mPos];
00315 if ( flags & PreserveSpaces ) {
00316 if ( ch == ' ' ) {
00317 if ( locator.mPos + 1 < locator.mText.length() ) {
00318 if ( locator.mText[locator.mPos + 1] != ' ' ) {
00319
00320
00321 const bool endOfLine = locator.mText[locator.mPos + 1] == '\n';
00322 if ( !startOfLine && !endOfLine ) {
00323 result += ' ';
00324 } else {
00325 result += " ";
00326 }
00327 } else {
00328
00329
00330 while ( locator.mPos < locator.mText.length() && locator.mText[locator.mPos] == ' ' ) {
00331 result += " ";
00332 locator.mPos++;
00333 x++;
00334 }
00335
00336
00337 locator.mPos--;
00338 x--;
00339 }
00340 } else {
00341
00342 result += " ";
00343 }
00344
00345 if ( startOfLine ) {
00346 startOfLine = false;
00347 }
00348 continue;
00349 } else if ( ch == '\t' ) {
00350 do
00351 {
00352 result += " ";
00353 x++;
00354 }
00355 while ( ( x & 7 ) != 0 );
00356 x--;
00357 startOfLine = false;
00358 continue;
00359 }
00360 }
00361 if ( ch == '\n' ) {
00362 result += "<br />\n";
00363 startOfLine = true;
00364 x = -1;
00365 continue;
00366 }
00367
00368 startOfLine = false;
00369 if ( ch == '&' ) {
00370 result += "&";
00371 } else if ( ch == '"' ) {
00372 result += """;
00373 } else if ( ch == '<' ) {
00374 result += "<";
00375 } else if ( ch == '>' ) {
00376 result += ">";
00377 } else {
00378 const int start = locator.mPos;
00379 if ( !( flags & IgnoreUrls ) ) {
00380 str = locator.getUrl();
00381 if ( !str.isEmpty() ) {
00382 QString hyperlink;
00383 if ( str.left( 4 ) == "www." ) {
00384 hyperlink = "http://" + str;
00385 } else if ( str.left( 4 ) == "ftp." ) {
00386 hyperlink = "ftp://" + str;
00387 } else {
00388 hyperlink = str;
00389 }
00390
00391 result += "<a href=\"" + hyperlink + "\">" + Qt::escape( str ) + "</a>";
00392 x += locator.mPos - start;
00393 continue;
00394 }
00395 str = locator.getEmailAddress();
00396 if ( !str.isEmpty() ) {
00397
00398 int len = str.indexOf( '@' );
00399 QString localPart = str.left( len );
00400
00401
00402
00403 result.truncate( result.length() -
00404 len - ( localPart.count( '&' ) * 4 ) );
00405 x -= len;
00406
00407 result += "<a href=\"mailto:" + str + "\">" + str + "</a>";
00408 x += str.length() - 1;
00409 continue;
00410 }
00411 }
00412 if ( flags & HighlightText ) {
00413 str = locator.highlightedText();
00414 if ( !str.isEmpty() ) {
00415 result += str;
00416 x += locator.mPos - start;
00417 continue;
00418 }
00419 }
00420 result += ch;
00421 }
00422 }
00423
00424 #if KDE_IS_VERSION( 4, 0, 95 )
00425 if ( flags & ReplaceSmileys ) {
00426 QStringList exclude;
00427 exclude << "(c)" << "(C)" << ">:-(" << ">:(" << "(B)" << "(b)" << "(P)" << "(p)";
00428 exclude << "(O)" << "(o)" << "(D)" << "(d)" << "(E)" << "(e)" << "(K)" << "(k)";
00429 exclude << "(I)" << "(i)" << "(L)" << "(l)" << "(8)" << "(T)" << "(t)" << "(G)";
00430 exclude << "(g)" << "(F)" << "(f)" << "(H)";
00431 exclude << "8)" << "(N)" << "(n)" << "(Y)" << "(y)" << "(U)" << "(u)" << "(W)" << "(w)";
00432 static QString cachedEmoticonsThemeName;
00433 if ( cachedEmoticonsThemeName.isEmpty() ) {
00434 cachedEmoticonsThemeName = KEmoticons::currentThemeName();
00435 }
00436 result =
00437 sEmoticons->theme( cachedEmoticonsThemeName ).parseEmoticons(
00438 result, KEmoticonsTheme::StrictParse | KEmoticonsTheme::SkipHTML, exclude );
00439 }
00440 #endif
00441
00442 return result;
00443 }
00444
00445 QString LinkLocator::pngToDataUrl( const QString &iconPath )
00446 {
00447 if ( iconPath.isEmpty() ) {
00448 return QString();
00449 }
00450
00451 QFile pngFile( iconPath );
00452 if ( !pngFile.open( QIODevice::ReadOnly | QIODevice::Unbuffered ) ) {
00453 return QString();
00454 }
00455
00456 QByteArray ba = pngFile.readAll();
00457 pngFile.close();
00458 return QString::fromLatin1( "data:image/png;base64,%1" ).arg( ba.toBase64().constData() );
00459 }
00460
00461 QString LinkLocator::highlightedText()
00462 {
00463
00464 if ( ( mPos > 0 ) && !mText[mPos-1].isSpace() ) {
00465 return QString();
00466 }
00467
00468 const QChar ch = mText[mPos];
00469 if ( ch != '/' && ch != '*' && ch != '_' ) {
00470 return QString();
00471 }
00472
00473 QRegExp re =
00474 QRegExp( QString( "\\%1((\\w+)([\\s-']\\w+)*( ?[,.:\\?!;])?)\\%2" ).arg( ch ).arg( ch ) );
00475 re.setMinimal(true);
00476 if ( re.indexIn( mText, mPos ) == mPos ) {
00477 int length = re.matchedLength();
00478
00479 if ( mPos + length < mText.length() && !mText[mPos + length].isSpace() ) {
00480 return QString();
00481 }
00482 mPos += length - 1;
00483 switch ( ch.toLatin1() ) {
00484 case '*':
00485 return "<b>" + re.cap( 1 ) + "</b>";
00486 case '_':
00487 return "<u>" + re.cap( 1 ) + "</u>";
00488 case '/':
00489 return "<i>" + re.cap( 1 ) + "</i>";
00490 }
00491 }
00492 return QString();
00493 }