summaryrefslogtreecommitdiffstats
path: root/klinkstatus/src/utils/xsl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'klinkstatus/src/utils/xsl.cpp')
-rw-r--r--klinkstatus/src/utils/xsl.cpp437
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 *
+ * *
+ * 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\"/>&#160;"
+ "(<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\"/>&#160;"
+ "(<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:
+