/*************************************************************************** * Copyright (C) 2004 by Paulo Moura Guedes * * moura@kdewebdev.org * * * * 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 #include // 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 #include #include // stdlib.h is required to build on Solaris #include #include #include #include #include #include #include #include #include #include #include /** * @author Jason Keirstead * * 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( "
KLinkStatus encountered the following error while parsing a message:
%1
" ).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 '' 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( "(.*)" ) ); 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( "" ); } else if ( *it == QString::fromLatin1( "TIMESTAMP" ) ) { trans += QString::fromLatin1( "" ); } else if ( *it == QString::fromLatin1( "FORMATTEDTIMESTAMP" ) ) { trans += QString::fromLatin1( "" ); } else if ( *it == QString::fromLatin1( "FROM_CONTACT_DISPLAYNAME" ) ) { trans += QString::fromLatin1( "" "" "" "" "" "" " " "()" "" "" "" "" "" "" ); } else if ( *it == QString::fromLatin1( "TO_CONTACT_DISPLAYNAME" ) ) { trans += QString::fromLatin1( "" "" "" "" "" "" " " "()" "" "" "" "" "" "" ); } else if ( *it == QString::fromLatin1( "FROM_METACONTACT_DISPLAYNAME" ) ) { trans += QString::fromLatin1( "" "" "" "" "" ); } else if ( *it == QString::fromLatin1( "TO_METACONTACT_DISPLAYNAME" ) ) { trans += QString::fromLatin1( "" "" "" "" "" ); } else if ( *it == QString::fromLatin1( "FROM_CONTACT_ID" ) ) { trans += QString::fromLatin1( "" "" "" ); } else if ( *it == QString::fromLatin1( "TO_CONTACT_ID" ) ) { trans += QString::fromLatin1( "" "" "" ); } else if ( *it == QString::fromLatin1( "BODY" ) ) { trans += QString::fromLatin1( "" ); } else { if ( prependPercent ) trans += '%'; trans += *it; prependPercent = true; } } //kdDebug( 14010 ) << k_funcinfo << "Translated text: " << trans << endl; // Add "" and "" 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: