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

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