Logo Search packages:      
Sourcecode: kmess version File versions

xsltransformation.cpp

/***************************************************************************
                          xsltransformation.cpp -  description
                             -------------------
    begin                : Sat Oct 8 2005
    copyright            : (C) 2005 by Diederik van der Boor
    email                : vdboor --at-- codingdomain.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

/*
 * The kopetexsl.cpp file from Kopete is used as working
 * demo/example to create this code, since it's more
 * to-the-point then the online libxml documentation.
 */

#include "xsltransformation.h"
#include "../kmessdebug.h"

#include <qfile.h>
#include <qcstring.h>
#include <qregexp.h>

#include <kurl.h>
#include <kglobal.h>
#include <klocale.h>
#include <kstddirs.h>

#include <libxml/globals.h>
#include <libxml/parser.h>

#include <libxslt/xsltconfig.h>
#include <libxslt/xsltInternals.h>
#include <libxslt/transform.h>  // After xsltconfig and xsltInternals or it breaks compiling on some libxslt versions.

// stdlib.h is required to build on Solaris
#include <stdlib.h>
#include <string.h>



// The constructor
XslTransformation::XslTransformation()
  : styleSheet_(0)
  , xslDoc_(0)
  , xslParams_(0)
{
  // Init libxml
  xmlLoadExtDtdDefaultValue = 0;
  xmlSubstituteEntitiesDefault( 1 );
}



// The destructor
XslTransformation::~XslTransformation()
{
  if(styleSheet_ != 0)
  {
    xsltFreeStylesheet(styleSheet_);
    // also frees xslDoc_
  }

  delete[] xslParams_;

  xsltCleanupGlobals();
  xmlCleanupParser();
}



// Convert an XML string
QString XslTransformation::convertXmlString(const QString &xml) const
{
  // Asserts
  if(KMESS_NULL(styleSheet_)) return QString::null;

  // Convert data to C-string
  QCString xmlCString = xml.utf8();

  // Create XmlDoc
  xmlDoc *sourceXmlDoc = xmlParseMemory( xmlCString.data(), xmlCString.length() );
  if(sourceXmlDoc == 0)
  {
    kdWarning() << "XslTransformation: could not parse the XML data." << endl;
    return QString::null;
  }

  // Apply XSL
  xmlDoc *resultXmlDoc = xsltApplyStylesheet( styleSheet_, sourceXmlDoc, xslParams_ );
  if(resultXmlDoc == 0)
  {
    kdWarning() << "XslTransformation: could not convert the XML data." << endl;
    // Free intermediate results. You gotta love C-API's.. ;-)
    xmlFree(sourceXmlDoc);
    return QString::null;
  }

  // Save
  xmlChar *xmlData     = 0;  // Filled by xmlDocDumpMemory()
  int      xmlDataSize = 0;
  xmlDocDumpMemory( resultXmlDoc, &xmlData, &xmlDataSize );
  QString resultXmlString = QString::fromUtf8( reinterpret_cast<char*>(xmlData), xmlDataSize );
  xmlFree( xmlData );        // Free data from xmlDocDumpMemory() again

  // Free XmlDoc 
  xmlFree(resultXmlDoc);
  xmlFree(sourceXmlDoc);

  // Strip xml header and last \n from result string
  resultXmlString = resultXmlString.replace( QRegExp("^<\\?xml [^?]+\\?>\n?"), QString::null )
                                   .replace( QRegExp("\r?\n?$"),               QString::null );

  // xsltApplyStylesheet returns an empty result when the parsing of user parameters failed.
  if( resultXmlString.isEmpty() )
  {
    return QString::null;
  }
  else
  {
    return resultXmlString;
  }
}



// Check whether a stylesheet is loaded successfully
bool XslTransformation::hasStylesheet() const
{
  return (styleSheet_ != 0);
}



// Set the XSL parameters
void XslTransformation::setParameters( const QMap<QString,QString> xslParameters )
{
  // Delete previous code.
  delete[] xslParams_;

  // Params
  if( xslParameters.isEmpty() )
  {
    xslParams_ = 0;
  }
  else
  {
    int argCount = xslParameters.count() * 2 + 1;
    xslParams_ = new const char*[ argCount ];

    int i = 0;
    for( QMapConstIterator<QString,QString> it = xslParameters.begin(); it != xslParameters.end(); ++it )
    {
      // Duplicate the strings as they will be deleted outside this scope.
      xslParams_[ i++ ] = strdup( it.key().utf8().data() );
      xslParams_[ i++ ] = strdup( QString( "'" + it.data() + "'" ).utf8().data() );
    }
    xslParams_[i++] = 0;

#ifdef KMESSTEST
    ASSERT( i == argCount );
#endif
  }
}



