You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdewebdev/klinkstatus/src/utils/xsl.cpp

438 lines
16 KiB

/***************************************************************************
* 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 <kapplication.h>
#include <kdebug.h>
#include <klocale.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::tqfromLatin1("\"%1\"").tqarg( KApplication::kApplication()->dirs()->findDirs("appdata", TQString::tqfromLatin1("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>" ).tqarg( 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::tqfromLatin1( "<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::tqfromLatin1( "TIME" ) )
{
trans += TQString::tqfromLatin1( "<xsl:value-of select=\"@time\"/>" );
}
else if ( *it == TQString::tqfromLatin1( "TIMESTAMP" ) )
{
trans += TQString::tqfromLatin1( "<xsl:value-of select=\"@timestamp\"/>" );
}
else if ( *it == TQString::tqfromLatin1( "FORMATTEDTIMESTAMP" ) )
{
trans += TQString::tqfromLatin1( "<xsl:value-of select=\"@formattedTimestamp\"/>" );
}
else if ( *it == TQString::tqfromLatin1( "FROM_CONTACT_DISPLAYNAME" ) )
{
trans += TQString::tqfromLatin1( "<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 == TQString::tqfromLatin1( "TO_CONTACT_DISPLAYNAME" ) )
{
trans += TQString::tqfromLatin1( "<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 == TQString::tqfromLatin1( "FROM_METACONTACT_DISPLAYNAME" ) )
{
trans += TQString::tqfromLatin1( "<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::tqfromLatin1( "TO_METACONTACT_DISPLAYNAME" ) )
{
trans += TQString::tqfromLatin1( "<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::tqfromLatin1( "FROM_CONTACT_ID" ) )
{
trans += TQString::tqfromLatin1( "<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::tqfromLatin1( "TO_CONTACT_ID" ) )
{
trans += TQString::tqfromLatin1( "<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::tqfromLatin1( "BODY" ) )
{
trans += TQString::tqfromLatin1( "<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::tqfromLatin1("|") ) );
}
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: