|
|
|
/***************************************************************************
|
|
|
|
* Copyright (C) 2004 by Paulo Moura Guedes *
|
|
|
|
* moura@tdewebdev.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 <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 <tqregexp.h>
|
|
|
|
#include <tqsignal.h>
|
|
|
|
#include <tqstylesheet.h>
|
|
|
|
#include <tqthread.h>
|
|
|
|
#include <tqevent.h>
|
|
|
|
#include <tqmutex.h>
|
|
|
|
|
|
|
|
#include <tdeapplication.h>
|
|
|
|
#include <kdebug.h>
|
|
|
|
#include <tdelocale.h>
|
|
|
|
#include <kstandarddirs.h>
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @author Jason Keirstead <jason@keirstead.org>
|
|
|
|
*
|
|
|
|
* The thread class that actually performs the XSL processing.
|
|
|
|
* Using a thread allows async operation.
|
|
|
|
*/
|
|
|
|
class KopeteXSLThread : public TQObject, public TQThread
|
|
|
|
{
|
|
|
|
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 TQString &xmlString, xsltStylesheetPtr xslDoc, TQObject *target = 0L, const char *slotCompleted = 0L );
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reimplemented from TQThread. 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( TQEvent *event );
|
|
|
|
|
|
|
|
static TQString xsltTransform( const TQString &xmlString, xsltStylesheetPtr xslDoc );
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the result string
|
|
|
|
*/
|
|
|
|
const TQString &result()
|
|
|
|
{ return m_resultString; };
|
|
|
|
|
|
|
|
private:
|
|
|
|
TQString m_xml;
|
|
|
|
xsltStylesheetPtr m_xsl;
|
|
|
|
TQString m_resultString;
|
|
|
|
TQObject *m_target;
|
|
|
|
const char *m_slotCompleted;
|
|
|
|
TQMutex dataMutex;
|
|
|
|
};
|
|
|
|
|
|
|
|
KopeteXSLThread::KopeteXSLThread( const TQString &xmlString, xsltStylesheetPtr xslDoc, TQObject *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
|
|
|
|
tqApp->postEvent( this, new TQEvent( TQEvent::User ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KopeteXSLThread::event( TQEvent *event )
|
|
|
|
{
|
|
|
|
if ( event->type() == TQEvent::User )
|
|
|
|
{
|
|
|
|
dataMutex.lock();
|
|
|
|
if( m_target && m_slotCompleted )
|
|
|
|
{
|
|
|
|
TQSignal completeSignal( m_target );
|
|
|
|
completeSignal.connect( m_target, m_slotCompleted );
|
|
|
|
completeSignal.setValue( m_resultString );
|
|
|
|
completeSignal.activate();
|
|
|
|
}
|
|
|
|
dataMutex.unlock();
|
|
|
|
delete this;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return TQObject::event( event );
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString KopeteXSLThread::xsltTransform( const TQString &xmlString, xsltStylesheetPtr styleSheet )
|
|
|
|
{
|
|
|
|
// Convert TQString into a C string
|
|
|
|
TQCString xmlCString = xmlString.utf8();
|
|
|
|
|
|
|
|
TQString resultString;
|
|
|
|
TQString errorMsg;
|
|
|
|
|
|
|
|
xmlDocPtr xmlDoc = xmlParseMemory( xmlCString, xmlCString.length() );
|
|
|
|
if ( xmlDoc )
|
|
|
|
{
|
|
|
|
if ( styleSheet )
|
|
|
|
{
|
|
|
|
static TQCString appPath( TQString::fromLatin1("\"%1\"").arg( TDEApplication::kApplication()->dirs()->findDirs("appdata", TQString::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 TQString
|
|
|
|
xmlChar *mem;
|
|
|
|
int size;
|
|
|
|
xmlDocDumpMemory( resultDoc, &mem, &size );
|
|
|
|
resultString = TQString::fromUtf8( TQCString( ( 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 TQString &document, TQObject *parent )
|
|
|
|
: TQObject( 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 TQString &_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 TQString 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 TQDOM and
|
|
|
|
// namespaces in earlier TQt versions. When we drop TQt 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
|
|
|
|
//
|
|
|
|
TQRegExp elementMatch( TQString::fromLatin1( "<kopete-i18n>(.*)</kopete-i18n>" ) );
|
|
|
|
elementMatch.setMinimal( true );
|
|
|
|
TQString document = _document;
|
|
|
|
int pos;
|
|
|
|
while ( ( pos = elementMatch.search( document ) ) != -1 )
|
|
|
|
{
|
|
|
|
TQString 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!
|
|
|
|
TQStringList parts = TQStringList::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%
|
|
|
|
TQStringList::Iterator it = parts.begin();
|
|
|
|
TQString trans = *it;
|
|
|
|
bool prependPercent = true;
|
|
|
|
it = parts.remove( it );
|
|
|
|
for ( it = parts.begin(); it != parts.end(); ++it )
|
|
|
|
{
|
|
|
|
prependPercent = false;
|
|
|
|
|
|
|
|
if ( *it == TQString::fromLatin1( "TIME" ) )
|
|
|
|
{
|
|
|
|
trans += TQString::fromLatin1( "<xsl:value-of select=\"@time\"/>" );
|
|
|
|
}
|
|
|
|
else if ( *it == TQString::fromLatin1( "TIMESTAMP" ) )
|
|
|
|
{
|
|
|
|
trans += TQString::fromLatin1( "<xsl:value-of select=\"@timestamp\"/>" );
|
|
|
|
}
|
|
|
|
else if ( *it == TQString::fromLatin1( "FORMATTEDTIMESTAMP" ) )
|
|
|
|
{
|
|
|
|
trans += TQString::fromLatin1( "<xsl:value-of select=\"@formattedTimestamp\"/>" );
|
|
|
|
}
|
|
|
|
else if ( *it == TQString::fromLatin1( "FROM_CONTACT_DISPLAYNAME" ) )
|
|
|
|
{
|
|
|
|
trans += TQString::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 == TQString::fromLatin1( "TO_CONTACT_DISPLAYNAME" ) )
|
|
|
|
{
|
|
|
|
trans += TQString::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 == TQString::fromLatin1( "FROM_METACONTACT_DISPLAYNAME" ) )
|
|
|
|
{
|
|
|
|
trans += TQString::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 == TQString::fromLatin1( "TO_METACONTACT_DISPLAYNAME" ) )
|
|
|
|
{
|
|
|
|
trans += TQString::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 == TQString::fromLatin1( "FROM_CONTACT_ID" ) )
|
|
|
|
{
|
|
|
|
trans += TQString::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 == TQString::fromLatin1( "TO_CONTACT_ID" ) )
|
|
|
|
{
|
|
|
|
trans += TQString::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 == TQString::fromLatin1( "BODY" ) )
|
|
|
|
{
|
|
|
|
trans += TQString::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;
|
|
|
|
|
|
|
|
TQCString rawDocument = document.utf8();
|
|
|
|
d->xslDoc = xmlParseMemory( rawDocument, rawDocument.length() );
|
|
|
|
|
|
|
|
if( d->xslDoc )
|
|
|
|
{
|
|
|
|
d->styleSheet = xsltParseStylesheetDoc( d->xslDoc );
|
|
|
|
if( d->styleSheet )
|
|
|
|
{
|
|
|
|
// Check for flags
|
|
|
|
TQStringList 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;
|
|
|
|
TQCString flagData( (const char*)child->content );
|
|
|
|
|
|
|
|
if( flagData.contains( "Flag:" ) )
|
|
|
|
{
|
|
|
|
flags += flagData.mid(5);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( !flags.isEmpty() )
|
|
|
|
setProperty("flags", flags.join( TQString::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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString XSLT::transform( const TQString &xmlString )
|
|
|
|
{
|
|
|
|
return KopeteXSLThread::xsltTransform( xmlString, d->styleSheet );
|
|
|
|
}
|
|
|
|
|
|
|
|
void XSLT::transformAsync( const TQString &xmlString, TQObject *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:
|
|
|
|
|