/*************************************************************************** chatmessagestyle.cpp - description ------------------- begin : Sat Okt 29 2005 copyright : (C) 2005 by Diederik van der Boor email : "vdboor" --at-- "codingdomain.com" ***************************************************************************/ /*************************************************************************** * * * This program 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. * * * ***************************************************************************/ #include "chatmessagestyle.h" #include "../contact/contactbase.h" #include "../utils/richtextparser.h" #include "../utils/kmessshared.h" #include "../currentaccount.h" #include "../emoticonmanager.h" #include "../kmessdebug.h" #include "xsltransformation.h" #include <QDateTime> #include <QFile> #include <QList> #include <QRegExp> #include <QTextDocument> #include <KLocale> #include <KStandardDirs> #include <KUrl> // Originally from libxml/chvalid.h, but with the last check in xmlIsCharQ removed; // the last check was for four-byte characters, we only check shorts. Removing // the last check fixes two warnings on the console. :) #define xmlIsChar_ch(c) (((0x9 <= (c)) && ((c) <= 0xa)) || \ ((c) == 0xd) || \ (0x20 <= (c))) #define xmlIsCharQ(c) (((c) < 0x100) ? \ xmlIsChar_ch((c)) :\ (((0x100 <= (c)) && ((c) <= 0xd7ff)) || \ ((0xe000 <= (c)) && ((c) <= 0xfffd)))) // The constructor ChatMessageStyle::ChatMessageStyle() : QObject(0) , canConvert_(false) , contactFontColor_("#000000") , groupFollowupMessages_(false) , showTime_(true) , useContactFont_(false) , useEmoticons_(true) , useFontEffects_(true) , useFormatting_(false) { xslTransformation_ = new XslTransformation(); } // The destructor ChatMessageStyle::~ChatMessageStyle() { delete xslTransformation_; } // Return whether XSL conversion works with the current style bool ChatMessageStyle::canConvert() const { return canConvert_; } // Convert a chat message to HTML string QString ChatMessageStyle::convertMessage(const ChatMessage &message) { // Reset state, changed by parseMsnString pendingEmoticonTags_.clear(); // Create and convert the XML message if(xslTransformation_ == 0 || ! xslTransformation_->hasStylesheet()) { #ifdef KMESSDEBUG_CHATVIEW kWarning() << "no XSL theme loaded (theme=" << name_ << ")," << " using fallback theme." << endl; #endif // Create fallback message. return createFallbackMessage(message); } else { QString xmlMessage ( convertMessageToXml( message ) ); QString parsedMessage( xslTransformation_->convertXmlString(xmlMessage) ); // Strip DOCTYPE because it should only appear in convertMessageRoot() output parsedMessage = stripDoctype(parsedMessage); if( parsedMessage.isNull() ) { #ifdef KMESSDEBUG_CHATVIEW kWarning() << "XSL conversion failed (theme=" << name_ << ")," << " using fallback theme." << endl; #endif return createFallbackMessage(message); } else if( isEmptyResult(parsedMessage) ) { #ifdef KMESSDEBUG_CHATVIEW kWarning() << "XSL conversion returned no data (theme=" << name_ << ")," << " using fallback theme." << endl; #endif return createFallbackMessage(message); } else { // Received expected result return parsedMessage; // DONE! } } } // Convert a group of chat message to HTML string QString ChatMessageStyle::convertMessageList(const QList<ChatMessage*> &messageList) { // Reset state, changed by parseMsnString pendingEmoticonTags_.clear(); // Warn for empty list, avoid broken markup if( messageList.isEmpty() ) { kWarning() << "no messages given!"; return QString::null; } else if( messageList.count() == 1 ) { // When there is only one message, don't wrap it in a <messagegroup>. // Makes the calling-code easier, and the layout consistent. return convertMessage( *messageList.first() ); } // Create iterator QListIterator<ChatMessage*> it( messageList ); bool hasStyle = (xslTransformation_ != 0 && xslTransformation_->hasStylesheet()); QString parsedMessage; // Avoid all the trouble when we can't create if( ! hasStyle ) { #ifdef KMESSDEBUG_CHATVIEW kWarning() << "no XSL theme loaded (theme=" << name_ << ")," << " using fallback theme." << endl; #endif } else { // Create XML string. QDateTime lastMessageDate; QString xmlMessage( "<messagegroup>\n" ); while( it.hasNext() ) { const ChatMessage &message = *it.next(); // Do not group messages which are distant in time by more than 10 minutes if( lastMessageDate.isValid() && lastMessageDate.secsTo( message.getDateTime() ) > 600 ) { // Flush the XML to the parser, then start again for the next messages xmlMessage += "</messagegroup>\n"; parsedMessage += xslTransformation_->convertXmlString( xmlMessage ); xmlMessage = "<messagegroup>\n"; } xmlMessage += convertMessageToXml( message ) + "\n"; lastMessageDate = message.getDateTime(); } xmlMessage += "</messagegroup>\n"; // Convert the current messages parsedMessage += xslTransformation_->convertXmlString(xmlMessage); // Strip DOCTYPE because it should only appear in convertMessageRoot() output parsedMessage = stripDoctype(parsedMessage); if( parsedMessage.isNull() ) { #ifdef KMESSDEBUG_CHATVIEW kWarning() << "XSL conversion failed (theme=" << name_ << ")," << " using fallback theme." << endl; #endif } else if( isEmptyResult(parsedMessage) ) { #ifdef KMESSDEBUG_CHATVIEW kWarning() << "XSL conversion returned no data (theme=" << name_ << ")," << " using fallback theme." << endl; #endif } else { // Received expected result return parsedMessage; // DONE! } } // Conversion failed, create fallback while( it.hasNext() ) { parsedMessage += createFallbackMessage( *it.next() ); } return parsedMessage; } // Convert a string with XML to HTML QString ChatMessageStyle::convertXmlMessageList( const QString &xmlMessagesList ) { QString parsedMessage; if( xslTransformation_ != 0 && xslTransformation_->hasStylesheet() ) { // Convert parsedMessage = xslTransformation_->convertXmlString( xmlMessagesList ); // Strip DOCTYPE because it should only appear in convertMessageRoot() output parsedMessage = stripDoctype( parsedMessage ); if( parsedMessage.isNull() ) { #ifdef KMESSDEBUG_CHATVIEW kWarning() << "XSL conversion failed (theme=" << name_ << "), cannot convert."; #endif } else if( isEmptyResult( parsedMessage ) ) { #ifdef KMESSDEBUG_CHATVIEW kWarning() << "XSL conversion returned no data (theme=" << name_ << "), cannot convert."; #endif } else { // Received expected result return parsedMessage; } } #ifdef KMESSDEBUG_CHATVIEW kWarning() << "no XSL theme loaded (theme=" << name_ << "), cannot convert."; #endif // Conversion failed, return the original string. // TODO: Parse the XML (using QDom classes or regexps) and create // ChatMessages to send to createFallbackMessage(). return parsedMessage; } // Convert the message root. QString ChatMessageStyle::convertMessageRoot() { QString parsedMessage; // Avoid all the trouble when we can't create a style if( ! hasStyle() ) { #ifdef KMESSDEBUG_CHATVIEW kWarning() << "no XSL theme loaded."; #endif } else { // Not much to add here yet.. QString xmlMessage( "<messageRoot>" "</messageRoot>" ); // Convert parsedMessage = xslTransformation_->convertXmlString(xmlMessage); if( ! parsedMessage.isNull() ) { if( isEmptyResult(parsedMessage) ) { // Indicate the chat style doesn't define the whole header/footer, // so KMess can use it's default instead. return QString::null; } return parsedMessage; // DONE! } else { #ifdef KMESSDEBUG_CHATVIEW kWarning() << "XSL conversion failed (theme=" << name_ << ")."; #endif } } return QString::null; } // Convert the message as HTML, fallback method when XML fails. QString ChatMessageStyle::createFallbackMessage(const ChatMessage &message) { QString color; int type = message.getType(); QString handle ( message.getContactHandle() ); QString name ( message.getContactName() ); QString body ( message.getBody() ); QString fontDir ( body.isRightToLeft() ? "rtl" : "ltr" ); // Reset state, changed by parseMsnString pendingEmoticonTags_.clear(); // Create the fallback message when the XML/XSL conversion failed if( message.isNormalMessage() ) { // Contact message QString fontBefore; QString fontAfter; QString time; QFont font; QString color; // Extract fonts parseFont(message, font, color, fontBefore, fontAfter); // Escape HTML, replace standard emoticons, links and formatting RichTextParser::parseMsnString( name, useEmoticons_, true, false, useFormatting_ ); // Replace font special effects, like *bold* if( useFontEffects_ ) { parseEffects( name ); } // Body can already be HTML if generated internally. if( ! message.hasHtmlBody() ) { // Escape HTML, replace all emoticons, links and formatting RichTextParser::parseMsnString( body, useEmoticons_, false, true, useFormatting_, handle, pendingEmoticonTags_ ); // Replace font special effects, like *bold* if( useFontEffects_ ) { parseEffects( body ); } } // Replace newlines with <br/> parseBody(body); // Misc variables if( showTime_ ) { QDateTime messageTime = message.getDateTime(); if ( showDate_ ) { time = KGlobal::locale()->formatDateTime( messageTime, KLocale::ShortDate, showSeconds_ ); } else { time = KGlobal::locale()->formatTime( messageTime.time(), showSeconds_ ); } } color = (type == ChatMessage::TYPE_INCOMING || type == ChatMessage::TYPE_OFFLINE_INCOMING) ? "666699" : "666666"; // Create HTML return "<div dir='" + fontDir + "'><font color='" + color + "'>" + (showTime_ ? time + " " : "") + i18n("%1 says:", name) + "<br></font>" + fontBefore + body + "<br><br>" + fontAfter + "</div>"; } else if( type == ChatMessage::TYPE_SYSTEM ) { // Red system message parseBody( KMessShared::htmlEscape( body ) ); return "<div dir='" + fontDir + "'><hr size='2' color='red'>" "<font color='red'>" + body + "</font>" "<hr size='2' color='red'></div>"; } else if( type == ChatMessage::TYPE_APPLICATION || type == ChatMessage::TYPE_APPLICATION_WEBCAM || type == ChatMessage::TYPE_APPLICATION_FILE || type == ChatMessage::TYPE_APPLICATION_AUDIO ) { // Blue application message parseBody( KMessShared::htmlEscape( body ) ); return "<div dir='" + fontDir + "'><font color='blue'>" + body + "</font><br></div>"; } else if( type == ChatMessage::TYPE_PRESENCE ) { // Gray presence message parseBody( KMessShared::htmlEscape( body ) ); return "<div dir='" + fontDir + "'><font color='gray'>" + body + "</font><br></div>"; } else { // Purple notification message parseBody( KMessShared::htmlEscape( body ) ); return "<div dir='" + fontDir + "'><font color='purple'>" + body + "</font><br></div>"; } } // Convert the message as XML. QString ChatMessageStyle::convertMessageToXml( const ChatMessage &message, bool isHistory ) { uint timestamp = 0; QString fontBefore; QString fontAfter; QString xmlMessage; QString parsedMessage; QString typeString; QString color; QFont font; // Remove any characters which are not allowed in XML const ushort* utf16 = message.getBody().utf16(); ushort* newutf16 = new ushort[ message.getBody().size() ]; int j = 0; for( int i = 0; i < message.getBody().size(); ++i ) { // Utf16 sometimes uses more than two bytes for a character, but in those cases, // both parts will be valid, so xmlIsCharQ (checking if a short is valid) will always return true. if( xmlIsCharQ( utf16[i] ) ) { newutf16[ j++ ] = utf16[i]; } #ifdef KMESSDEBUG_CHATVIEW else { kDebug() << "Found invalid character with code " << (int)utf16[i] << ", not showing"; } #endif } QString body = QString::fromUtf16( newutf16, j ); // Get message info int type = message.getType(); QString handle ( message.getContactHandle() ); QString name ( message.getContactName() ); bool isRtl = body.isRightToLeft(); bool nameIsRtl = name.isRightToLeft(); // See if this is a contact's message and not the user // Prepare the strings for HTML // App messages are not parsed, they may contain HTML (links to click on, etc..) if( message.isNormalMessage() ) { // Extract fonts parseFont(message, font, color, fontBefore, fontAfter); // Escape HTML, replace standard emoticons, links and formatting RichTextParser::parseMsnString( name, useEmoticons_, true, false, useFormatting_ ); // Parse effects characters (_, *, /) if( useFontEffects_ ) { parseEffects( name ); } // Body can already be HTML if generated internally. if( ! message.hasHtmlBody() ) { // Escape HTML, replace all emoticons, links and formatting RichTextParser::parseMsnString( body, useEmoticons_, false, true, useFormatting_, handle, pendingEmoticonTags_ ); if( useFontEffects_ ) { parseEffects( body ); } } } // Replace newlines with <br/> tags parseBody(body); // Show the message time // TODO: maybe timestamps should always available in the histories, just not necessarily shown? if( showTime_ ) { // Get the time const QDateTime &messageDate = message.getDateTime(); timestamp = messageDate.toTime_t(); } // Get message type for XSL switch(type) { case ChatMessage::TYPE_INCOMING: typeString = "incoming"; break; case ChatMessage::TYPE_OUTGOING: typeString = "outgoing"; break; case ChatMessage::TYPE_SYSTEM: typeString = "system"; break; case ChatMessage::TYPE_APPLICATION_FILE: case ChatMessage::TYPE_APPLICATION_WEBCAM: case ChatMessage::TYPE_APPLICATION_AUDIO: case ChatMessage::TYPE_APPLICATION: typeString = "application"; break; case ChatMessage::TYPE_OFFLINE_INCOMING: typeString = "offlineIncoming"; break; case ChatMessage::TYPE_PRESENCE: typeString = "presence"; break; case ChatMessage::TYPE_NOTIFICATION: default: typeString = "notification"; break; } // IMPORTANT: // The QStyleSheet::escape() function doesn't escape quotes. // Special care needs to be taken to avoid XML parsing errors. // Create an XML message for the XSL theme conversion // "<?xml version='1.0'?>" if ( isHistory ) { xmlMessage = "<message" " type='" + KMessShared::htmlEscape( typeString ) + "'" + " timestamp='" + QString::number( timestamp ) + "'" + ">\n"; } else { QString timeString; const QDateTime &datetime = QDateTime::fromTime_t( timestamp ); if ( showDate_ ) { timeString = KGlobal::locale()->formatDateTime( datetime, KLocale::ShortDate, showSeconds_ ); } else { timeString = KGlobal::locale()->formatTime( datetime.time(), showSeconds_ ); } xmlMessage = "<message" " type='" + KMessShared::htmlEscape( typeString ) + "'" + ( ! showTime_ ? QString() : " time='" + timeString + "'" ) + ">\n"; } // The contact can be empty for some notification messges if(! message.getContactHandle().isEmpty() ) { xmlMessage += " <from>\n" " <contact contactId='" + KMessShared::htmlEscape( message.getContactHandle() ) + "'>\n"; // If the user doesn't have a display picture, the style will use a default image. if( ! message.getContactPicturePath().isEmpty() && QFile::exists( message.getContactPicturePath() ) ) { xmlMessage += " <displayPicture url='" + KMessShared::htmlEscape( message.getContactPicturePath() ) + "' />\n"; } if( ! name.isEmpty() ) { xmlMessage += " <displayName text='" + KMessShared::htmlEscape( name ) + "' dir='" + (nameIsRtl ? "rtl" : "ltr") + "' />\n"; } xmlMessage += " </contact>\n" " </from>\n"; } // Add message xmlMessage += " <body" " color='" + KMessShared::htmlEscape( color ) + "'" " fontFamily='" + KMessShared::htmlEscape( font.family() ) + "'" " fontSize='" + QString::number( font.pointSize() ) + "'" " fontBold='" + (font.bold() ? "1" : "0") + "'" " fontItalic='" + (font.italic() ? "1" : "0") + "'" " fontUnderline='" + (font.underline() ? "1" : "0") + "'" " fontBefore='" + KMessShared::htmlEscape( fontBefore ) + "'" " fontAfter='" + KMessShared::htmlEscape( fontAfter ) + "'" " dir='" + (isRtl ? "rtl" : "ltr") + "'" ">\n" + KMessShared::htmlEscape( body ) + '\n' + "</body>\n" // escape again for XML "</message>\n"; return xmlMessage; } // Return the base folder of the style. const QString & ChatMessageStyle::getBaseFolder() const { return baseFolder_; } // Return the font used for contact messages, if forced to. const QFont& ChatMessageStyle::getContactFont() const { return contactFont_; } // Return the color of the forced contact font. const QString& ChatMessageStyle::getContactFontColor() const { return contactFontColor_; } // Return the xsl stylesheet. Return null if there is none. QString ChatMessageStyle::getStyleSheet() const { QString xslFile( baseFolder_ + name_ + ".xsl" ); if( ! hasStyle() || ! QFile::exists( xslFile ) ) { return QString(); } return xslFile; } // Return the css file attached to the stylesheet. Return null if there is none. QString ChatMessageStyle::getCssFile() const { QString cssFile( baseFolder_ + name_ + ".css" ); if( ! QFile::exists(cssFile) ) { return QString::null; } else { return cssFile; } } // Return the currently used emoticon style const QString &ChatMessageStyle::getEmoticonStyle() const { return emoticonStyle_; } // Return the grow follow up messages bool ChatMessageStyle::getGroupFollowupMessages() const { return groupFollowupMessages_; } // Return the name of the style. const QString & ChatMessageStyle::getName() const { return name_; } // Return if the date is displayed in messages bool ChatMessageStyle::getShowMessageDate() const { return showDate_; } // Return if the seconds are displayed in the message times bool ChatMessageStyle::getShowMessageSeconds() const { return showSeconds_; } // Return if the time is displayed in messages bool ChatMessageStyle::getShowMessageTime() const { return showTime_; } // Return the ID's of inserted <img> tags for the pending emoticons. const QStringList & ChatMessageStyle::getPendingEmoticonTagIds() const { return pendingEmoticonTags_; } // Return whether or not to show text formatting in chat messages bool ChatMessageStyle::getUseChatFormatting() const { return useFormatting_; } // Return whether or not to show contact messages in the stored font. bool ChatMessageStyle::getUseContactFont() const { return useContactFont_; } // Return whether or not the emoticons are visible bool ChatMessageStyle::getUseEmoticons() const { return useEmoticons_; } // Return whether or not the font effects are visible bool ChatMessageStyle::getUseFontEffects() const { return useFontEffects_; } // Return whether or not the formatting is visible bool ChatMessageStyle::getUseFormatting() const { return useFormatting_; } // Return whether the given result is empty bool ChatMessageStyle::isEmptyResult( const QString &parsedMessage ) { return stripDoctype(parsedMessage).trimmed().isEmpty(); } // Return whether an style is loaded. bool ChatMessageStyle::hasStyle() const { return (xslTransformation_ != 0 && xslTransformation_->hasStylesheet()); } // Replace the newline characters void ChatMessageStyle::parseBody(QString &body) const { // Replace any newline characters in the message with "<br>" so that carriage returns will show properly. body = body.replace(QRegExp("\r?\n?$"), QString::null) // Remove last \n .replace( "\r\n", "<br/>" ) .replace( '\r', "<br/>" ) .replace( '\n', "<br/>" ); } // Do some effects characters (ie, bold, underline and italic specials) void ChatMessageStyle::parseEffects(QString &text) const { int offset = 0; int nextOffset = 0; QRegExp effectsSearch; QString effectsCharacter; QChar boundaryCharacter; int tagPosOpen, tagPosClose; QString replacement; // Process bold, italics, underline const char* effectHtml = "biu"; // bold, italic, underline QString effectCharacters( "*/_" ); for( int i = 0; i < 3; i++ ) { effectsCharacter = QRegExp::escape( effectCharacters.mid(i, 1) ); effectsSearch.setPattern( effectsCharacter + "([\\d\\w\\s]+)" + effectsCharacter ); offset = 0; while( offset >= 0 ) { // Find next pattern match offset = effectsSearch.indexIn( text, nextOffset ); if( offset == -1 ) { break; } // Skip effects contained in HTML tags tagPosOpen = text.indexOf( "<", nextOffset ); tagPosClose = text.indexOf( ">", tagPosOpen ); if( tagPosOpen != -1 && tagPosClose != -1 && offset > tagPosOpen && offset < tagPosClose ) { // Continue from the first character after the HTML tag nextOffset = tagPosClose + 1; continue; } nextOffset = offset + effectsSearch.matchedLength(); // Abort if there is a normal char before if( offset != 0 ) { boundaryCharacter = text.at( offset - 1 ); if( ! boundaryCharacter.isSpace() && ! boundaryCharacter.isPunct() ) { continue; } } // Abort if there is a normal char after if( ( offset + effectsSearch.matchedLength() ) < text.length() ) { boundaryCharacter = text.at( offset + effectsSearch.matchedLength() ); if( ! boundaryCharacter.isSpace() && ! boundaryCharacter.isPunct() ) { continue; } } // Replace if start and end are free. if( effectHtml[i] == 'b' ) { // Bold is standard replacement = "*<b>" + effectsSearch.cap(1) + "</b>*"; } else if( effectHtml[i] == 'i' ) { // Same for italics replacement = "/<i>" + effectsSearch.cap(1) + "</i>/"; } else if( effectHtml[i] == 'u' ) { // Same for underline replacement = "_<u>" + effectsSearch.cap(1) + "</u>_"; } // Replace and change offset text.replace(offset, effectsSearch.matchedLength(), replacement); nextOffset = offset + replacement.length() + 2; } } } // Parse the font tags void ChatMessageStyle::parseFont(const ChatMessage &message, QFont &font, QString &color, QString& fontBefore, QString& fontAfter) const { // Extract the font from the message. ChatMessage::MessageType type = message.getType(); if( useContactFont_ && (type == ChatMessage::TYPE_INCOMING || type == ChatMessage::TYPE_OFFLINE_INCOMING) ) { // Replace the given font with the user's stored contact font font = contactFont_; color = contactFontColor_; } else { // Use the font settings from the message font = message.getFont(); color = message.getFontColor(); } // Include the dir="rtl" tag to the font so the text is at least displayed in the right direction. // To make it align right as well, the dir needs to be assigned to a block element. // This can't be done here as it would influence the chat style. QString fontDir( message.getBody().isRightToLeft() ? "rtl" : "ltr" ); // Set the font size, be it the user's or the contacts' QString size( " style=\"font-size:" + QString::number( font.pointSize() ) + "pt\"" ); // Create the font HTML for the message fontBefore.clear(); fontAfter.clear(); if( font.bold() ) fontBefore += "<b>"; if( font.italic() ) fontBefore += "<i>"; if( font.underline() ) fontBefore += "<u>"; fontBefore += "<font face=\"" + font.family() + "\" color=\"" + color + "\" dir=\"" + fontDir + "\"" + size + ">"; fontAfter += "</font>"; if( font.underline() ) fontAfter += "</u>"; if( font.italic() ) fontAfter += "</i>"; if( font.bold() ) fontAfter += "</b>"; } // The the contact font void ChatMessageStyle::setContactFont(const QFont &font) { contactFont_ = font; } // The the contact font color void ChatMessageStyle::setContactFontColor(const QString &fontColor) { contactFontColor_ = fontColor; } // Set the emoticon style void ChatMessageStyle::setEmoticonStyle( const QString &style ) { emoticonStyle_ = style; } // Set grow follow up messages void ChatMessageStyle::setGroupFollowupMessages( bool groupFollowupMessages ) { groupFollowupMessages_ = groupFollowupMessages; } // Set the show time state void ChatMessageStyle::setShowTime(bool showTime) { showTime_ = showTime; } // Set the show date state void ChatMessageStyle::setShowDate(bool showDate) { showDate_ = showDate; } // Set the show seconds state void ChatMessageStyle::setShowSeconds(bool showSeconds) { showSeconds_ = showSeconds; } // Set the message style, return false if it failed bool ChatMessageStyle::setStyle(const QString &style) { // Avoid reloading the same style again if( style == name_ ) { return hasStyle(); } KStandardDirs *dirs = KGlobal::dirs(); QString path( dirs->findResource( "data", "kmess/styles/" + style + "/" + style + ".xsl" ) ); if(path.isNull()) { kWarning() << "could not find the style named '" << style << "'."; return false; } // Set stylesheet name_ = style; xslTransformation_->setStylesheet(path); bool styleSheetLoaded = xslTransformation_->hasStylesheet(); if(styleSheetLoaded) { // Update the base folder KUrl pathUrl; pathUrl.setPath(path); baseFolder_ = pathUrl.directory( KUrl::AppendTrailingSlash ); QMap<QString,QString> parameters; parameters["basepath"] = baseFolder_; parameters["csspath"] = getCssFile(); xslTransformation_->setParameters(parameters); #ifdef KMESSDEBUG_CHATVIEW kDebug() << "Style" << style << "loaded from path:" << path << ". Base folder:" << baseFolder_; #endif } #ifdef KMESSDEBUG_CHATVIEW else { kDebug() << "Unable to load style:" << style << "from path:" << path; } #endif // Also verify if the XSL conversion works with the new style canConvert_ = ( ! xslTransformation_->convertXmlString( "<messageRoot></messageRoot>" ).isEmpty() ); return styleSheetLoaded; } // Enable or disable contact font overrides void ChatMessageStyle::setUseContactFont(bool useContactFont) { useContactFont_ = useContactFont; } // Enable or disable emoticons void ChatMessageStyle::setUseEmoticons(bool useEmoticons) { useEmoticons_ = useEmoticons; } // Enable or disable font effects void ChatMessageStyle::setUseFontEffects(bool useFontEffects) { useFontEffects_ = useFontEffects; } // Enable or disable MSN Plus formatting void ChatMessageStyle::setUseFormatting( bool useFormatting ) { useFormatting_ = useFormatting; } // Strip the DOCTYPE tag from the message QString ChatMessageStyle::stripDoctype( const QString &parsedMessage ) { if( parsedMessage.startsWith( "<!DOCTYPE" ) ) { QRegExp re(">\r?\n?"); int endPos = parsedMessage.indexOf( re ); if( endPos == -1 ) { kWarning() << "Could not strip DOCTYPE tag: end position not found!"; return parsedMessage; } // Strip both end end possible \r\n character return parsedMessage.mid( endPos + re.matchedLength() ); } else { return parsedMessage; } } #include "chatmessagestyle.moc"