KDocTools

xslt.cpp
1#include "docbookxslt.h"
2#include "docbookxslt_p.h"
3
4#ifdef Q_OS_WIN
5// one of the xslt/xml headers pulls in windows.h and breaks <limits>
6#define NOMINMAX
7#include <QHash>
8#endif
9
10#include "../config-kdoctools.h"
11#include "loggingcategory.h"
12
13#include <libxml/catalog.h>
14#include <libxml/parser.h>
15#include <libxml/parserInternals.h>
16#include <libxml/xmlIO.h>
17#include <libxml/xmlsave.h>
18#include <libxslt/transform.h>
19#include <libxslt/xsltInternals.h>
20#include <libxslt/xsltconfig.h>
21#include <libxslt/xsltutils.h>
22
23#include <QByteArray>
24#include <QDir>
25#include <QFile>
26#include <QList>
27#include <QStandardPaths>
28#include <QString>
29#include <QUrl>
30
31#if !defined(SIMPLE_XSLT)
32extern HelpProtocol *slave;
33#define INFO(x) \
34 if (slave) \
35 slave->infoMessage(x);
36#else
37#define INFO(x)
38#endif
39
40int writeToQString(void *context, const char *buffer, int len)
41{
42 QString *t = (QString *)context;
43 *t += QString::fromUtf8(buffer, len);
44 return len;
45}
46
47#if defined(SIMPLE_XSLT) && defined(Q_OS_WIN)
48
49#define MAX_PATHS 64
50xmlExternalEntityLoader defaultEntityLoader = NULL;
51static xmlChar *paths[MAX_PATHS + 1];
52static int nbpaths = 0;
53static QHash<QString, QString> replaceURLList;
54
55/*
56 * Entity loading control and customization.
57 * taken from xsltproc.c
58 */
59static xmlParserInputPtr xsltprocExternalEntityLoader(const char *_URL, const char *ID, xmlParserCtxtPtr ctxt)
60{
61 xmlParserInputPtr ret;
62 warningSAXFunc warning = NULL;
63
64 // use local available dtd versions instead of fetching it every time from the internet
65 QString url = QLatin1String(_URL);
67 for (i = replaceURLList.constBegin(); i != replaceURLList.constEnd(); i++) {
68 if (url.startsWith(i.key())) {
69 url.replace(i.key(), i.value());
70 qCDebug(KDocToolsLog) << "converted" << _URL << "to" << url;
71 }
72 }
73 char URL[1024];
74 strcpy(URL, url.toLatin1().constData());
75
76 const char *lastsegment = URL;
77 const char *iter = URL;
78
79 if (nbpaths > 0) {
80 while (*iter != 0) {
81 if (*iter == '/') {
82 lastsegment = iter + 1;
83 }
84 iter++;
85 }
86 }
87
88 if ((ctxt != NULL) && (ctxt->sax != NULL)) {
89 warning = ctxt->sax->warning;
90 ctxt->sax->warning = NULL;
91 }
92
93 if (defaultEntityLoader != NULL) {
94 ret = defaultEntityLoader(URL, ID, ctxt);
95 if (ret != NULL) {
96 if (warning != NULL) {
97 ctxt->sax->warning = warning;
98 }
99 qCDebug(KDocToolsLog) << "Loaded URL=\"" << URL << "\" ID=\"" << ID << "\"";
100 return (ret);
101 }
102 }
103 for (int i = 0; i < nbpaths; i++) {
104 xmlChar *newURL;
105
106 newURL = xmlStrdup((const xmlChar *)paths[i]);
107 newURL = xmlStrcat(newURL, (const xmlChar *)"/");
108 newURL = xmlStrcat(newURL, (const xmlChar *)lastsegment);
109 if (newURL != NULL) {
110 ret = defaultEntityLoader((const char *)newURL, ID, ctxt);
111 if (ret != NULL) {
112 if (warning != NULL) {
113 ctxt->sax->warning = warning;
114 }
115 qCDebug(KDocToolsLog) << "Loaded URL=\"" << newURL << "\" ID=\"" << ID << "\"";
116 xmlFree(newURL);
117 return (ret);
118 }
119 xmlFree(newURL);
120 }
121 }
122 if (warning != NULL) {
123 ctxt->sax->warning = warning;
124 if (URL != NULL) {
125 warning(ctxt, "failed to load external entity \"%s\"\n", URL);
126 } else if (ID != NULL) {
127 warning(ctxt, "failed to load external entity \"%s\"\n", ID);
128 }
129 }
130 return (NULL);
131}
132#endif
133
134QString KDocTools::transform(const QString &pat, const QString &tss, const QList<const char *> &params)
135{
136 QString parsed;
137
138 INFO(i18n("Parsing stylesheet"));
139#if defined(SIMPLE_XSLT) && defined(Q_OS_WIN)
140 // prepare use of local available dtd versions instead of fetching every time from the internet
141 // this approach is url based
142 if (!defaultEntityLoader) {
143 defaultEntityLoader = xmlGetExternalEntityLoader();
144 xmlSetExternalEntityLoader(xsltprocExternalEntityLoader);
145
146 replaceURLList[QLatin1String("http://www.oasis-open.org/docbook/xml/4.5")] = QString("file:///%1").arg(DOCBOOK_XML_CURRDTD);
147 }
148#endif
149
150 xsltStylesheetPtr style_sheet = xsltParseStylesheetFile((const xmlChar *)QFile::encodeName(tss).constData());
151
152 if (!style_sheet) {
153 return parsed;
154 }
155 if (style_sheet->indent == 1) {
156 xmlIndentTreeOutput = 1;
157 } else {
158 xmlIndentTreeOutput = 0;
159 }
160
161 INFO(i18n("Parsing document"));
162
163 xmlParserCtxtPtr pctxt;
164
165 pctxt = xmlNewParserCtxt();
166 if (pctxt == nullptr) {
167 return parsed;
168 }
169
170 xmlDocPtr doc = xmlCtxtReadFile(pctxt, QFile::encodeName(pat).constData(), nullptr, XML_PARSE_NOENT | XML_PARSE_DTDLOAD | XML_PARSE_NONET);
171 /* Clean the context pointer, now useless */
172 const bool context_valid = (pctxt->valid == 0);
173 xmlFreeParserCtxt(pctxt);
174
175 /* Check both the returned doc (for parsing errors) and the context
176 (for validation errors) */
177 if (doc == nullptr) {
178 return parsed;
179 } else {
180 if (context_valid) {
181 xmlFreeDoc(doc);
182 return parsed;
183 }
184 }
185
186 INFO(i18n("Applying stylesheet"));
187 QList<const char *> p = params;
188 p.append(nullptr);
189 xmlDocPtr res = xsltApplyStylesheet(style_sheet, doc, const_cast<const char **>(&p[0]));
190 xmlFreeDoc(doc);
191 if (res != nullptr) {
192 xmlOutputBufferPtr outp = xmlOutputBufferCreateIO(writeToQString, nullptr, &parsed, nullptr);
193 outp->written = 0;
194 INFO(i18n("Writing document"));
195 xsltSaveResultTo(outp, res, style_sheet);
196 xmlOutputBufferClose(outp);
197 xmlFreeDoc(res);
198 }
199 xsltFreeStylesheet(style_sheet);
200
201 if (parsed.isEmpty()) {
202 parsed = QLatin1Char(' '); // avoid error message
203 }
204 return parsed;
205}
206
207/*
208xmlParserInputPtr meinExternalEntityLoader(const char *URL, const char *ID,
209 xmlParserCtxtPtr ctxt) {
210 xmlParserInputPtr ret = NULL;
211
212 // fprintf(stderr, "loading %s %s %s\n", URL, ID, ctxt->directory);
213
214 if (URL == NULL) {
215 if ((ctxt->sax != NULL) && (ctxt->sax->warning != NULL))
216 ctxt->sax->warning(ctxt,
217 "failed to load external entity \"%s\"\n", ID);
218 return(NULL);
219 }
220 if (!qstrcmp(ID, "-//OASIS//DTD DocBook XML V4.1.2//EN"))
221 URL = "docbook/xml-dtd-4.1.2/docbookx.dtd";
222 if (!qstrcmp(ID, "-//OASIS//DTD XML DocBook V4.1.2//EN"))
223 URL = "docbook/xml-dtd-4.1.2/docbookx.dtd";
224
225 QString file;
226 if (QFile::exists( QDir::currentPath() + "/" + URL ) )
227 file = QDir::currentPath() + "/" + URL;
228 else
229 file = locate("dtd", URL);
230
231 ret = xmlNewInputFromFile(ctxt, file.toLatin1().constData());
232 if (ret == NULL) {
233 if ((ctxt->sax != NULL) && (ctxt->sax->warning != NULL))
234 ctxt->sax->warning(ctxt,
235
236 "failed to load external entity \"%s\"\n", URL);
237 }
238 return(ret);
239}
240*/
241
242QString splitOut(const QString &parsed, int index)
243{
244 int start_index = index + 1;
245 while (parsed.at(start_index - 1) != QLatin1Char('>')) {
246 start_index++;
247 }
248
249 int inside = 0;
250
251 QString filedata;
252
253 while (true) {
254 int endindex = parsed.indexOf(QStringLiteral("</FILENAME>"), index);
255 int startindex = parsed.indexOf(QStringLiteral("<FILENAME "), index) + 1;
256
257 // qCDebug(KDocToolsLog) << "FILENAME " << startindex << " " << endindex << " " << inside << " " << parsed.mid(startindex + 18, 15)<< " " <<
258 // parsed.length();
259
260 if (startindex > 0) {
261 if (startindex < endindex) {
262 // qCDebug(KDocToolsLog) << "finding another";
263 index = startindex + 8;
264 inside++;
265 } else {
266 index = endindex + 8;
267 inside--;
268 }
269 } else {
270 inside--;
271 index = endindex + 1;
272 }
273
274 if (inside == 0) {
275 filedata = parsed.mid(start_index, endindex - start_index);
276 break;
277 }
278 }
279
280 index = filedata.indexOf(QStringLiteral("<FILENAME "));
281
282 if (index > 0) {
283 int endindex = filedata.lastIndexOf(QStringLiteral("</FILENAME>"));
284 while (filedata.at(endindex) != QLatin1Char('>')) {
285 endindex++;
286 }
287 endindex++;
288 filedata = filedata.left(index) + filedata.mid(endindex);
289 }
290
291 return filedata;
292}
293
294QByteArray fromUnicode(const QString &data)
295{
296 return data.toUtf8();
297}
298
299void replaceCharsetHeader(QString &output)
300{
301 // may be required for all xml output
302 if (output.contains("<table-of-contents>"))
303 output.replace(QLatin1String("<?xml version=\"1.0\"?>"), QLatin1String("<?xml version=\"1.0\" encoding=\"utf-8\"?>"));
304}
305
307{
308 const int index = content.indexOf(QLatin1String("<FILENAME filename=\"%1\"").arg(filename));
309 if (index == -1) {
310 if (filename == QLatin1String("index.html")) {
311 return fromUnicode(content);
312 } else {
313 return QByteArray(); // null value, not just empty
314 }
315 }
316 QString data_file = splitOut(content, index);
317 replaceCharsetHeader(data_file);
318 return fromUnicode(data_file);
319}
320
321class DtdStandardDirs
322{
323public:
324 QString srcdir;
325};
326
327Q_GLOBAL_STATIC(DtdStandardDirs, s_dtdDirs)
328
329void KDocTools::setupStandardDirs(const QString &srcdir)
330{
331 QByteArray catalogs;
332
333 if (srcdir.isEmpty()) {
334 catalogs += getKDocToolsCatalogs().join(" ").toLocal8Bit();
335 } else {
336 catalogs += QUrl::fromLocalFile(srcdir + QStringLiteral("/customization/catalog.xml")).toEncoded();
337 s_dtdDirs()->srcdir = srcdir;
338 }
339 // qCDebug(KDocToolsLog) << "XML_CATALOG_FILES: " << catalogs;
340 qputenv("XML_CATALOG_FILES", catalogs);
341 xmlInitializeCatalog();
342#if defined(_MSC_VER)
343 /* Workaround: apparently setting XML_CATALOG_FILES set here
344 has no effect on the libxml2 functions.
345 This code path could be used in all cases instead of setting the
346 variable, but this requires more investigation on the reason of
347 the issue. */
348 xmlLoadCatalogs(catalogs.constData());
349#endif
350}
351
353{
354 const QStringList lst = locateFilesInDtdResource(file, option);
355 return lst.isEmpty() ? QString() : lst.first();
356}
357
358QStringList locateFilesInDtdResource(const QString &file, const QStandardPaths::LocateOptions option)
359{
360 QFileInfo info(file);
361 if (info.exists() && info.isAbsolute()) {
362 return QStringList() << file;
363 }
364
365 const QString srcdir = s_dtdDirs()->srcdir;
366 if (!srcdir.isEmpty()) {
367 const QString test = srcdir + QLatin1Char('/') + file;
368 if (QFile::exists(test)) {
369 return QStringList() << test;
370 }
371 qCDebug(KDocToolsLog) << "Could not locate file" << file << "in" << srcdir;
372 return QStringList();
373 }
374 // Using locateAll() is necessary to be able to find all catalogs when
375 // running in environments where every repository is installed in its own
376 // prefix.
377 // This is the case on build.kde.org where kdelibs4support installs catalogs
378 // in a different prefix than kdoctools.
379 const QString fileName = QStringLiteral("kf6/kdoctools/") + file;
381
382 // fallback to stuff installed with KDocTools
383 const QFileInfo fileInInstallDataDir(QStringLiteral(KDOCTOOLS_INSTALL_DATADIR_KF) + QStringLiteral("/kdoctools/") + file);
384 if (fileInInstallDataDir.exists()) {
385 if ((option == QStandardPaths::LocateFile) && fileInInstallDataDir.isFile()) {
386 result.append(fileInInstallDataDir.absoluteFilePath());
387 }
388 if ((option == QStandardPaths::LocateDirectory) && fileInInstallDataDir.isDir()) {
389 result.append(fileInInstallDataDir.absoluteFilePath());
390 }
391 }
392
393 if (result.isEmpty()) {
394 qCDebug(KDocToolsLog) << "Could not locate file" << fileName << "in" << QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
395 }
396 return result;
397}
398
399QStringList getKDocToolsCatalogs()
400{
401 // Find all catalogs as catalog*.xml, and add them to the list, starting
402 // from catalog.xml (the main one).
403 const QStringList dirNames = locateFilesInDtdResource(QStringLiteral("customization"), QStandardPaths::LocateDirectory);
404 if (dirNames.isEmpty()) {
405 return QStringList();
406 }
407 QStringList catalogFiles;
408 for (const QString &customizationDirName : dirNames) {
409 QDir customizationDir = QDir(customizationDirName);
410 const QStringList catalogFileFilters(QStringLiteral("catalog*.xml"));
411 const QFileInfoList catalogInfoFiles = customizationDir.entryInfoList(catalogFileFilters, QDir::Files, QDir::Name);
412 for (const QFileInfo &fileInfo : catalogInfoFiles) {
413 const QString fullFileName = QUrl::fromLocalFile(fileInfo.absoluteFilePath()).toEncoded();
414 if (fileInfo.fileName() == QStringLiteral("catalog.xml")) {
415 catalogFiles.prepend(fullFileName);
416 } else {
417 catalogFiles.append(fullFileName);
418 }
419 }
420 }
421
422 return catalogFiles;
423}
424
426{
427 /* List of paths containing documentation */
429}
QString i18n(const char *text, const TYPE &arg...)
Utility methods to generate documentation in various format from DocBook files.
KDOCTOOLS_EXPORT QString transform(const QString &file, const QString &stylesheet, const QList< const char * > &params=QList< const char * >())
Transform and return the content of file with the specified XSLT stylesheet (both already in memory) ...
Definition xslt.cpp:134
KDOCTOOLS_EXPORT QString locateFileInDtdResource(const QString &file, const QStandardPaths::LocateOptions option=QStandardPaths::LocateFile)
Find a specified file amongst the resource shipped with KDocTools.
Definition xslt.cpp:352
KDOCTOOLS_EXPORT QByteArray extractFileToBuffer(const QString &content, const QString &filename)
Extract the content of a single file from the content string generated by the transformation scripts.
Definition xslt.cpp:306
KDOCTOOLS_EXPORT QStringList documentationDirs()
Returns the directories which can contain documentation.
Definition xslt.cpp:425
KGuiItem test()
const char * constData() const const
QFileInfoList entryInfoList(Filters filters, SortFlags sort) const const
QByteArray encodeName(const QString &fileName)
bool exists() const const
const_iterator constBegin() const const
const_iterator constEnd() const const
void append(QList< T > &&value)
T & first()
bool isEmpty() const const
void prepend(parameter_type value)
QStringList locateAll(StandardLocation type, const QString &fileName, LocateOptions options)
QStringList standardLocations(StandardLocation type)
QString arg(Args &&... args) const const
const QChar at(qsizetype position) const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString fromUtf8(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype lastIndexOf(QChar ch, Qt::CaseSensitivity cs) const const
QString left(qsizetype n) const const
QString mid(qsizetype position, qsizetype n) const const
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QByteArray toLatin1() const const
QByteArray toUtf8() const const
QUrl fromLocalFile(const QString &localFile)
QByteArray toEncoded(FormattingOptions options) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:17:51 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.