diff --git a/src/meinproc.cpp b/src/meinproc.cpp index 01a7e6c..a882b75 100644 --- a/src/meinproc.cpp +++ b/src/meinproc.cpp @@ -1,231 +1,231 @@ #include "../config-kdoctools.h" #include "xslt.h" #include "meinproc_common.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef _WIN32 extern "C" int xmlLoadExtDtdDefaultValue; #endif class MyPair { public: QString word; int base; }; typedef QList PairList; #define DIE(x) do { qCritical() << "Error:" << x; exit(1); } while (0) void parseEntry(PairList &list, xmlNodePtr cur, int base) { if (!cur) { return; } base += atoi((const char *)xmlGetProp(cur, (const xmlChar *)"header")); if (base > 10) { // 10 is the maximum base = 10; } /* We don't care what the top level element name is */ cur = cur->xmlChildrenNode; - while (cur != NULL) { + while (cur != nullptr) { if (cur->type == XML_TEXT_NODE) { QString words = QString::fromUtf8((char *)cur->content); const QStringList wlist = words.simplified().split(QLatin1Char(' '), QString::SkipEmptyParts); for (QStringList::ConstIterator it = wlist.begin(); it != wlist.end(); ++it) { MyPair m; m.word = *it; m.base = base; list.append(m); } } else if (!xmlStrcmp(cur->name, (const xmlChar *) "entry")) { parseEntry(list, cur, base); } cur = cur->next; } } int main(int argc, char **argv) { // xsltSetGenericDebugFunc(stderr, NULL); /*options.add("stylesheet ", ki18n("Stylesheet to use")); options.add("stdout", ki18n("Output whole document to stdout")); options.add("o"); options.add("output ", ki18n("Output whole document to file")); options.add("htdig", ki18n("Create a ht://dig compatible index")); options.add("check", ki18n("Check the document for validity")); options.add("cache ", ki18n("Create a cache file for the document")); options.add("srcdir ", ki18n("Set the srcdir, for kdelibs")); options.add("param =", ki18n("Parameters to pass to the stylesheet")); options.add("+xml", ki18n("The file to transform"));*/ QCoreApplication app(argc, argv); app.setApplicationName(QStringLiteral("meinproc")); app.setApplicationVersion(QStringLiteral("5.0")); QCommandLineParser parser; parser.setApplicationDescription(QCoreApplication::translate("main", "KDE Translator for XML")); parser.addHelpOption(); parser.addVersionOption(); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("stylesheet"), QCoreApplication::translate("main", "Stylesheet to use"), QStringLiteral("xsl"))); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("stdout"), QCoreApplication::translate("main", "Output whole document to stdout"))); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("o") << QStringLiteral("output"), QCoreApplication::translate("main", "Output whole document to file"), QStringLiteral("file"))); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("check"), QCoreApplication::translate("main", "Check the document for validity"))); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("cache"), QCoreApplication::translate("main", "Create a cache file for the document"), QStringLiteral("file"))); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("srcdir"), QCoreApplication::translate("main", "Set the srcdir, for kdoctools"), QStringLiteral("dir"))); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("param"), QCoreApplication::translate("main", "Parameters to pass to the stylesheet"), QStringLiteral("key=value"))); parser.addPositionalArgument(QStringLiteral("xml"), QCoreApplication::translate("main", "The file to transform")); parser.process(app); if (parser.positionalArguments().count() != 1) { parser.showHelp(); return (1); } exsltRegisterAll(); // Need to set SRCDIR before calling setupStandardDirs QString srcdir; if (parser.isSet(QStringLiteral("srcdir"))) { srcdir = QDir(parser.value(QStringLiteral("srcdir"))).absolutePath(); } setupStandardDirs(srcdir); LIBXML_TEST_VERSION const QString checkFilename = parser.positionalArguments().first(); CheckFileResult ckr = checkFile(checkFilename); switch (ckr) { case CheckFileSuccess: break; case CheckFileDoesNotExist: DIE("File" << checkFilename << "does not exist."); case CheckFileIsNotFile: DIE(checkFilename << "is not a file."); case CheckFileIsNotReadable: DIE("File" << checkFilename << "is not readable."); } if (parser.isSet(QStringLiteral("check"))) { QStringList lst = getKDocToolsCatalogs(); if (lst.isEmpty()) { DIE("Could not find kdoctools catalogs"); } QByteArray catalogs = lst.join(" ").toLocal8Bit(); QString exe; #if defined( XMLLINT ) exe = QStringLiteral(XMLLINT); #endif if (!QFileInfo(exe).isExecutable()) { exe = QStandardPaths::findExecutable(QStringLiteral("xmllint")); } CheckResult cr = check(checkFilename, exe, catalogs); switch (cr) { case CheckSuccess: break; case CheckNoXmllint: DIE("Could not find xmllint"); case CheckNoOut: DIE("`xmllint --noout` outputted text"); } } QVector params; { const QStringList paramList = parser.values(QStringLiteral("param")); QStringList::ConstIterator it = paramList.constBegin(); QStringList::ConstIterator end = paramList.constEnd(); for (; it != end; ++it) { const QString tuple = *it; const int ch = tuple.indexOf(QLatin1Char('=')); if (ch == -1) { DIE("Key-Value tuple" << tuple << "lacks a '='!"); } params.append(qstrdup(tuple.left(ch).toUtf8().constData())); params.append(qstrdup(tuple.mid(ch + 1).toUtf8().constData())); } } - params.append(NULL); + params.append(nullptr); QString tss = parser.value(QStringLiteral("stylesheet")); if (tss.isEmpty()) { tss = QStringLiteral("customization/kde-chunk.xsl"); } QString tssPath = locateFileInDtdResource(tss); if (tssPath.isEmpty()) { DIE("Unable to find the stylesheet named" << tss << "in dtd resources"); } #ifndef MEINPROC_NO_KARCHIVE const QString cache = parser.value(QStringLiteral("cache")); #else if (parser.isSet("cache")) { qWarning() << QCoreApplication::translate("main", "The cache option is not available, please re-compile with KArchive support. See MEINPROC_NO_KARCHIVE in KDocTools"); } #endif const bool usingStdOut = parser.isSet(QStringLiteral("stdout")); const bool usingOutput = parser.isSet(QStringLiteral("output")); const QString outputOption = parser.value(QStringLiteral("output")); QString output = transform(checkFilename, tssPath, params); if (output.isEmpty()) { DIE("Unable to parse" << checkFilename); } #ifndef MEINPROC_NO_KARCHIVE if (!cache.isEmpty()) { if (!saveToCache(output, cache)) { qWarning() << QCoreApplication::translate("main", "Could not write to cache file %1.").arg(cache); } goto end; } #endif doOutput(output, usingStdOut, usingOutput, outputOption, true /* replaceCharset */); #ifndef MEINPROC_NO_KARCHIVE end: #endif xmlCleanupParser(); xmlMemoryDump(); return (0); } diff --git a/src/xslt.cpp b/src/xslt.cpp index 425e70b..e31d6e5 100644 --- a/src/xslt.cpp +++ b/src/xslt.cpp @@ -1,463 +1,463 @@ #include "xslt.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 #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()); qDebug() << "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; } qDebug() << "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; } qDebug() << "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 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 == NULL ) { + if ( pctxt == nullptr ) { return parsed; } - xmlDocPtr doc = xmlCtxtReadFile(pctxt, QFile::encodeName(pat).constData(), NULL, + 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 == NULL) { + if (doc == nullptr) { return parsed; } else { if (context_valid) { xmlFreeDoc(doc); return parsed; } } INFO(i18n("Applying stylesheet")); QVector p = params; - p.append(NULL); + p.append(nullptr); xmlDocPtr res = xsltApplyStylesheet(style_sheet, doc, const_cast(&p[0])); xmlFreeDoc(doc); - if (res != NULL) { - xmlOutputBufferPtr outp = xmlOutputBufferCreateIO(writeToQString, 0, &parsed, 0); + 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) { // //qDebug() << "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; res.sprintf("&#%d;", 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 } class DtdStandardDirs { public: QString srcdir; }; Q_GLOBAL_STATIC(DtdStandardDirs, s_dtdDirs) void setupStandardDirs(const QString &srcdir) { QByteArray catalogs; if (srcdir.isEmpty()) { catalogs += getKDocToolsCatalogs().join(" ").toLocal8Bit(); } else { catalogs += QUrl::fromLocalFile(srcdir + QStringLiteral("/customization/catalog.xml")).toEncoded(); s_dtdDirs()->srcdir = srcdir; } //qDebug() << "XML_CATALOG_FILES: " << catalogs; qputenv("XML_CATALOG_FILES", catalogs); xmlInitializeCatalog(); } QString 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; } qDebug() << "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()) { qDebug() << "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). QStringList dirNames = locateFilesInDtdResource(QStringLiteral("customization"), QStandardPaths::LocateDirectory); if (dirNames.isEmpty()) { return QStringList(); } QStringList catalogFiles; foreach (const QString &customizationDirName, dirNames) { QDir customizationDir = QDir(customizationDirName); const QStringList catalogFileFilters(QStringLiteral("catalog*.xml")); const QFileInfoList catalogInfoFiles = customizationDir.entryInfoList(catalogFileFilters, QDir::Files, QDir::Name); foreach (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; foreach (const QString &aCatalog, catalogFiles) { catalogs << aCatalog; } //qDebug() << "Found catalogs: " << catalogs; return catalogs; }