diff options
Diffstat (limited to 'klinkstatus/src/utils/xsl.cpp')
-rw-r--r-- | klinkstatus/src/utils/xsl.cpp | 437 |
1 files changed, 437 insertions, 0 deletions
diff --git a/klinkstatus/src/utils/xsl.cpp b/klinkstatus/src/utils/xsl.cpp new file mode 100644 index 00000000..c3b13412 --- /dev/null +++ b/klinkstatus/src/utils/xsl.cpp @@ -0,0 +1,437 @@ +/*************************************************************************** + * Copyright (C) 2004 by Paulo Moura Guedes * + * [email protected] * + * * + * 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. * + * * + * 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 "xsl.h" + +#include <libxml/globals.h> +#include <libxml/parser.h> + +// Don't try to sort the libxslt includes alphabetically! +// transform.h _HAS_ to be after xsltInternals.h and xsltconfig.h _HAS_ to be +// the first libxslt include or it will break the compilation on some +// libxslt versions +#include <libxslt/xsltconfig.h> +#include <libxslt/xsltInternals.h> +#include <libxslt/transform.h> + +// stdlib.h is required to build on Solaris +#include <stdlib.h> + +#include <qregexp.h> +#include <qsignal.h> +#include <qstylesheet.h> +#include <qthread.h> +#include <qevent.h> +#include <qmutex.h> + +#include <kapplication.h> +#include <kdebug.h> +#include <klocale.h> +#include <kstandarddirs.h> + +/** + * @author Jason Keirstead <[email protected]> + * + * The thread class that actually performs the XSL processing. + * Using a thread allows async operation. + */ +class KopeteXSLThread : public QObject, public QThread +{ +public: + /** + * Thread constructor + * + * @param xmlString The XML to be transformed + * @param xslString The XSL stylesheet we will use to transform + * @param target Target object to connect to for async operation + * @param slotCompleted Slot to fire on completion in asnc operation + */ + KopeteXSLThread( const QString &xmlString, xsltStylesheetPtr xslDoc, QObject *target = 0L, const char *slotCompleted = 0L ); + + /** + * Reimplemented from QThread. Does the processing. + */ + virtual void run(); + + /** + * A user event is used to get back to the UI thread to emit the completed signal + */ + bool event( QEvent *event ); + + static QString xsltTransform( const QString &xmlString, xsltStylesheetPtr xslDoc ); + + /** + * Returns the result string + */ + const QString &result() + { return m_resultString; }; + +private: + QString m_xml; + xsltStylesheetPtr m_xsl; + QString m_resultString; + QObject *m_target; + const char *m_slotCompleted; + QMutex dataMutex; +}; + +KopeteXSLThread::KopeteXSLThread( const QString &xmlString, xsltStylesheetPtr xslDoc, QObject *target, const char *slotCompleted ) +{ + m_xml = xmlString; + m_xsl = xslDoc; + + m_target = target; + m_slotCompleted = slotCompleted; +} + +void KopeteXSLThread::run() +{ + dataMutex.lock(); + m_resultString = xsltTransform( m_xml, m_xsl ); + dataMutex.unlock(); + // get back to the main thread + qApp->postEvent( this, new QEvent( QEvent::User ) ); +} + +bool KopeteXSLThread::event( QEvent *event ) +{ + if ( event->type() == QEvent::User ) + { + dataMutex.lock(); + if( m_target && m_slotCompleted ) + { + QSignal completeSignal( m_target ); + completeSignal.connect( m_target, m_slotCompleted ); + completeSignal.setValue( m_resultString ); + completeSignal.activate(); + } + dataMutex.unlock(); + delete this; + return true; + } + return QObject::event( event ); +} + +QString KopeteXSLThread::xsltTransform( const QString &xmlString, xsltStylesheetPtr styleSheet ) +{ + // Convert QString into a C string + QCString xmlCString = xmlString.utf8(); + + QString resultString; + QString errorMsg; + + xmlDocPtr xmlDoc = xmlParseMemory( xmlCString, xmlCString.length() ); + if ( xmlDoc ) + { + if ( styleSheet ) + { + static QCString appPath( QString::fromLatin1("\"%1\"").arg( KApplication::kApplication()->dirs()->findDirs("appdata", QString::fromLatin1("styles/data") ).front() ).utf8() ); + + static const char* params[3] = { + "appdata", + appPath, + NULL + }; + + xmlDocPtr resultDoc = xsltApplyStylesheet( styleSheet, xmlDoc, params ); + if ( resultDoc ) + { + // Save the result into the QString + xmlChar *mem; + int size; + xmlDocDumpMemory( resultDoc, &mem, &size ); + resultString = QString::fromUtf8( QCString( ( char * )( mem ), size + 1 ) ); + xmlFree( mem ); + xmlFreeDoc( resultDoc ); + } + else + { + errorMsg = i18n( "Message is null." ); + } + } + else + { + errorMsg = i18n( "The selected stylesheet is invalid." ); + } + + xmlFreeDoc( xmlDoc ); + } + else + { + errorMsg = i18n( "Message could not be parsed. This is likely due to an encoding problem." ); + } + + if ( resultString.isEmpty() ) + { + resultString = i18n( "<div><b>KLinkStatus encountered the following error while parsing a message:</b><br />%1</div>" ).arg( errorMsg ); + } + + #ifdef RAWXSL + kdDebug(23100) << k_funcinfo << resultString << endl; + #endif + return resultString; +} + +class XSLTPrivate +{ +public: + xmlDocPtr xslDoc; + xsltStylesheetPtr styleSheet; + unsigned int flags; +}; + +XSLT::XSLT( const QString &document, QObject *parent ) + : QObject( parent ), d(new XSLTPrivate) +{ + d->flags = 0; + d->xslDoc = 0; + d->styleSheet = 0; + + // Init Stuff + xmlLoadExtDtdDefaultValue = 0; + xmlSubstituteEntitiesDefault( 1 ); + + setXSLT( document ); +} + +XSLT::~XSLT() +{ + xsltFreeStylesheet( d->styleSheet ); + + delete d; +} + +void XSLT::setXSLT( const QString &_document ) +{ + // Search for '<kopete-i18n>' elements and feed them through i18n(). + // After that replace the %VAR% variables with their proper XSLT counterpart. + // + // FIXME: Preprocessing the document using the QString API is fast and simple, + // but also error-sensitive. + // In fact, there are a couple of known issues with this algorithm that + // depend on the strings in the current styles. If these strings change + // they may break the parsing here. + // + // The reason I'm doing it like this is because of issues with QDOM and + // namespaces in earlier Qt versions. When we drop Qt 3.1.x support we + // should probably convert this to more accurate DOM code. - Martijn + // + // Actually, since we need to parse into a libxml2 document anyway, this whole + // nonsense could be replaced with some simple XPath expressions - JK + // + QRegExp elementMatch( QString::fromLatin1( "<kopete-i18n>(.*)</kopete-i18n>" ) ); + elementMatch.setMinimal( true ); + QString document = _document; + int pos; + while ( ( pos = elementMatch.search( document ) ) != -1 ) + { + QString orig = elementMatch.cap( 1 ); + //kdDebug( 14010 ) << k_funcinfo << "Original text: " << orig << endl; + + // Split on % and go over all parts + // WARNING: If you change the translator comment, also change it in the + // styles/extracti18n Perl script, because the strings have to be + // identical! + QStringList parts = QStringList::split( '%', i18n( + "Translators: The %FOO% placeholders are variables that are substituted " + "in the code, please leave them untranslated", orig.utf8() ), true ); + + // The first part is always text, as our variables are written like %FOO% + QStringList::Iterator it = parts.begin(); + QString trans = *it; + bool prependPercent = true; + it = parts.remove( it ); + for ( it = parts.begin(); it != parts.end(); ++it ) + { + prependPercent = false; + + if ( *it == QString::fromLatin1( "TIME" ) ) + { + trans += QString::fromLatin1( "<xsl:value-of select=\"@time\"/>" ); + } + else if ( *it == QString::fromLatin1( "TIMESTAMP" ) ) + { + trans += QString::fromLatin1( "<xsl:value-of select=\"@timestamp\"/>" ); + } + else if ( *it == QString::fromLatin1( "FORMATTEDTIMESTAMP" ) ) + { + trans += QString::fromLatin1( "<xsl:value-of select=\"@formattedTimestamp\"/>" ); + } + else if ( *it == QString::fromLatin1( "FROM_CONTACT_DISPLAYNAME" ) ) + { + trans += QString::fromLatin1( "<span><xsl:attribute name=\"title\">" + "<xsl:choose>" + "<xsl:when test='from/contact/@contactId=from/contact/contactDisplayName/@text'>" + "<xsl:value-of disable-output-escaping=\"yes\" select=\"from/contact/metaContactDisplayName/@text\"/>" + "</xsl:when>" + "<xsl:otherwise>" + "<xsl:value-of disable-output-escaping=\"yes\" select=\"from/contact/metaContactDisplayName/@text\"/> " + "(<xsl:value-of disable-output-escaping=\"yes\" select=\"from/contact/@contactId\"/>)" + "</xsl:otherwise>" + "</xsl:choose></xsl:attribute>" + "<xsl:attribute name=\"dir\">" + "<xsl:value-of select=\"from/contact/contactDisplayName/@dir\"/>" + "</xsl:attribute>" + "<xsl:value-of disable-output-escaping=\"yes\" select=\"from/contact/contactDisplayName/@text\"/></span>" ); + } + else if ( *it == QString::fromLatin1( "TO_CONTACT_DISPLAYNAME" ) ) + { + trans += QString::fromLatin1( "<span><xsl:attribute name=\"title\">" + "<xsl:choose>" + "<xsl:when test='to/contact/@contactId=from/contact/contactDisplayName/@text'>" + "<xsl:value-of disable-output-escaping=\"yes\" select=\"to/contact/metaContactDisplayName/@text\"/>" + "</xsl:when>" + "<xsl:otherwise>" + "<xsl:value-of disable-output-escaping=\"yes\" select=\"to/contact/metaContactDisplayName/@text\"/> " + "(<xsl:value-of disable-output-escaping=\"yes\" select=\"to/contact/@contactId\"/>)" + "</xsl:otherwise>" + "</xsl:choose></xsl:attribute>" + "<xsl:attribute name=\"dir\">" + "<xsl:value-of select=\"to/contact/contactDisplayName/@dir\"/>" + "</xsl:attribute>" + "<xsl:value-of disable-output-escaping=\"yes\" select=\"to/contact/contactDisplayName/@text\"/></span>" ); + } + else if ( *it == QString::fromLatin1( "FROM_METACONTACT_DISPLAYNAME" ) ) + { + trans += QString::fromLatin1( "<span>" + "<xsl:attribute name=\"dir\">" + "<xsl:value-of select=\"from/contact/metaContactDisplayName/@dir\"/>" + "</xsl:attribute>" + "<xsl:value-of disable-output-escaping=\"yes\" select=\"from/contact/metaContactDisplayName/@text\"/></span>" ); + } + else if ( *it == QString::fromLatin1( "TO_METACONTACT_DISPLAYNAME" ) ) + { + trans += QString::fromLatin1( "<span>" + "<xsl:attribute name=\"dir\">" + "<xsl:value-of select=\"to/contact/metaContactDisplayName/@dir\"/>" + "</xsl:attribute>" + "<xsl:value-of disable-output-escaping=\"yes\" select=\"to/contact/metaContactDisplayName/@text\"/></span>" ); + } + else if ( *it == QString::fromLatin1( "FROM_CONTACT_ID" ) ) + { + trans += QString::fromLatin1( "<span><xsl:attribute name=\"title\">" + "<xsl:value-of disable-output-escaping=\"yes\" select=\"from/contact/contactDisplayName/@text\"/></xsl:attribute>" + "<xsl:value-of disable-output-escaping=\"yes\" select=\"from/contact/@contactId\"/></span>" ); + } + else if ( *it == QString::fromLatin1( "TO_CONTACT_ID" ) ) + { + trans += QString::fromLatin1( "<span><xsl:attribute name=\"title\">" + "<xsl:value-of disable-output-escaping=\"yes\" select=\"to/contact/contactDisplayName/@text\"/></xsl:attribute>" + "<xsl:value-of disable-output-escaping=\"yes\" select=\"to/contact/@contactId\"/></span>" ); + } + else if ( *it == QString::fromLatin1( "BODY" ) ) + { + trans += QString::fromLatin1( "<xsl:value-of disable-output-escaping=\"yes\" select=\"body\"/>" ); + } + else + { + if ( prependPercent ) + trans += '%'; + trans += *it; + prependPercent = true; + } + } + //kdDebug( 14010 ) << k_funcinfo << "Translated text: " << trans << endl; + // Add "<kopete-i18n>" and "</kopete-i18n>" to length, hence the '+ 27' + document.replace( uint( pos ), orig.length() + 27, trans ); + } + + #ifdef RAWXSL + kdDebug(14000) << k_funcinfo << document.utf8() << endl; + #endif + + //Freeing the stylesheet also frees the doc pointer; + xsltFreeStylesheet( d->styleSheet ); + d->styleSheet = 0; + d->xslDoc = 0; + d->flags = 0; + + QCString rawDocument = document.utf8(); + d->xslDoc = xmlParseMemory( rawDocument, rawDocument.length() ); + + if( d->xslDoc ) + { + d->styleSheet = xsltParseStylesheetDoc( d->xslDoc ); + if( d->styleSheet ) + { + // Check for flags + QStringList flags; + for( xmlNodePtr child = d->xslDoc->children; child != d->xslDoc->last; child = child->next ) + { + if( child->type == XML_PI_NODE ) + { + //We have a flag. Enable it; + QCString flagData( (const char*)child->content ); + + if( flagData.contains( "Flag:" ) ) + { + flags += flagData.mid(5); + } + } + } + + if( !flags.isEmpty() ) + setProperty("flags", flags.join( QString::fromLatin1("|") ) ); + } + else + { + kdWarning(14000) << "Invalid stylesheet provided" << endl; + + //We don't have a stylesheet, so free the doc pointer + xmlFreeDoc( d->xslDoc ); + d->styleSheet = 0; + d->xslDoc = 0; + } + } + else + { + kdWarning(14000) << "Invalid stylesheet provided" << endl; + d->xslDoc = 0; + } +} + +QString XSLT::transform( const QString &xmlString ) +{ + return KopeteXSLThread::xsltTransform( xmlString, d->styleSheet ); +} + +void XSLT::transformAsync( const QString &xmlString, QObject *target, const char *slotCompleted ) +{ + ( new KopeteXSLThread( xmlString, d->styleSheet, target, slotCompleted ) )->start(); +} + +bool XSLT::isValid() const +{ + return d->styleSheet != NULL; +} + +void XSLT::setFlags( unsigned int flags ) +{ + d->flags = flags; +} + +unsigned int XSLT::flags() const +{ + return d->flags; +} + +#include "xsl.moc" + +// vim: set noet ts=4 sts=4 sw=4: + |