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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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