Logo Search packages:      
Sourcecode: kmess version File versions  Download package

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)
  , xslParamCount_(0)
{
  // Init libxml
  xmlLoadExtDtdDefaultValue = 0;
  xmlSubstituteEntitiesDefault( 1 );
}



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

  // Remove xsl params
  for( int i = 0; i < xslParamCount_; i++ )
  {
    delete xslParams_[i];
  }
  delete[] xslParams_;

  xsltCleanupGlobals();
  xmlCleanupParser();
}



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

  // Intermediate variables
  QString  result;
  xmlDoc  *resultXmlDoc = 0;
  xmlDoc  *sourceXmlDoc = 0;
  xmlChar *xmlData      = 0;
  int      xmlDataSize  = 0;

  // Convert data to C-string
  // It needs to be stored in a local variable
  // before it can be passed to a function.
  QCString xmlCString = xml.utf8();

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

  // Apply XSL
  resultXmlDoc = xsltApplyStylesheet( styleSheet_, sourceXmlDoc, xslParams_ );
  if( resultXmlDoc == 0 )
  {
    kdWarning() << "XslTransformation: could not convert the XML data." << endl;
    goto cleanSource;
  }

  // Save
  // Fills xmlData with the results.
  xmlDocDumpMemory( resultXmlDoc, &xmlData, &xmlDataSize );

  // Load result in a QString, strip xml header and last \n from result string
  result = QString::fromUtf8( reinterpret_cast<char*>( xmlData ), xmlDataSize );
  result = result.replace( QRegExp("^<\\?xml [^?]+\\?>\n?"), QString::null )
                 .replace( QRegExp("\r?\n?$"),               QString::null );

  // Reverse calls to free intermediate results too.
  // You gotta love C-API's.. ;-)

//cleanAll:
  xmlFree( xmlData );
  xmlFreeDoc( resultXmlDoc );

cleanSource:
  xmlFreeDoc( sourceXmlDoc );

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



// 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.
  for( int i = 0; i < xslParamCount_; i++ )
  {
    delete xslParams_[i];
  }
  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.
      QCString key   = it.key().utf8();
      QCString value = QString( "'" + it.data() + "'" ).utf8();
      xslParams_[ i++ ] = strdup( key.data() );
      xslParams_[ i++ ] = strdup( value.data() );
    }
    xslParamCount_ = i;
    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/xmlns/ChatStyles/v1/" );
  if( xslNs == 0 || kmessNs == 0 )
  {
    kdWarning() << "XslTransformation::setStylesheet: 'xsl' and 'kmess' namespaces not found in '"
                << xslFileName << "' 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