diff options
Diffstat (limited to 'libtdepim/linklocator.cpp')
-rw-r--r-- | libtdepim/linklocator.cpp | 466 |
1 files changed, 466 insertions, 0 deletions
diff --git a/libtdepim/linklocator.cpp b/libtdepim/linklocator.cpp new file mode 100644 index 000000000..399c5d3b3 --- /dev/null +++ b/libtdepim/linklocator.cpp @@ -0,0 +1,466 @@ +/** + * linklocator.cpp + * + * Copyright (c) 2002 Dave Corrie <kde@davecorrie.com> + * + * This file is part of KMail. + * + * KMail is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "linklocator.h" +#include "pimemoticons.h" +#include <kglobal.h> +#include <kstandarddirs.h> +#include <kstaticdeleter.h> +#include <kmdcodec.h> +#include <kdebug.h> + +#include <tqstylesheet.h> +#include <tqfile.h> +#include <tqregexp.h> + +#include <limits.h> + +TQMap<TQString, TQString> *LinkLocator::s_smileyEmoticonNameMap = 0; +TQMap<TQString, TQString> *LinkLocator::s_smileyEmoticonHTMLCache = 0; + +static KStaticDeleter< TQMap<TQString, TQString> > smileyMapDeleter; +static KStaticDeleter< TQMap<TQString, TQString> > smileyCacheDeleter; + +LinkLocator::LinkLocator(const TQString& text, int pos) + : mText(text), mPos(pos), mMaxUrlLen(4096), mMaxAddressLen(255) +{ + // If you change either of the above values for maxUrlLen or + // maxAddressLen, then please also update the documentation for + // setMaxUrlLen()/setMaxAddressLen() in the header file AND the + // default values used for the maxUrlLen/maxAddressLen parameters + // of convertToHtml(). + + if ( !s_smileyEmoticonNameMap ) { + smileyMapDeleter.setObject( s_smileyEmoticonNameMap, + new TQMap<TQString, TQString>() ); + for ( int i = 0; i < EmotIcons::EnumSindex::COUNT; ++i ) { + TQString imageName( EmotIcons::EnumSindex::enumToString[i] ); + imageName.truncate( imageName.length() - 2 ); //remove the _0 bit + s_smileyEmoticonNameMap->insert( EmotIcons::smiley(i), imageName ); + } + } + + if ( !s_smileyEmoticonHTMLCache ) + smileyCacheDeleter.setObject( s_smileyEmoticonHTMLCache, + new TQMap<TQString, TQString>() ); +} + +void LinkLocator::setMaxUrlLen(int length) +{ + mMaxUrlLen = length; +} + +int LinkLocator::maxUrlLen() const +{ + return mMaxUrlLen; +} + +void LinkLocator::setMaxAddressLen(int length) +{ + mMaxAddressLen = length; +} + +int LinkLocator::maxAddressLen() const +{ + return mMaxAddressLen; +} + +TQString LinkLocator::getUrl() +{ + TQString url; + if(atUrl()) + { + // handle cases like this: <link>http://foobar.org/</link> + int start = mPos; + while(mPos < (int)mText.length() && mText[mPos] > ' ' && mText[mPos] != '"' && + TQString("<>()[]").find(mText[mPos]) == -1) + { + ++mPos; + } + /* some URLs really end with: # / & - _ */ + const TQString allowedSpecialChars = TQString("#/&-_"); + while(mPos > start && mText[mPos-1].isPunct() && + allowedSpecialChars.find(mText[mPos-1]) == -1 ) + { + --mPos; + } + + url = mText.mid(start, mPos - start); + if(isEmptyUrl(url) || mPos - start > maxUrlLen()) + { + mPos = start; + url = ""; + } + else + { + --mPos; + } + } + return url; +} + +// keep this in sync with KMMainWin::slotUrlClicked() +bool LinkLocator::atUrl() const +{ + // the following characters are allowed in a dot-atom (RFC 2822): + // a-z A-Z 0-9 . ! # $ % & ' * + - / = ? ^ _ ` { | } ~ + const TQString allowedSpecialChars = TQString(".!#$%&'*+-/=?^_`{|}~"); + + // the character directly before the URL must not be a letter, a number or + // any other character allowed in a dot-atom (RFC 2822). + if( ( mPos > 0 ) && ( mText[mPos-1].isLetterOrNumber() || + ( allowedSpecialChars.find( mText[mPos-1] ) != -1 ) ) ) + return false; + + TQChar ch = mText[mPos]; + return (ch=='h' && ( mText.mid(mPos, 7) == "http://" || + mText.mid(mPos, 8) == "https://") ) || + (ch=='v' && mText.mid(mPos, 6) == "vnc://") || + (ch=='f' && ( mText.mid(mPos, 7) == "fish://" || + mText.mid(mPos, 6) == "ftp://" || + mText.mid(mPos, 7) == "ftps://") ) || + (ch=='s' && ( mText.mid(mPos, 7) == "sftp://" || + mText.mid(mPos, 6) == "smb://") ) || + (ch=='m' && mText.mid(mPos, 7) == "mailto:") || + (ch=='w' && mText.mid(mPos, 4) == "www.") || + (ch=='f' && mText.mid(mPos, 4) == "ftp.") || + (ch=='n' && mText.mid(mPos, 5) == "news:"); + // note: no "file:" for security reasons +} + +bool LinkLocator::isEmptyUrl(const TQString& url) +{ + return url.isEmpty() || + url == "http://" || + url == "https://" || + url == "fish://" || + url == "ftp://" || + url == "ftps://" || + url == "sftp://" || + url == "smb://" || + url == "vnc://" || + url == "mailto" || + url == "www" || + url == "ftp" || + url == "news" || + url == "news://"; +} + +TQString LinkLocator::getEmailAddress() +{ + TQString address; + + if ( mText[mPos] == '@' ) { + // the following characters are allowed in a dot-atom (RFC 2822): + // a-z A-Z 0-9 . ! # $ % & ' * + - / = ? ^ _ ` { | } ~ + const TQString allowedSpecialChars = TQString(".!#$%&'*+-/=?^_`{|}~"); + + // determine the local part of the email address + int start = mPos - 1; + while ( start >= 0 && mText[start].tqunicode() < 128 && + ( mText[start].isLetterOrNumber() || + mText[start] == '@' || // allow @ to find invalid email addresses + allowedSpecialChars.find( mText[start] ) != -1 ) ) { + if ( mText[start] == '@' ) + return TQString(); // local part contains '@' -> no email address + --start; + } + ++start; + // we assume that an email address starts with a letter or a digit + while ( ( start < mPos ) && !mText[start].isLetterOrNumber() ) + ++start; + if ( start == mPos ) + return TQString(); // local part is empty -> no email address + + // determine the domain part of the email address + int dotPos = INT_MAX; + int end = mPos + 1; + while ( end < (int)mText.length() && + ( mText[end].isLetterOrNumber() || + mText[end] == '@' || // allow @ to find invalid email addresses + mText[end] == '.' || + mText[end] == '-' ) ) { + if ( mText[end] == '@' ) + return TQString(); // domain part contains '@' -> no email address + if ( mText[end] == '.' ) + dotPos = TQMIN( dotPos, end ); // remember index of first dot in domain + ++end; + } + // we assume that an email address ends with a letter or a digit + while ( ( end > mPos ) && !mText[end - 1].isLetterOrNumber() ) + --end; + if ( end == mPos ) + return TQString(); // domain part is empty -> no email address + if ( dotPos >= end ) + return TQString(); // domain part doesn't contain a dot + + if ( end - start > maxAddressLen() ) + return TQString(); // too long -> most likely no email address + address = mText.mid( start, end - start ); + + mPos = end - 1; + } + return address; +} + +TQString LinkLocator::convertToHtml(const TQString& plainText, int flags, + int maxUrlLen, int maxAddressLen) +{ + LinkLocator locator(plainText); + locator.setMaxUrlLen(maxUrlLen); + locator.setMaxAddressLen(maxAddressLen); + + TQString str; + TQString result((TQChar*)0, (int)locator.mText.length() * 2); + TQChar ch; + int x; + bool startOfLine = true; + TQString emoticon; + + for (locator.mPos = 0, x = 0; locator.mPos < (int)locator.mText.length(); locator.mPos++, x++) + { + ch = locator.mText[locator.mPos]; + if ( flags & PreserveSpaces ) + { + if (ch==' ') + { + if (startOfLine) { + result += " "; + locator.mPos++, x++; + startOfLine = false; + } + while (locator.mText[locator.mPos] == ' ') + { + result += " "; + locator.mPos++, x++; + if (locator.mText[locator.mPos] == ' ') { + result += " "; + locator.mPos++, x++; + } + } + locator.mPos--, x--; + continue; + } + else if (ch=='\t') + { + do + { + result += " "; + x++; + } + while((x&7) != 0); + x--; + startOfLine = false; + continue; + } + } + if (ch=='\n') + { + result += "<br />"; + startOfLine = true; + x = -1; + continue; + } + + startOfLine = false; + if (ch=='&') + result += "&"; + else if (ch=='"') + result += """; + else if (ch=='<') + result += "<"; + else if (ch=='>') + result += ">"; + else + { + const int start = locator.mPos; + if ( !(flags & IgnoreUrls) ) { + str = locator.getUrl(); + if (!str.isEmpty()) + { + TQString hyperlink; + if(str.left(4) == "www.") + hyperlink = "http://" + str; + else if(str.left(4) == "ftp.") + hyperlink = "ftp://" + str; + else + hyperlink = str; + + str = str.replace('&', "&"); + result += "<a href=\"" + hyperlink + "\">" + str + "</a>"; + x += locator.mPos - start; + continue; + } + str = locator.getEmailAddress(); + if(!str.isEmpty()) + { + // len is the length of the local part + int len = str.find('@'); + TQString localPart = str.left(len); + + // remove the local part from the result (as '&'s have been expanded to + // & we have to take care of the 4 additional characters per '&') + result.truncate(result.length() - len - (localPart.contains('&')*4)); + x -= len; + + result += "<a href=\"mailto:" + str + "\">" + str + "</a>"; + x += str.length() - 1; + continue; + } + } + if ( flags & ReplaceSmileys ) { + str = locator.getEmoticon(); + if ( ! str.isEmpty() ) { + result += str; + x += locator.mPos - start; + continue; + } + } + if ( flags & HighlightText ) { + str = locator.highlightedText(); + if ( !str.isEmpty() ) { + result += str; + x += locator.mPos - start; + continue; + } + } + result += ch; + } + } + + return result; +} + +TQString LinkLocator::pngToDataUrl( const TQString & iconPath ) +{ + if ( iconPath.isEmpty() ) + return TQString(); + + TQFile pngFile( iconPath ); + if ( !pngFile.open( IO_ReadOnly | IO_Raw ) ) + return TQString(); + + TQByteArray ba = pngFile.readAll(); + pngFile.close(); + return TQString::tqfromLatin1("data:image/png;base64,%1") + .tqarg( KCodecs::base64Encode( ba ).data() ); +} + + +TQString LinkLocator::getEmoticon() +{ + // smileys have to be prepended by whitespace + if ( ( mPos > 0 ) && !mText[mPos-1].isSpace() ) + return TQString(); + + // since smileys start with ':', ';', '(' or '8' short circuit method + const TQChar ch = mText[mPos]; + if ( ch !=':' && ch != ';' && ch != '(' && ch != '8' ) + return TQString(); + + // find the end of the smiley (a smiley is at most 4 chars long and ends at + // lineend or whitespace) + const int MinSmileyLen = 2; + const int MaxSmileyLen = 4; + int smileyLen = 1; + while ( ( smileyLen <= MaxSmileyLen ) && + ( mPos+smileyLen < (int)mText.length() ) && + !mText[mPos+smileyLen].isSpace() ) + smileyLen++; + if ( smileyLen < MinSmileyLen || smileyLen > MaxSmileyLen ) + return TQString(); + + const TQString smiley = mText.mid( mPos, smileyLen ); + if ( !s_smileyEmoticonNameMap->contains( smiley ) ) + return TQString(); // that's not a (known) smiley + + TQString htmlRep; + if ( s_smileyEmoticonHTMLCache->contains( smiley ) ) { + htmlRep = (*s_smileyEmoticonHTMLCache)[smiley]; + } + else { + const TQString imageName = (*s_smileyEmoticonNameMap)[smiley]; + +#if KDE_IS_VERSION( 3, 3, 91 ) + const TQString iconPath = locate( "emoticons", + EmotIcons::theme() + + TQString::tqfromLatin1( "/" ) + + imageName + TQString::tqfromLatin1(".png") ); +#else + const TQString iconPath = locate( "data", + TQString::tqfromLatin1( "kopete/pics/emoticons/" )+ + EmotIcons::theme() + + TQString::tqfromLatin1( "/" ) + + imageName + TQString::tqfromLatin1(".png") ); +#endif + + const TQString dataUrl = pngToDataUrl( iconPath ); + if ( dataUrl.isEmpty() ) { + htmlRep = TQString(); + } + else { + // create an image tag (the text in attribute alt is used + // for copy & paste) representing the smiley + htmlRep = TQString("<img class=\"pimsmileyimg\" src=\"%1\" " + "alt=\"%2\" title=\"%3\" width=\"16\" height=\"16\"/>") + .tqarg( dataUrl, + TQStyleSheet::escape( smiley ), + TQStyleSheet::escape( smiley ) ); + } + s_smileyEmoticonHTMLCache->insert( smiley, htmlRep ); + } + + if ( !htmlRep.isEmpty() ) + mPos += smileyLen - 1; + + return htmlRep; +} + +TQString LinkLocator::highlightedText() +{ + // formating symbols must be prepended with a whitespace + if ( ( mPos > 0 ) && !mText[mPos-1].isSpace() ) + return TQString(); + + const TQChar ch = mText[mPos]; + if ( ch != '/' && ch != '*' && ch != '_' ) + return TQString(); + + TQRegExp re = TQRegExp( TQString("\\%1([0-9A-Za-z]+)\\%2").tqarg( ch ).tqarg( ch ) ); + if ( re.search( mText, mPos ) == mPos ) { + uint length = re.matchedLength(); + // there must be a whitespace after the closing formating symbol + if ( mPos + length < mText.length() && !mText[mPos + length].isSpace() ) + return TQString(); + mPos += length - 1; + switch ( ch.latin1() ) { + case '*': + return "<b>" + re.cap( 1 ) + "</b>"; + case '_': + return "<u>" + re.cap( 1 ) + "</u>"; + case '/': + return "<i>" + re.cap( 1 ) + "</i>"; + } + } + return TQString(); +} + |