1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt Assistant of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include "../shared/collectionconfiguration.h"
30#include "helpgenerator.h"
31#include "collectionconfigreader.h"
32#include "qhelpprojectdata_p.h"
33
34#include <QtCore/QBuffer>
35#include <QtCore/QDataStream>
36#include <QtCore/QDir>
37#include <QtCore/QFileInfo>
38#include <QtCore/QLibraryInfo>
39#include <QtCore/QRegExp>
40#include <QtCore/QTranslator>
41
42#include <QtGui/QGuiApplication>
43
44#include <QtHelp/QHelpEngineCore>
45
46
47QT_USE_NAMESPACE
48
49class QHG {
50 Q_DECLARE_TR_FUNCTIONS(QHelpGenerator)
51};
52
53static const char QHP[] = "qhp";
54static const char QCH[] = "qch";
55
56static const char QHCP[] = "qhcp";
57static const char QHC[] = "qhc";
58
59namespace {
60 QString absoluteFilePath(const QString &basePath, const QString &fileName)
61 {
62 return QDir(basePath).absoluteFilePath(fileName);
63 }
64}
65
66int generateCollectionFile(const QByteArray &data, const QString &basePath, const QString outputFile)
67{
68 fputs(qPrintable(QHG::tr("Reading collection config file...\n")), stdout);
69 CollectionConfigReader config;
70 config.readData(contents: data);
71 if (config.hasError()) {
72 fputs(qPrintable(QHG::tr("Collection config file error: %1\n")
73 .arg(config.errorString())), stderr);
74 return 1;
75 }
76
77 const QMap<QString, QString> &filesToGenerate = config.filesToGenerate();
78 for (auto it = filesToGenerate.cbegin(), end = filesToGenerate.cend(); it != end; ++it) {
79 fputs(qPrintable(QHG::tr("Generating help for %1...\n").arg(it.key())), stdout);
80 QHelpProjectData helpData;
81 if (!helpData.readData(fileName: absoluteFilePath(basePath, fileName: it.key()))) {
82 fprintf(stderr, format: "%s\n", qPrintable(helpData.errorMessage()));
83 return 1;
84 }
85
86 HelpGenerator helpGenerator;
87 if (!helpGenerator.generate(helpData: &helpData, outputFileName: absoluteFilePath(basePath, fileName: it.value()))) {
88 fprintf(stderr, format: "%s\n", qPrintable(helpGenerator.error()));
89 return 1;
90 }
91 }
92
93 fputs(qPrintable(QHG::tr("Creating collection file...\n")), stdout);
94
95 QFileInfo colFi(outputFile);
96 if (colFi.exists()) {
97 if (!colFi.dir().remove(fileName: colFi.fileName())) {
98 fputs(qPrintable(QHG::tr("The file %1 cannot be overwritten.\n")
99 .arg(outputFile)), stderr);
100 return 1;
101 }
102 }
103
104 QHelpEngineCore helpEngine(outputFile);
105 if (!helpEngine.setupData()) {
106 fprintf(stderr, format: "%s\n", qPrintable(helpEngine.error()));
107 return 1;
108 }
109
110 for (const QString &file : config.filesToRegister()) {
111 if (!helpEngine.registerDocumentation(documentationFileName: absoluteFilePath(basePath, fileName: file))) {
112 fprintf(stderr, format: "%s\n", qPrintable(helpEngine.error()));
113 return 1;
114 }
115 }
116 if (!config.filesToRegister().isEmpty()) {
117 if (Q_UNLIKELY(qEnvironmentVariableIsSet("SOURCE_DATE_EPOCH"))) {
118 QDateTime dt;
119 dt.setSecsSinceEpoch(qEnvironmentVariableIntValue(varName: "SOURCE_DATE_EPOCH"));
120 CollectionConfiguration::updateLastRegisterTime(helpEngine, dt);
121 } else {
122 CollectionConfiguration::updateLastRegisterTime(helpEngine);
123 }
124 }
125
126 if (!config.title().isEmpty())
127 CollectionConfiguration::setWindowTitle(helpEngine, windowTitle: config.title());
128
129 if (!config.homePage().isEmpty()) {
130 CollectionConfiguration::setDefaultHomePage(helpEngine,
131 page: config.homePage());
132 }
133
134 if (!config.startPage().isEmpty()) {
135 CollectionConfiguration::setLastShownPages(helpEngine,
136 lastShownPages: QStringList(config.startPage()));
137 }
138
139 if (!config.currentFilter().isEmpty()) {
140 helpEngine.setCurrentFilter(config.currentFilter());
141 }
142
143 if (!config.cacheDirectory().isEmpty()) {
144 CollectionConfiguration::setCacheDir(helpEngine, cacheDir: config.cacheDirectory(),
145 relativeToCollection: config.cacheDirRelativeToCollection());
146 }
147
148 CollectionConfiguration::setFilterFunctionalityEnabled(helpEngine,
149 enabled: config.enableFilterFunctionality());
150 CollectionConfiguration::setFilterToolbarVisible(helpEngine,
151 visible: !config.hideFilterFunctionality());
152 CollectionConfiguration::setDocumentationManagerEnabled(helpEngine,
153 enabled: config.enableDocumentationManager());
154 CollectionConfiguration::setAddressBarEnabled(helpEngine,
155 enabled: config.enableAddressBar());
156 CollectionConfiguration::setAddressBarVisible(helpEngine,
157 visible: !config.hideAddressBar());
158 uint time = QDateTime::currentMSecsSinceEpoch() / 1000;
159 if (Q_UNLIKELY(qEnvironmentVariableIsSet("SOURCE_DATE_EPOCH")))
160 time = qEnvironmentVariableIntValue(varName: "SOURCE_DATE_EPOCH");
161 CollectionConfiguration::setCreationTime(helpEngine, time);
162 CollectionConfiguration::setFullTextSearchFallbackEnabled(helpEngine,
163 on: config.fullTextSearchFallbackEnabled());
164
165 if (!config.applicationIcon().isEmpty()) {
166 QFile icon(absoluteFilePath(basePath, fileName: config.applicationIcon()));
167 if (!icon.open(flags: QIODevice::ReadOnly)) {
168 fputs(qPrintable(QHG::tr("Cannot open %1.\n").arg(icon.fileName())), stderr);
169 return 1;
170 }
171 CollectionConfiguration::setApplicationIcon(helpEngine, icon: icon.readAll());
172 }
173
174 if (config.aboutMenuTexts().count()) {
175 QByteArray ba;
176 QDataStream s(&ba, QIODevice::WriteOnly);
177 const QMap<QString, QString> &aboutMenuTexts = config.aboutMenuTexts();
178 for (auto it = aboutMenuTexts.cbegin(), end = aboutMenuTexts.cend(); it != end; ++it)
179 s << it.key() << it.value();
180 CollectionConfiguration::setAboutMenuTexts(helpEngine, texts: ba);
181 }
182
183 if (!config.aboutIcon().isEmpty()) {
184 QFile icon(absoluteFilePath(basePath, fileName: config.aboutIcon()));
185 if (!icon.open(flags: QIODevice::ReadOnly)) {
186 fputs(qPrintable(QHG::tr("Cannot open %1.\n").arg(icon.fileName())), stderr);
187 return 1;
188 }
189 CollectionConfiguration::setAboutIcon(helpEngine, icon: icon.readAll());
190 }
191
192 if (config.aboutTextFiles().count()) {
193 QByteArray ba;
194 QDataStream s(&ba, QIODevice::WriteOnly);
195 QMap<QString, QByteArray> imgData;
196
197 QRegExp srcRegExp(QLatin1String("src=(\"(.+)\"|([^\"\\s]+)).*>"));
198 srcRegExp.setMinimal(true);
199 QRegExp imgRegExp(QLatin1String("(<img[^>]+>)"));
200 imgRegExp.setMinimal(true);
201
202 const QMap<QString, QString> &aboutMenuTexts = config.aboutTextFiles();
203 for (auto it = aboutMenuTexts.cbegin(), end = aboutMenuTexts.cend(); it != end; ++it) {
204 s << it.key();
205 QFileInfo fi(absoluteFilePath(basePath, fileName: it.value()));
206 QFile f(fi.absoluteFilePath());
207 if (!f.open(flags: QIODevice::ReadOnly)) {
208 fputs(qPrintable(QHG::tr("Cannot open %1.\n").arg(f.fileName())), stderr);
209 return 1;
210 }
211 QByteArray data = f.readAll();
212 s << data;
213
214 QString contents = QString::fromUtf8(str: data);
215 int pos = 0;
216 while ((pos = imgRegExp.indexIn(str: contents, offset: pos)) != -1) {
217 QString imgTag = imgRegExp.cap(nth: 1);
218 pos += imgRegExp.matchedLength();
219
220 if (srcRegExp.indexIn(str: imgTag, offset: 0) != -1) {
221 QString src = srcRegExp.cap(nth: 2);
222 if (src.isEmpty())
223 src = srcRegExp.cap(nth: 3);
224
225 QFile img(fi.absolutePath() + QDir::separator() + src);
226 if (img.open(flags: QIODevice::ReadOnly)) {
227 if (!imgData.contains(akey: src))
228 imgData.insert(akey: src, avalue: img.readAll());
229 } else {
230 fputs(qPrintable(QHG::tr("Cannot open referenced image file %1.\n")
231 .arg(img.fileName())), stderr);
232 }
233 }
234 }
235 }
236 CollectionConfiguration::setAboutTexts(helpEngine, texts: ba);
237 if (imgData.count()) {
238 QByteArray imageData;
239 QBuffer buffer(&imageData);
240 buffer.open(openMode: QIODevice::WriteOnly);
241 QDataStream out(&buffer);
242 out << imgData;
243 CollectionConfiguration::setAboutImages(helpEngine, images: imageData);
244 }
245 }
246 return 0;
247}
248
249int main(int argc, char *argv[])
250{
251 QString error;
252 QString outputFile;
253 QString inputFile;
254 QString basePath;
255 bool showHelp = false;
256 bool showVersion = false;
257 bool checkLinks = false;
258 bool silent = false;
259
260 // don't require a window manager even though we're a QGuiApplication
261 qputenv(varName: "QT_QPA_PLATFORM", QByteArrayLiteral("minimal"));
262
263 QGuiApplication app(argc, argv);
264#ifndef Q_OS_WIN32
265 QTranslator translator;
266 QTranslator qtTranslator;
267 QTranslator qt_helpTranslator;
268 QString sysLocale = QLocale::system().name();
269 QString resourceDir = QLibraryInfo::location(QLibraryInfo::TranslationsPath);
270 if (translator.load(filename: QLatin1String("assistant_") + sysLocale, directory: resourceDir)
271 && qtTranslator.load(filename: QLatin1String("qt_") + sysLocale, directory: resourceDir)
272 && qt_helpTranslator.load(filename: QLatin1String("qt_help_") + sysLocale, directory: resourceDir)) {
273 app.installTranslator(messageFile: &translator);
274 app.installTranslator(messageFile: &qtTranslator);
275 app.installTranslator(messageFile: &qt_helpTranslator);
276 }
277#endif // Q_OS_WIN32
278
279 for (int i = 1; i < argc; ++i) {
280 const QString arg = QString::fromLocal8Bit(str: argv[i]);
281 if (arg == QLatin1String("-o")) {
282 if (++i < argc) {
283 QFileInfo fi(QString::fromLocal8Bit(str: argv[i]));
284 outputFile = fi.absoluteFilePath();
285 } else {
286 error = QHG::tr(sourceText: "Missing output file name.");
287 }
288 } else if (arg == QLatin1String("-v")) {
289 showVersion = true;
290 } else if (arg == QLatin1String("-h")) {
291 showHelp = true;
292 } else if (arg == QLatin1String("-c")) {
293 checkLinks = true;
294 } else if (arg == QLatin1String("-s")) {
295 silent = true;
296 } else {
297 const QFileInfo fi(arg);
298 inputFile = fi.absoluteFilePath();
299 basePath = fi.absolutePath();
300 }
301 }
302
303 if (showVersion) {
304 fputs(qPrintable(QHG::tr("Qt Help Generator version 1.0 (Qt %1)\n")
305 .arg(QT_VERSION_STR)), stdout);
306 return 0;
307 }
308
309 enum InputType {
310 InputQhp,
311 InputQhcp,
312 InputUnknown
313 };
314
315 InputType inputType = InputUnknown;
316
317 if (!showHelp) {
318 if (inputFile.isEmpty()) {
319 error = QHG::tr(sourceText: "Missing input file name.");
320 } else {
321 const QFileInfo fi(inputFile);
322 if (fi.suffix() == QHP)
323 inputType = InputQhp;
324 else if (fi.suffix() == QHCP)
325 inputType = InputQhcp;
326
327 if (inputType == InputUnknown)
328 error = QHG::tr(sourceText: "Unknown input file type.");
329 }
330 }
331
332 const QString help = QHG::tr(sourceText: "\nUsage:\n\n"
333 "qhelpgenerator <file> [options]\n\n"
334 " -o <output-file> Generates a Qt compressed help\n"
335 " called <output-file> (*.qch) for the\n"
336 " Qt help project <file> (*.qhp).\n"
337 " Generates a Qt help collection\n"
338 " called <output-file> (*.qhc) for the\n"
339 " Qt help collection project <file> (*.qhcp).\n"
340 " If this option is not specified\n"
341 " a default name will be used\n"
342 " (*.qch for *.qhp and *.qhc for *.qhcp).\n"
343 " -c Checks whether all links in HTML files\n"
344 " point to files in this help project.\n"
345 " -s Suppresses status messages.\n"
346 " -v Displays the version of \n"
347 " qhelpgenerator.\n\n");
348
349 if (showHelp) {
350 fputs(qPrintable(help), stdout);
351 return 0;
352 } else if (!error.isEmpty()) {
353 fprintf(stderr, format: "%s\n\n%s", qPrintable(error), qPrintable(help));
354 return 1;
355 }
356
357 // detect input file type (qhp or qhcp)
358
359 QFile file(inputFile);
360 if (!file.open(flags: QIODevice::ReadOnly)) {
361 fputs(qPrintable(QHG::tr("Could not open %1.\n").arg(inputFile)), stderr);
362 return 1;
363 }
364
365 const QString outputExtension = inputType == InputQhp ? QCH : QHC;
366
367 if (outputFile.isEmpty()) {
368 if (inputType == InputQhcp || !checkLinks) {
369 QFileInfo fi(inputFile);
370 outputFile = basePath + QDir::separator()
371 + fi.baseName() + QLatin1Char('.') + outputExtension;
372 }
373 } else {
374 // check if the output dir exists -- create if it doesn't
375 QFileInfo fi(outputFile);
376 QDir parentDir = fi.dir();
377 if (!parentDir.exists()) {
378 if (!parentDir.mkpath(dirPath: QLatin1String("."))) {
379 fputs(qPrintable(QHG::tr("Could not create output directory: %1\n")
380 .arg(parentDir.path())), stderr);
381 }
382 }
383 }
384
385 if (inputType == InputQhp) {
386 QHelpProjectData *helpData = new QHelpProjectData();
387 if (!helpData->readData(fileName: inputFile)) {
388 fprintf(stderr, format: "%s\n", qPrintable(helpData->errorMessage()));
389 return 1;
390 }
391
392 HelpGenerator generator(silent);
393 bool success = true;
394 if (checkLinks)
395 success = generator.checkLinks(helpData: *helpData);
396 if (success && !outputFile.isEmpty())
397 success = generator.generate(helpData, outputFileName: outputFile);
398 delete helpData;
399 if (!success) {
400 fprintf(stderr, format: "%s\n", qPrintable(generator.error()));
401 return 1;
402 }
403 } else {
404 const QByteArray data = file.readAll();
405 return generateCollectionFile(data, basePath, outputFile);
406
407 }
408
409 return 0;
410}
411

source code of qttools/src/assistant/qhelpgenerator/main.cpp