diff --git a/src/docbookl10nhelper.cpp b/src/docbookl10nhelper.cpp index c1245b5..a271579 100644 --- a/src/docbookl10nhelper.cpp +++ b/src/docbookl10nhelper.cpp @@ -1,253 +1,256 @@ /* This file is part of the KDE project Copyright (C) 2010 Luigi Toscano This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "loggingcategory.h" #include #include #include #include #include #include -#include +#include #include #include class LangListType: public QList > { public: int searchLang(const QString &el) { for (int i = 0; i < size(); ++i) { if (at(i).first == el) { return i; } } return -1; } }; int writeLangFile(const QString &fname, const QString &dtdPath, const LangListType &langMap) { QFile outFile(fname); if (! outFile.open(QIODevice::WriteOnly)) { qCCritical(KDocToolsLog) << QStringLiteral("Could not write %1") .arg(outFile.fileName()); return (1); } QTextStream outStream(&outFile); outStream << "" << endl; outStream << QStringLiteral("") .arg((*i).first).arg((*i).second) << endl; ++i; } outStream << "]>" << endl; if (!langMap.isEmpty()) { outStream << "" << endl; i = langMap.constBegin(); while (i != langMap.constEnd()) { outStream << QStringLiteral("&%1;") .arg((*i).first) << endl; ++i; } outStream << "" << endl; } outFile.close(); return (0); } int writeLangFileNew(const QString &fname, const QString &dtdPath, const LangListType &langMap) { QFile outFile(fname); if (! outFile.open(QIODevice::WriteOnly)) { qCCritical(KDocToolsLog) << QStringLiteral("Could not write %1") .arg(outFile.fileName()); return (1); } QTextStream outStream(&outFile); outStream << "" << endl; outStream << QStringLiteral("") .arg(dtdPath) << endl; if (!langMap.isEmpty()) { outStream << "" << endl; LangListType::const_iterator i = langMap.constBegin(); while (i != langMap.constEnd()) { outStream << QStringLiteral("") .arg((*i).first).arg((*i).second) << endl; ++i; } outStream << "" << endl; } outFile.close(); return (0); } inline const QString addTrailingSlash(const QString &p) { return p.endsWith(QStringLiteral("/")) ? p : p + QStringLiteral("/"); } int main(int argc, char **argv) { QCoreApplication app(argc, argv); const QStringList arguments = app.arguments(); if (arguments.count() != 4) { qCCritical(KDocToolsLog) << "wrong argument count"; return (1); } const QString l10nDir = addTrailingSlash(arguments[1]); const QString l10nCustomDir = addTrailingSlash(arguments[2]); const QString destDir = addTrailingSlash(arguments[3]); QFile i18nFile(l10nDir + QStringLiteral("common/l10n.xml")); if (! i18nFile.open(QIODevice::ReadOnly)) { qCCritical(KDocToolsLog) << i18nFile.fileName() << " not found"; return (1); } const QString all10nFName = destDir + QStringLiteral("all-l10n.xml"); const QString customl10nFName = destDir + QStringLiteral("kde-custom-l10n.xml"); /* * for each language defined in the original l10n.xml, copy * it into all-l10n.xml and store it in a list; **/ - QRegExp rxEntity, rxEntity2, rxDocType, rxDocType2; - rxDocType.setPattern(QStringLiteral("^\\s*$")); - rxEntity.setPattern(QStringLiteral("^\\s*\\s*$")); - rxEntity2.setPattern(QStringLiteral("^\\s*\\s*$")); + const QRegularExpression rxDocType(QStringLiteral("^\\s*$")); + const QRegularExpression rxEntity(QStringLiteral("^\\s*\\s*$")); + const QRegularExpression rxEntity2(QStringLiteral("^\\s*\\s*$")); QTextStream inStream(&i18nFile); int parsingState = 0; LangListType allLangs, customLangs; bool foundRxEntity = false; bool foundRxEntity2 = false; while (! inStream.atEnd()) { QString line = inStream.readLine(); switch (parsingState) { case 0: - if (rxDocType.indexIn(line) != -1) { + if (rxDocType.match(line).hasMatch()) { parsingState = 1; //qCDebug(KDocToolsLog) << "DTD found"; - } else if (rxDocType2.indexIn(line) != -1) { + } else if (rxDocType2.match(line).hasMatch()) { parsingState = 1; //qCDebug(KDocToolsLog) << "DTD found"; } break; case 1: QString langCode, langFile; - if (rxEntity.indexIn(line) != -1 && !foundRxEntity2) { + QRegularExpressionMatch match = rxEntity.match(line); + if (match.hasMatch() && !foundRxEntity2) { foundRxEntity = true; - langCode = rxEntity.cap(1); - langFile = l10nDir + QStringLiteral("common/") + rxEntity.cap(2); - allLangs += qMakePair(langCode, langFile); - //qCDebug(KDocToolsLog) << langCode << " - " << langFile; - } else if (rxEntity2.indexIn(line) != -1 && !foundRxEntity) { - foundRxEntity2 = true; - langCode = rxEntity2.cap(1); - langFile = l10nDir + QStringLiteral("common/") + rxEntity2.cap(2); + langCode = match.captured(1); + langFile = l10nDir + QStringLiteral("common/") + match.captured(2); allLangs += qMakePair(langCode, langFile); //qCDebug(KDocToolsLog) << langCode << " - " << langFile; + } else if (!foundRxEntity) { + match = rxEntity2.match(line); + if (match.hasMatch()) { + foundRxEntity2 = true; + langCode = match.captured(1); + langFile = l10nDir + QStringLiteral("common/") + match.captured(2); + allLangs += qMakePair(langCode, langFile); + //qCDebug(KDocToolsLog) << langCode << " - " << langFile; + } } break; } } i18nFile.close(); /* read the list of locally-available custom languages */ QDir outDir(l10nCustomDir); QStringList dirFileFilters; dirFileFilters << QStringLiteral("*.xml"); QStringList customLangFiles = outDir.entryList(dirFileFilters, QDir::Files, QDir::Name); /* the following two calls to removeOne should not be needed, as * the customization directory from the sources should not contain * those files */ customLangFiles.removeOne(QStringLiteral("all-l10n.xml")); customLangFiles.removeOne(QStringLiteral("kde-custom-l10n.xml")); //qCDebug(KDocToolsLog) << "customLangFiles:" << customLangFiles; /* * for each custom language (from directory listing), if it is not * in the list of upstream languages, add it to all-l10n.xml, * otherwise add it to kde-custom-l10n.xml */ QStringList::const_iterator i = customLangFiles.constBegin(); while (i != customLangFiles.constEnd()) { QString langFile = (*i); /* remove trailing .xml */ QString langCode = langFile.left(langFile.length() - 4); QPair cl = qMakePair(langCode, langFile); if ((allLangs.searchLang(langCode)) > 0) { /* custom language found in upstream list */ customLangs += cl; } else { /* custom language not found in upstream list */ allLangs += cl; } ++i; } int res = 0; if (foundRxEntity) { /* old style (docbook-xsl<=1.75) */ res = writeLangFile(all10nFName, l10nDir + QStringLiteral("common/l10n.dtd"), allLangs); } else { res = writeLangFileNew(all10nFName, l10nDir + QStringLiteral("common/l10n.dtd"), allLangs); } return (res); } diff --git a/src/xslt.cpp b/src/xslt.cpp index 42756e1..98d7aaf 100644 --- a/src/xslt.cpp +++ b/src/xslt.cpp @@ -1,495 +1,493 @@ #include "docbookxslt.h" #include "docbookxslt_p.h" #ifdef Q_OS_WIN //one of the xslt/xml headers pulls in windows.h and breaks #define NOMINMAX #include #endif #include "../config-kdoctools.h" #include "loggingcategory.h" #include #include #include #include #include #include #include #include #include #include -#include #include #include #include #include #include #include #if !defined( SIMPLE_XSLT ) extern HelpProtocol *slave; #define INFO( x ) if (slave) slave->infoMessage(x); #else #define INFO( x ) #endif int writeToQString(void *context, const char *buffer, int len) { QString *t = (QString *)context; *t += QString::fromUtf8(buffer, len); return len; } #if defined (SIMPLE_XSLT) && defined(Q_OS_WIN) #define MAX_PATHS 64 xmlExternalEntityLoader defaultEntityLoader = NULL; static xmlChar *paths[MAX_PATHS + 1]; static int nbpaths = 0; static QHash replaceURLList; /* * Entity loading control and customization. * taken from xsltproc.c */ static xmlParserInputPtr xsltprocExternalEntityLoader(const char *_URL, const char *ID, xmlParserCtxtPtr ctxt) { xmlParserInputPtr ret; warningSAXFunc warning = NULL; // use local available dtd versions instead of fetching it every time from the internet QString url = QLatin1String(_URL); QHash::const_iterator i; for (i = replaceURLList.constBegin(); i != replaceURLList.constEnd(); i++) { if (url.startsWith(i.key())) { url.replace(i.key(), i.value()); qCDebug(KDocToolsLog) << "converted" << _URL << "to" << url; } } char URL[1024]; strcpy(URL, url.toLatin1().constData()); const char *lastsegment = URL; const char *iter = URL; if (nbpaths > 0) { while (*iter != 0) { if (*iter == '/') { lastsegment = iter + 1; } iter++; } } if ((ctxt != NULL) && (ctxt->sax != NULL)) { warning = ctxt->sax->warning; ctxt->sax->warning = NULL; } if (defaultEntityLoader != NULL) { ret = defaultEntityLoader(URL, ID, ctxt); if (ret != NULL) { if (warning != NULL) { ctxt->sax->warning = warning; } qCDebug(KDocToolsLog) << "Loaded URL=\"" << URL << "\" ID=\"" << ID << "\""; return (ret); } } for (int i = 0; i < nbpaths; i++) { xmlChar *newURL; newURL = xmlStrdup((const xmlChar *) paths[i]); newURL = xmlStrcat(newURL, (const xmlChar *) "/"); newURL = xmlStrcat(newURL, (const xmlChar *) lastsegment); if (newURL != NULL) { ret = defaultEntityLoader((const char *)newURL, ID, ctxt); if (ret != NULL) { if (warning != NULL) { ctxt->sax->warning = warning; } qCDebug(KDocToolsLog) << "Loaded URL=\"" << newURL << "\" ID=\"" << ID << "\""; xmlFree(newURL); return (ret); } xmlFree(newURL); } } if (warning != NULL) { ctxt->sax->warning = warning; if (URL != NULL) { warning(ctxt, "failed to load external entity \"%s\"\n", URL); } else if (ID != NULL) { warning(ctxt, "failed to load external entity \"%s\"\n", ID); } } return (NULL); } #endif QString KDocTools::transform(const QString &pat, const QString &tss, const QVector ¶ms) { QString parsed; INFO(i18n("Parsing stylesheet")); #if defined (SIMPLE_XSLT) && defined(Q_OS_WIN) // prepare use of local available dtd versions instead of fetching every time from the internet // this approach is url based if (!defaultEntityLoader) { defaultEntityLoader = xmlGetExternalEntityLoader(); xmlSetExternalEntityLoader(xsltprocExternalEntityLoader); replaceURLList[QLatin1String("http://www.oasis-open.org/docbook/xml/4.5")] = QString("file:///%1").arg(DOCBOOK_XML_CURRDTD); } #endif xsltStylesheetPtr style_sheet = xsltParseStylesheetFile((const xmlChar *)QFile::encodeName(tss).constData()); if (!style_sheet) { return parsed; } if (style_sheet->indent == 1) { xmlIndentTreeOutput = 1; } else { xmlIndentTreeOutput = 0; } INFO(i18n("Parsing document")); xmlParserCtxtPtr pctxt; pctxt = xmlNewParserCtxt(); if ( pctxt == nullptr ) { return parsed; } xmlDocPtr doc = xmlCtxtReadFile(pctxt, QFile::encodeName(pat).constData(), nullptr, XML_PARSE_NOENT|XML_PARSE_DTDLOAD|XML_PARSE_NONET); /* Clean the context pointer, now useless */ const bool context_valid = (pctxt->valid == 0); xmlFreeParserCtxt(pctxt); /* Check both the returned doc (for parsing errors) and the context (for validation errors) */ if (doc == nullptr) { return parsed; } else { if (context_valid) { xmlFreeDoc(doc); return parsed; } } INFO(i18n("Applying stylesheet")); QVector p = params; p.append(nullptr); xmlDocPtr res = xsltApplyStylesheet(style_sheet, doc, const_cast(&p[0])); xmlFreeDoc(doc); if (res != nullptr) { xmlOutputBufferPtr outp = xmlOutputBufferCreateIO(writeToQString, nullptr, &parsed, nullptr); outp->written = 0; INFO(i18n("Writing document")); xsltSaveResultTo(outp, res, style_sheet); xmlOutputBufferClose(outp); xmlFreeDoc(res); } xsltFreeStylesheet(style_sheet); if (parsed.isEmpty()) { parsed = QLatin1Char(' '); // avoid error message } return parsed; } /* xmlParserInputPtr meinExternalEntityLoader(const char *URL, const char *ID, xmlParserCtxtPtr ctxt) { xmlParserInputPtr ret = NULL; // fprintf(stderr, "loading %s %s %s\n", URL, ID, ctxt->directory); if (URL == NULL) { if ((ctxt->sax != NULL) && (ctxt->sax->warning != NULL)) ctxt->sax->warning(ctxt, "failed to load external entity \"%s\"\n", ID); return(NULL); } if (!qstrcmp(ID, "-//OASIS//DTD DocBook XML V4.1.2//EN")) URL = "docbook/xml-dtd-4.1.2/docbookx.dtd"; if (!qstrcmp(ID, "-//OASIS//DTD XML DocBook V4.1.2//EN")) URL = "docbook/xml-dtd-4.1.2/docbookx.dtd"; QString file; if (QFile::exists( QDir::currentPath() + "/" + URL ) ) file = QDir::currentPath() + "/" + URL; else file = locate("dtd", URL); ret = xmlNewInputFromFile(ctxt, file.toLatin1().constData()); if (ret == NULL) { if ((ctxt->sax != NULL) && (ctxt->sax->warning != NULL)) ctxt->sax->warning(ctxt, "failed to load external entity \"%s\"\n", URL); } return(ret); } */ QString splitOut(const QString &parsed, int index) { int start_index = index + 1; while (parsed.at(start_index - 1) != QLatin1Char('>')) { start_index++; } int inside = 0; QString filedata; while (true) { int endindex = parsed.indexOf(QStringLiteral(""), index); int startindex = parsed.indexOf(QStringLiteral(" 0) { if (startindex < endindex) { //qCDebug(KDocToolsLog) << "finding another"; index = startindex + 8; inside++; } else { index = endindex + 8; inside--; } } else { inside--; index = endindex + 1; } if (inside == 0) { filedata = parsed.mid(start_index, endindex - start_index); break; } } index = filedata.indexOf(QStringLiteral(" 0) { int endindex = filedata.lastIndexOf(QStringLiteral("")); while (filedata.at(endindex) != QLatin1Char('>')) { endindex++; } endindex++; filedata = filedata.left(index) + filedata.mid(endindex); } - // filedata.replace(QRegExp(">"), "\n>"); return filedata; } QByteArray fromUnicode(const QString &data) { #ifdef Q_OS_WIN return data.toUtf8(); #else QTextCodec *locale = QTextCodec::codecForLocale(); QByteArray result; char buffer[30000]; uint buffer_len = 0; uint len = 0; int offset = 0; const int part_len = 5000; QString part; while (offset < data.length()) { part = data.mid(offset, part_len); QByteArray test = locale->fromUnicode(part); if (locale->toUnicode(test) == part) { result += test; offset += part_len; continue; } len = part.length(); buffer_len = 0; for (uint i = 0; i < len; i++) { QByteArray test = locale->fromUnicode(part.mid(i, 1)); if (locale->toUnicode(test) == part.mid(i, 1)) { if (buffer_len + test.length() + 1 > sizeof(buffer)) { break; } strcpy(buffer + buffer_len, test.data()); buffer_len += test.length(); } else { QString res = QStringLiteral("&#%1;").arg(part.at(i).unicode()); test = locale->fromUnicode(res); if (buffer_len + test.length() + 1 > sizeof(buffer)) { break; } strcpy(buffer + buffer_len, test.data()); buffer_len += test.length(); } } result += QByteArray(buffer, buffer_len + 1); offset += part_len; } return result; #endif } void replaceCharsetHeader(QString &output) { QString name; #ifdef Q_OS_WIN name = "utf-8"; // may be required for all xml output if (output.contains("")) output.replace(QString(""), QString("").arg(name)); #else name = QLatin1String(QTextCodec::codecForLocale()->name()); name.replace(QStringLiteral("ISO "), QStringLiteral("iso-")); output.replace(QStringLiteral(""), QStringLiteral("").arg(name)); #endif } QByteArray KDocTools::extractFileToBuffer(const QString &content, const QString &filename) { int index = content.indexOf(QStringLiteral("srcdir = srcdir; } //qCDebug(KDocToolsLog) << "XML_CATALOG_FILES: " << catalogs; qputenv("XML_CATALOG_FILES", catalogs); xmlInitializeCatalog(); #if defined(_MSC_VER) /* Workaround: apparently setting XML_CATALOG_FILES set here has no effect on the libxml2 functions. This code path could be used in all cases instead of setting the variable, but this requires more investigation on the reason of the issue. */ xmlLoadCatalogs(catalogs.constData()); #endif } QString KDocTools::locateFileInDtdResource(const QString &file, const QStandardPaths::LocateOptions option) { const QStringList lst = locateFilesInDtdResource(file, option); return lst.isEmpty() ? QString() : lst.first(); } QStringList locateFilesInDtdResource(const QString &file, const QStandardPaths::LocateOptions option) { QFileInfo info(file); if (info.exists() && info.isAbsolute()) { return QStringList() << file; } const QString srcdir = s_dtdDirs()->srcdir; if (!srcdir.isEmpty()) { const QString test = srcdir + QLatin1Char('/') + file; if (QFile::exists(test)) { return QStringList() << test; } qCDebug(KDocToolsLog) << "Could not locate file" << file << "in" << srcdir; return QStringList(); } // Using locateAll() is necessary to be able to find all catalogs when // running in environments where every repository is installed in its own // prefix. // This is the case on build.kde.org where kdelibs4support installs catalogs // in a different prefix than kdoctools. const QString fileName = QStringLiteral("kf5/kdoctools/") + file; QStringList result = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, fileName, option); // fallback to stuff installed with KDocTools const QFileInfo fileInInstallDataDir(QStringLiteral(KDOCTOOLS_INSTALL_DATADIR_KF5) + QStringLiteral("/kdoctools/") + file); if (fileInInstallDataDir.exists()) { if ((option == QStandardPaths::LocateFile) && fileInInstallDataDir.isFile()) { result.append(fileInInstallDataDir.absoluteFilePath()); } if ((option == QStandardPaths::LocateDirectory) && fileInInstallDataDir.isDir()) { result.append(fileInInstallDataDir.absoluteFilePath()); } } if (result.isEmpty()) { qCDebug(KDocToolsLog) << "Could not locate file" << fileName << "in" << QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); } return result; } QStringList getKDocToolsCatalogs() { // Find all catalogs as catalog*.xml, and add them to the list, starting // from catalog.xml (the main one). const QStringList dirNames = locateFilesInDtdResource(QStringLiteral("customization"), QStandardPaths::LocateDirectory); if (dirNames.isEmpty()) { return QStringList(); } QStringList catalogFiles; for (const QString &customizationDirName : dirNames) { QDir customizationDir = QDir(customizationDirName); const QStringList catalogFileFilters(QStringLiteral("catalog*.xml")); const QFileInfoList catalogInfoFiles = customizationDir.entryInfoList(catalogFileFilters, QDir::Files, QDir::Name); for (const QFileInfo &fileInfo : catalogInfoFiles) { const QString fullFileName = QUrl::fromLocalFile(fileInfo.absoluteFilePath()).toEncoded(); if (fileInfo.fileName() == QStringLiteral("catalog.xml")) { catalogFiles.prepend(fullFileName); } else { catalogFiles.append(fullFileName); } } } QStringList catalogs; for (const QString &aCatalog : qAsConst(catalogFiles)) { catalogs << aCatalog; } //qCDebug(KDocToolsLog) << "Found catalogs: " << catalogs; return catalogs; } QStringList KDocTools::documentationDirs() { /* List of paths containing documentation */ return QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("doc/HTML"), QStandardPaths::LocateDirectory); }