File indexing completed on 2024-05-12 05:10:15

0001 /***************************************************************************
0002     Copyright (C) 2003-2009 Robby Stephenson <robby@periapsis.org>
0003  ***************************************************************************/
0004 
0005 /***************************************************************************
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or         *
0008  *   modify it under the terms of the GNU General Public License as        *
0009  *   published by the Free Software Foundation; either version 2 of        *
0010  *   the License or (at your option) version 3 or any later version        *
0011  *   accepted by the membership of KDE e.V. (or its successor approved     *
0012  *   by the membership of KDE e.V.), which shall act as a proxy            *
0013  *   defined in Section 14 of version 3 of the license.                    *
0014  *                                                                         *
0015  *   This program is distributed in the hope that it will be useful,       *
0016  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0017  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0018  *   GNU General Public License for more details.                          *
0019  *                                                                         *
0020  *   You should have received a copy of the GNU General Public License     *
0021  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0022  *                                                                         *
0023  ***************************************************************************/
0024 
0025 #include "xslthandler.h"
0026 #include "../tellico_debug.h"
0027 #include "../utils/string_utils.h"
0028 
0029 #include <QUrl>
0030 
0031 #include <QDomDocument>
0032 #include <QTextCodec>
0033 #include <QVector>
0034 
0035 extern "C" {
0036 #include <libxslt/xslt.h>
0037 #include <libxslt/transform.h>
0038 #include <libxslt/xsltutils.h>
0039 #include <libxslt/extensions.h>
0040 
0041 #include <libexslt/exslt.h>
0042 }
0043 
0044 // I don't want any network I/O at all
0045 static const int xml_options = XML_PARSE_NONET | XML_PARSE_NOCDATA;
0046 static const int xslt_options = xml_options;
0047 
0048 /* some functions to pass to the XSLT libs */
0049 static int writeToQString(void* context, const char* buffer, int len) {
0050   QString* t = static_cast<QString*>(context);
0051   *t += QString::fromUtf8(buffer, len);
0052   return len;
0053 }
0054 
0055 static int closeQString(void* context) {
0056   QString* t = static_cast<QString*>(context);
0057   *t += QLatin1String("\n");
0058   return 0;
0059 }
0060 
0061 using Tellico::XSLTHandler;
0062 
0063 XSLTHandler::XMLOutputBuffer::XMLOutputBuffer() {
0064   m_buf = xmlOutputBufferCreateIO((xmlOutputWriteCallback)writeToQString,
0065                                   (xmlOutputCloseCallback)closeQString,
0066                                   &m_res, nullptr);
0067   if(m_buf) {
0068     m_buf->written = 0;
0069   } else {
0070     myWarning() << "error writing output buffer!";
0071   }
0072 }
0073 
0074 XSLTHandler::XMLOutputBuffer::~XMLOutputBuffer() {
0075   if(m_buf) {
0076     xmlOutputBufferClose(m_buf); //also flushes
0077     m_buf = nullptr;
0078   }
0079 }
0080 
0081 int XSLTHandler::s_initCount = 0;
0082 
0083 XSLTHandler::XSLTHandler(const QByteArray& xsltFile_) :
0084     m_stylesheet(nullptr) {
0085   init();
0086   QByteArray file = QUrl::toPercentEncoding(QString::fromLocal8Bit(xsltFile_));
0087   if(!file.isEmpty()) {
0088     xmlDocPtr xsltDoc = xmlReadFile(file.constData(), nullptr, xslt_options);
0089     m_stylesheet = xsltParseStylesheetDoc(xsltDoc);
0090     if(!m_stylesheet) {
0091       myDebug() << "null stylesheet pointer for " << xsltFile_;
0092     }
0093   } else {
0094     myDebug() << "XSLTHandler(QByteArray) - empty file name";
0095   }
0096 }
0097 
0098 XSLTHandler::XSLTHandler(const QUrl& xsltURL_) :
0099     m_stylesheet(nullptr) {
0100   init();
0101   if(xsltURL_.isValid() && xsltURL_.isLocalFile()) {
0102     xmlDocPtr xsltDoc = xmlReadFile(xsltURL_.toLocalFile().toUtf8().constData(), nullptr, xslt_options);
0103     m_stylesheet = xsltParseStylesheetDoc(xsltDoc);
0104     if(!m_stylesheet) {
0105       myDebug() << "null stylesheet pointer for " << xsltURL_.path();
0106     }
0107   } else {
0108     myDebug() << "XSLTHandler(QUrl) - invalid: " << xsltURL_;
0109   }
0110 }
0111 
0112 XSLTHandler::XSLTHandler(const QDomDocument& xsltDoc_, const QByteArray& xsltFile_, bool translate_) :
0113     m_stylesheet(nullptr) {
0114   init();
0115   QByteArray file = QUrl::toPercentEncoding(QString::fromLocal8Bit(xsltFile_));
0116   if(!xsltDoc_.isNull() && !file.isEmpty()) {
0117     setXSLTDoc(xsltDoc_, file, translate_);
0118   }
0119 }
0120 
0121 XSLTHandler::~XSLTHandler() {
0122   if(m_stylesheet) {
0123     xsltFreeStylesheet(m_stylesheet);
0124   }
0125 
0126   --s_initCount;
0127   if(s_initCount == 0) {
0128     xsltUnregisterExtModule(EXSLT_STRINGS_NAMESPACE);
0129     xsltUnregisterExtModule(EXSLT_DYNAMIC_NAMESPACE);
0130     xsltCleanupGlobals();
0131     xmlCleanupParser();
0132   }
0133 }
0134 
0135 void XSLTHandler::init() {
0136   if(s_initCount == 0) {
0137     xmlLoadExtDtdDefaultValue = 0;
0138 
0139     // register all exslt extensions
0140     exsltRegisterAll();
0141   }
0142   ++s_initCount;
0143 
0144   m_params.clear();
0145 }
0146 
0147 bool XSLTHandler::isValid() const {
0148   return (m_stylesheet != nullptr);
0149 }
0150 
0151 void XSLTHandler::setXSLTDoc(const QDomDocument& dom_, const QByteArray& xsltFile_, bool translate_) {
0152   bool utf8 = true; // XML defaults to utf-8
0153 
0154   // need to find out if utf-8 or not
0155   const QDomNodeList children = dom_.childNodes();
0156   for(int j = 0; j < children.count(); ++j) {
0157     if(children.item(j).isProcessingInstruction()) {
0158       QDomProcessingInstruction pi = children.item(j).toProcessingInstruction();
0159       if(pi.data().toLower().contains(QLatin1String("encoding"))) {
0160         if(!pi.data().toLower().contains(QLatin1String("utf-8"))) {
0161           utf8 = false;
0162 //        } else {
0163 //          myDebug() << "PI = " << pi.data();
0164         }
0165         break;
0166       }
0167     }
0168   }
0169 
0170   QString s;
0171   if(translate_) {
0172     s = Tellico::i18nReplace(dom_.toString(0 /* indent */));
0173   } else {
0174     s = dom_.toString();
0175   }
0176 
0177   xmlDocPtr xsltDoc;
0178   if(utf8) {
0179     xsltDoc = xmlReadDoc(reinterpret_cast<xmlChar*>(s.toUtf8().data()), xsltFile_.data(), nullptr, xslt_options);
0180   } else {
0181     xsltDoc = xmlReadDoc(reinterpret_cast<xmlChar*>(s.toLocal8Bit().data()), xsltFile_.data(), nullptr, xslt_options);
0182   }
0183 
0184   if(m_stylesheet) {
0185     xsltFreeStylesheet(m_stylesheet);
0186   }
0187   m_stylesheet = xsltParseStylesheetDoc(xsltDoc);
0188   if(!m_stylesheet) {
0189     myDebug() << "null stylesheet pointer for " << xsltFile_;
0190   }
0191 //  xmlFreeDoc(xsltDoc); // this causes a crash for some reason
0192 }
0193 
0194 void XSLTHandler::addStringParam(const QByteArray& name_, const QByteArray& value_) {
0195   QByteArray value = value_;
0196   if(value.contains('\'')) {
0197     if(value.contains('"')) {
0198       myWarning() << "String param contains both ' and \"" << value_;
0199       value.replace('"', "'");
0200     }
0201     addParam(name_, QByteArray("\"") + value + QByteArray("\""));
0202   } else {
0203     addParam(name_, QByteArray("'") + value + QByteArray("'"));
0204   }
0205 }
0206 
0207 void XSLTHandler::addParam(const QByteArray& name_, const QByteArray& value_) {
0208   m_params.insert(name_, value_);
0209 //  myDebug() << name_ << ":" << value_;
0210 }
0211 
0212 void XSLTHandler::removeParam(const QByteArray& name_) {
0213   m_params.remove(name_);
0214 }
0215 
0216 const QByteArray& XSLTHandler::param(const QByteArray& name_) {
0217   return m_params[name_];
0218 }
0219 
0220 QString XSLTHandler::applyStylesheet(const QString& text_) {
0221   if(!m_stylesheet) {
0222     myDebug() << "null stylesheet pointer!";
0223     return QString();
0224   }
0225   if(text_.isEmpty()) {
0226     myDebug() << "XSLTHandler::applyStylesheet() - empty input";
0227     return QString();
0228   }
0229 
0230   xmlDocPtr docIn;
0231   docIn = xmlReadDoc(reinterpret_cast<xmlChar*>(text_.toUtf8().data()), nullptr, nullptr, xml_options);
0232 
0233   return process(docIn);
0234 }
0235 
0236 QString XSLTHandler::process(xmlDocPtr docIn) {
0237   if(!docIn) {
0238     myDebug() << "XSLTHandler::applyStylesheet() - error parsing input string!";
0239     return QString();
0240   }
0241 
0242   QVector<const char*> params(2*m_params.count() + 1);
0243   params[0] = nullptr;
0244   QHash<QByteArray, QByteArray>::ConstIterator it = m_params.constBegin();
0245   QHash<QByteArray, QByteArray>::ConstIterator end = m_params.constEnd();
0246   for(int i = 0; it != end; ++it) {
0247     params[i  ] = qstrdup(it.key().constData());
0248     params[i+1] = qstrdup(it.value().constData());
0249     params[i+2] = nullptr;
0250     i += 2;
0251   }
0252   // returns NULL on error
0253   xmlDocPtr docOut;
0254   docOut = xsltApplyStylesheet(m_stylesheet, docIn, params.data());
0255   for(int i = 0; i < 2*m_params.count(); ++i) {
0256     delete[] params[i];
0257   }
0258 
0259   xmlFreeDoc(docIn);
0260   docIn = nullptr;
0261 
0262   if(!docOut) {
0263     myDebug() << "error applying stylesheet!";
0264     return QString();
0265   }
0266 
0267   XMLOutputBuffer output;
0268   if(output.isValid()) {
0269     int num_bytes = xsltSaveResultTo(output.buffer(), docOut, m_stylesheet);
0270     if(num_bytes == -1) {
0271       myDebug() << "error saving output buffer!";
0272     }
0273   }
0274 
0275   xmlFreeDoc(docOut);
0276   docOut = nullptr;
0277 
0278   return output.result();
0279 }
0280 
0281 //static
0282 QDomDocument& XSLTHandler::setLocaleEncoding(QDomDocument& dom_) {
0283   const QDomNodeList children = dom_.documentElement().childNodes();
0284   for(int j = 0; j < children.count(); ++j) {
0285     if(children.item(j).isElement() && children.item(j).nodeName() == QLatin1String("xsl:output")) {
0286       QDomElement e = children.item(j).toElement();
0287       const QString encoding = QLatin1String(QTextCodec::codecForLocale()->name());
0288       e.setAttribute(QStringLiteral("encoding"), encoding);
0289       break;
0290     }
0291   }
0292   return dom_;
0293 }