// Set the XSL stylesheet.
void XslTransformation::setStylesheet(const QString &xslFileName)
{
  // No expensive operations when file is identical.
  if(xslFileName == xslFileName_) return;
  xslFileName_ = xslFileName;

#ifdef KMESSDEBUG_XSLTRANSFORMATION
  kdDebug() << "XslTransformation: loading new style sheet '" << xslFileName << "'" << endl;
#endif

  // Clean up previous stylesheet
  if(styleSheet_ != 0)
  {
    xsltFreeStylesheet(styleSheet_);
    // also frees xslDoc_
    styleSheet_ = 0;
    xslDoc_     = 0;
  }

  // Get file path
  KURL fileUrl(xslFileName);
  QCString basePath = fileUrl.directory(false).utf8();

  // Open XSL file
  QFile file(xslFileName);
  if( ! file.open(IO_ReadOnly) )
  {
    kdWarning() << "XslTransformation: could not load '" << xslFileName << "'!" << endl;
    return;
  }

  // Read all data
  QByteArray fileData = file.readAll();
  file.close();

  // Parse file data as XML
  xslDoc_ = xmlParseMemory( fileData.data(), fileData.size() );
  if(xslDoc_ == 0)
  {
    kdWarning() << "XslTransformation: could not parse XML from '" << xslFileName << "'!" << endl;
    return;
  }

  // Documentation on the libxml data types (all have public fields):
  // http://xmlsoft.org/html/libxml-tree.html#xmlDoc
  // http://xmlsoft.org/html/libxml-tree.html#xmlNode
  // http://www.xmlsoft.org/examples/index.html

  // Useful commands:
  // xmlDocDump( stdout, xslDoc_ );
  // 

  xmlNode *xslRoot = xmlDocGetRootElement( xslDoc_ );

  // Set base path (e.g. xml:base attribute), required for xsl imports/includes
//  xmlNodeSetBase( xslRoot, reinterpret_cast<const xmlChar*>( basePath.data() ) );


  // Process structure, replace translated strings.
/*
  // Find the prefix the XML document uses for the "XSL namespace"
  xmlNs *xslNs = xmlSearchNsByHref( xslDoc_, xslRoot, (xmlChar*) "http://www.w3.org/1999/XSL/Transform" );
  if( xslNs == 0 )
  {
    kdWarning() << "XslTransformation::setStylesheet: Could not find XSL namespace in chat style." << endl;
  }
*/


  // Translate the <xsl:text> nodes in the XSL document.
  xmlNs *xslNs   = xmlSearchNsByHref( xslDoc_, xslRoot, (xmlChar*) "http://www.w3.org/1999/XSL/Transform" );
  xmlNs *kmessNs = xmlSearchNsByHref( xslDoc_, xslRoot, (xmlChar*) "http://www.kmess.org/2006/ChatStyles" );
  if( xslNs == 0 || kmessNs == 0 )
  {
    kdWarning() << "XslTransformation::setStylesheet: 'xsl' and 'kmess' namespaces not found in chat style, "
                << "chat style can't be translated." << endl;
  }
  else
  {
    updateXslTranslations( xslRoot, xslNs, kmessNs );
  }


  // Parse XML data as XSL
  styleSheet_ = xsltParseStylesheetDoc( xslDoc_ );
  if( styleSheet_ == 0 || styleSheet_->errors != 0 )
  {
    // Also happens when an <xsl:import> fails
    kdWarning() << "XslTransformation: could not parse XSL from '" << xslFileName << "'!" << endl;
    xmlFreeDoc( xslDoc_ );
    xslDoc_ = 0;
  }
#ifdef KMESSDEBUG_XSLTRANSFORMATION
  else
  {
    kdDebug() << "XslTransformation: style sheet loaded." << endl;
  }
#endif

}



// Update the translation strings in the XSL document.
void XslTransformation::updateXslTranslations( xmlNode *node, const xmlNs *xslNs, const xmlNs *kmessNs )
{
  // This method updates the string of all nodes which match:
  //  <xsl:text kmess:translate="true">fdsfsd</xsl:text>
  // the contents is trimmed to avoid translations are broken too easily.

  QRegExp trimmer("^([ \t\r\n\f\v]+)(.+)([ \t\r\n\f\v]+)$");

  // Browse all child nodes
  for( xmlNode *curNode = node; curNode != 0; curNode = curNode->next )
  {
    // Ignore leaf elements, even <xsl:text> elements have a text() childnode,
    if( curNode->children == 0 )
    {
      continue;
    }

    // Check node properties.
    if( curNode->type == XML_ELEMENT_NODE
    &&  curNode->ns   == xslNs
    &&  xmlStrEqual( curNode->name, (const xmlChar *) "text" ) )
    {
      // Found <xsl:text> node
      // Check if node has kmess:translate="true" property.
      xmlChar *translate  = xmlGetNsProp( curNode, (const xmlChar *) "translate", kmessNs->href );
      bool kmessTranslate = ( translate != 0 && xmlStrEqual( translate,  (const xmlChar *) "true" ) );
      if( translate != 0 )
      {
        xmlFree( translate );
      }

      // Ignore if node is not marked for translation.
      if( ! kmessTranslate )
      {
        continue;
      }

      // Get the content and convert to QString.
      xmlChar *rawContent = xmlNodeGetContent( curNode );
      QString content = QString::fromUtf8( (const char *) rawContent, xmlStrlen( rawContent ) );
      xmlFree( rawContent );

#ifdef KMESSDEBUG_XSLTRANSFORMATION
      QString oldContent = content;
#endif

      // Trim and simplyfy spaces to avoid breaking translations too easily.
      // Ignore <xsl:text> </xsl:text> nodes.
      bool timmed = trimmer.match( content ) != -1;
      content = content.simplifyWhiteSpace();
      if( content.isEmpty() )
      {
        continue;
      }

      // Translate the string.
      // Add the trimmed spaces back.
      content = i18n( "chat-style-text", content );
      if( timmed )
      {
        content = trimmer.cap(1) + content + trimmer.cap(3);
      }

#ifdef KMESSDEBUG_XSLTRANSFORMATION
      kdDebug() << "XslTransformation: translated <xsl:text> node \"" << oldContent << "\""
                << " to \"" << content << "\" using i18n(\"" << oldContent.simplifyWhiteSpace() << "\")." << endl;
#endif

      // Write the translated string back.
      QCString utf8Content = content.utf8();  // store to avoid cleanup before end of if-block.
      const xmlChar *newRawContent = (const xmlChar*) utf8Content.data();
      xmlNodeSetContent( curNode, newRawContent );
      continue;
    }

    // Recursion for sub elements.
    updateXslTranslations( curNode->children, xslNs, kmessNs );
  }
}


Generated by  Doxygen 1.6.0   Back to index