File indexing completed on 2024-05-12 16:46:36

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     xmlSubstituteEntitiesDefault(1);
0138     xmlLoadExtDtdDefaultValue = 0;
0139 
0140     // register all exslt extensions
0141     exsltRegisterAll();
0142   }
0143   ++s_initCount;
0144 
0145   m_params.clear();
0146 }
0147 
0148 bool XSLTHandler::isValid() const {
0149   return (m_stylesheet != nullptr);
0150 }
0151 
0152 void XSLTHandler::setXSLTDoc(const QDomDocument& dom_, const QByteArray& xsltFile_, bool translate_) {
0153   bool utf8 = true; // XML defaults to utf-8
0154 
0155   // need to find out if utf-8 or not
0156   const QDomNodeList children = dom_.childNodes();
0157   for(int j = 0; j < children.count(); ++j) {
0158     if(children.item(j).isProcessingInstruction()) {
0159       QDomProcessingInstruction pi = children.item(j).toProcessingInstruction();
0160       if(pi.data().toLower().contains(QLatin1String("encoding"))) {
0161         if(!pi.data().toLower().contains(QLatin1String("utf-8"))) {
0162           utf8 = false;
0163 //        } else {
0164 //          myDebug() << "PI = " << pi.data();
0165         }
0166         break;
0167       }
0168     }
0169   }
0170 
0171   QString s;
0172   if(translate_) {
0173     s = Tellico::i18nReplace(dom_.toString(0 /* indent */));
0174   } else {
0175     s = dom_.toString();
0176   }
0177 
0178   xmlDocPtr xsltDoc;
0179   if(utf8) {
0180     xsltDoc = xmlReadDoc(reinterpret_cast<xmlChar*>(s.toUtf8().data()), xsltFile_.data(), nullptr, xslt_options);
0181   } else {
0182     xsltDoc = xmlReadDoc(reinterpret_cast<xmlChar*>(s.toLocal8Bit().data()), xsltFile_.data(), nullptr, xslt_options);
0183   }
0184 
0185   if(m_stylesheet) {
0186     xsltFreeStylesheet(m_stylesheet);
0187   }
0188   m_stylesheet = xsltParseStylesheetDoc(xsltDoc);
0189   if(!m_stylesheet) {
0190     myDebug() << "null stylesheet pointer for " << xsltFile_;
0191   }
0192 //  xmlFreeDoc(xsltDoc); // this causes a crash for some reason
0193 }
0194 
0195 void XSLTHandler::addStringParam(const QByteArray& name_, const QByteArray& value_) {
0196   QByteArray value = value_;
0197   if(value.contains('\'')) {
0198     if(value.contains('"')) {
0199       myWarning() << "String param contains both ' and \"" << value_;
0200       value.replace('"', "'");
0201     }
0202     addParam(name_, QByteArray("\"") + value + QByteArray("\""));
0203   } else {
0204     addParam(name_, QByteArray("'") + value + QByteArray("'"));
0205   }
0206 }
0207 
0208 void XSLTHandler::addParam(const QByteArray& name_, const QByteArray& value_) {
0209   m_params.insert(name_, value_);
0210 //  myDebug() << name_ << ":" << value_;
0211 }
0212 
0213 void XSLTHandler::removeParam(const QByteArray& name_) {
0214   m_params.remove(name_);
0215 }
0216 
0217 const QByteArray& XSLTHandler::param(const QByteArray& name_) {
0218   return m_params[name_];
0219 }
0220 
0221 QString XSLTHandler::applyStylesheet(const QString& text_) {
0222   if(!m_stylesheet) {
0223     myDebug() << "null stylesheet pointer!";
0224     return QString();
0225   }
0226   if(text_.isEmpty()) {
0227     myDebug() << "XSLTHandler::applyStylesheet() - empty input";
0228     return QString();
0229   }
0230 
0231   xmlDocPtr docIn;
0232   docIn = xmlReadDoc(reinterpret_cast<xmlChar*>(text_.toUtf8().data()), nullptr, nullptr, xml_options);
0233 
0234   return process(docIn);
0235 }
0236 
0237 QString XSLTHandler::process(xmlDocPtr docIn) {
0238   if(!docIn) {
0239     myDebug() << "XSLTHandler::applyStylesheet() - error parsing input string!";
0240     return QString();
0241   }
0242 
0243   QVector<const char*> params(2*m_params.count() + 1);
0244   params[0] = nullptr;
0245   QHash<QByteArray, QByteArray>::ConstIterator it = m_params.constBegin();
0246   QHash<QByteArray, QByteArray>::ConstIterator end = m_params.constEnd();
0247   for(int i = 0; it != end; ++it) {
0248     params[i  ] = qstrdup(it.key().constData());
0249     params[i+1] = qstrdup(it.value().constData());
0250     params[i+2] = nullptr;
0251     i += 2;
0252   }
0253   // returns NULL on error
0254   xmlDocPtr docOut;
0255   docOut = xsltApplyStylesheet(m_stylesheet, docIn, params.data());
0256   for(int i = 0; i < 2*m_params.count(); ++i) {
0257     delete[] params[i];
0258   }
0259 
0260   xmlFreeDoc(docIn);
0261   docIn = nullptr;
0262 
0263   if(!docOut) {
0264     myDebug() << "error applying stylesheet!";
0265     return QString();
0266   }
0267 
0268   XMLOutputBuffer output;
0269   if(output.isValid()) {
0270     int num_bytes = xsltSaveResultTo(output.buffer(), docOut, m_stylesheet);
0271     if(num_bytes == -1) {
0272       myDebug() << "error saving output buffer!";
0273     }
0274   }
0275 
0276   xmlFreeDoc(docOut);
0277   docOut = nullptr;
0278 
0279   return output.result();
0280 }
0281 
0282 //static
0283 QDomDocument& XSLTHandler::setLocaleEncoding(QDomDocument& dom_) {
0284   const QDomNodeList children = dom_.documentElement().childNodes();
0285   for(int j = 0; j < children.count(); ++j) {
0286     if(children.item(j).isElement() && children.item(j).nodeName() == QLatin1String("xsl:output")) {
0287       QDomElement e = children.item(j).toElement();
0288       const QString encoding = QLatin1String(QTextCodec::codecForLocale()->name());
0289       e.setAttribute(QStringLiteral("encoding"), encoding);
0290       break;
0291     }
0292   }
0293   return dom_;
0294 }