1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Assistant of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
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 http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file. Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "../shared/collectionconfiguration.h"
43 #include "../shared/helpgenerator.h"
44
45 #include <private/qhelpgenerator_p.h>
46 #include <private/qhelpprojectdata_p.h>
47
48 #include <QtCore/QCoreApplication>
49 #include <QtCore/QDir>
50 #include <QtCore/QMap>
51 #include <QtCore/QFileInfo>
52 #include <QtCore/QDateTime>
53 #include <QtCore/QBuffer>
54 #include <QtCore/QTranslator>
55 #include <QtCore/QLocale>
56 #include <QtCore/QLibraryInfo>
57 #include <QtHelp/QHelpEngineCore>
58 #include <QtXml/QXmlStreamReader>
59
60
61 QT_USE_NAMESPACE
62
63 class QCG {
64 Q_DECLARE_TR_FUNCTIONS(QCollectionGenerator)
65 };
66
67 class CollectionConfigReader : public QXmlStreamReader
68 {
69 public:
70 void readData(const QByteArray &contents);
71
title() const72 QString title() const { return m_title; }
homePage() const73 QString homePage() const { return m_homePage; }
startPage() const74 QString startPage() const { return m_startPage; }
applicationIcon() const75 QString applicationIcon() const { return m_applicationIcon; }
currentFilter() const76 QString currentFilter() const { return m_currentFilter; }
enableFilterFunctionality() const77 bool enableFilterFunctionality() const
78 { return m_enableFilterFunctionality; }
hideFilterFunctionality() const79 bool hideFilterFunctionality() const
80 { return m_hideFilterFunctionality; }
enableAddressBar() const81 bool enableAddressBar() const { return m_enableAddressBar; }
hideAddressBar() const82 bool hideAddressBar() const { return m_hideAddressBar; }
enableDocumentationManager() const83 bool enableDocumentationManager() const
84 { return m_enableDocumentationManager; }
85
aboutMenuTexts() const86 QMap<QString, QString> aboutMenuTexts() const
87 { return m_aboutMenuTexts; }
aboutIcon() const88 QString aboutIcon() const { return m_aboutIcon; }
aboutTextFiles() const89 QMap<QString, QString> aboutTextFiles() const
90 { return m_aboutTextFiles; }
91
filesToGenerate() const92 QMap<QString, QString> filesToGenerate() const
93 { return m_filesToGenerate; }
94
filesToRegister() const95 QStringList filesToRegister() const { return m_filesToRegister; }
96
cacheDirectory() const97 QString cacheDirectory() const { return m_cacheDirectory; }
cacheDirRelativeToCollection() const98 bool cacheDirRelativeToCollection() const { return m_cacheDirRelativeToCollection; }
99
fullTextSearchFallbackEnabled() const100 bool fullTextSearchFallbackEnabled() const {
101 return m_enableFullTextSearchFallback;
102 }
103
104 private:
105 void raiseErrorWithLine();
106 void readConfig();
107 void readAssistantSettings();
108 void readMenuTexts();
109 void readAboutDialog();
110 void readDocFiles();
111 void readGenerate();
112 void readFiles();
113 void readRegister();
114
115 QString m_title;
116 QString m_homePage;
117 QString m_startPage;
118 QString m_applicationIcon;
119 QString m_currentFilter;
120 bool m_enableFilterFunctionality;
121 bool m_hideFilterFunctionality;
122 bool m_enableAddressBar;
123 bool m_hideAddressBar;
124 bool m_enableDocumentationManager;
125 QMap<QString, QString> m_aboutMenuTexts;
126 QString m_aboutIcon;
127 QMap<QString, QString> m_aboutTextFiles;
128 QMap<QString, QString> m_filesToGenerate;
129 QStringList m_filesToRegister;
130 QString m_cacheDirectory;
131 bool m_cacheDirRelativeToCollection;
132 bool m_enableFullTextSearchFallback;
133 };
134
raiseErrorWithLine()135 void CollectionConfigReader::raiseErrorWithLine()
136 {
137 raiseError(QCG::tr("Unknown token at line %1.").arg(lineNumber()));
138 }
139
readData(const QByteArray & contents)140 void CollectionConfigReader::readData(const QByteArray &contents)
141 {
142 m_enableFilterFunctionality = true;
143 m_hideFilterFunctionality = true;
144 m_enableAddressBar = true;
145 m_hideAddressBar = true;
146 m_enableDocumentationManager = true;
147 m_enableFullTextSearchFallback = false;
148
149 addData(contents);
150 while (!atEnd()) {
151 readNext();
152 if (isStartElement()) {
153 if (name() == QLatin1String("QHelpCollectionProject")
154 && attributes().value(QLatin1String("version")) == QLatin1String("1.0"))
155 readConfig();
156 else
157 raiseError(QCG::tr("Unknown token at line %1. "
158 "Expected \"QtHelpCollectionProject\".")
159 .arg(lineNumber()));
160 }
161 }
162 }
163
readConfig()164 void CollectionConfigReader::readConfig()
165 {
166 bool ok = false;
167 while (!atEnd()) {
168 readNext();
169 if (isStartElement()) {
170 if (name() == QLatin1String("assistant"))
171 readAssistantSettings();
172 else if (name() == QLatin1String("docFiles"))
173 readDocFiles();
174 else
175 raiseErrorWithLine();
176 } else if (isEndElement() && name() == QLatin1String("QHelpCollectionProject")) {
177 ok = true;
178 }
179 }
180 if (!ok && !hasError())
181 raiseError(QCG::tr("Missing end tags."));
182 }
183
readAssistantSettings()184 void CollectionConfigReader::readAssistantSettings()
185 {
186 while (!atEnd()) {
187 readNext();
188 if (isStartElement()) {
189 if (name() == QLatin1String("title")) {
190 m_title = readElementText();
191 } else if (name() == QLatin1String("homePage")) {
192 m_homePage = readElementText();
193 } else if (name() == QLatin1String("startPage")) {
194 m_startPage = readElementText();
195 } else if (name() == QLatin1String("currentFilter")) {
196 m_currentFilter = readElementText();
197 } else if (name() == QLatin1String("applicationIcon")) {
198 m_applicationIcon = readElementText();
199 } else if (name() == QLatin1String("enableFilterFunctionality")) {
200 if (attributes().value(QLatin1String("visible")) == QLatin1String("true"))
201 m_hideFilterFunctionality = false;
202 if (readElementText() == QLatin1String("false"))
203 m_enableFilterFunctionality = false;
204 } else if (name() == QLatin1String("enableDocumentationManager")) {
205 if (readElementText() == QLatin1String("false"))
206 m_enableDocumentationManager = false;
207 } else if (name() == QLatin1String("enableAddressBar")) {
208 if (attributes().value(QLatin1String("visible")) == QLatin1String("true"))
209 m_hideAddressBar = false;
210 if (readElementText() == QLatin1String("false"))
211 m_enableAddressBar = false;
212 } else if (name() == QLatin1String("aboutMenuText")) {
213 readMenuTexts();
214 } else if (name() == QLatin1String("aboutDialog")) {
215 readAboutDialog();
216 } else if (name() == "cacheDirectory") {
217 m_cacheDirRelativeToCollection =
218 attributes().value(QLatin1String("base"))
219 == QLatin1String("collection");
220 m_cacheDirectory = readElementText();
221 } else if (name() == QLatin1String("enableFullTextSearchFallback")) {
222 if (readElementText() == QLatin1String("true"))
223 m_enableFullTextSearchFallback = true;
224 } else {
225 raiseErrorWithLine();
226 }
227 } else if (isEndElement() && name() == QLatin1String("assistant")) {
228 break;
229 }
230 }
231 }
232
readMenuTexts()233 void CollectionConfigReader::readMenuTexts()
234 {
235 while (!atEnd())
236 {
237 readNext();
238 if (isStartElement()) {
239 if (name() == QLatin1String("text")) {
240 QString lang = attributes().value(QLatin1String("language")).toString();
241 if (lang.isEmpty())
242 lang = QLatin1String("default");
243 m_aboutMenuTexts.insert(lang, readElementText());
244 } else {
245 raiseErrorWithLine();
246 }
247 } else if (isEndElement() && name() == QLatin1String("aboutMenuText")) {
248 break;
249 }
250 }
251 }
252
readAboutDialog()253 void CollectionConfigReader::readAboutDialog()
254 {
255 while (!atEnd())
256 {
257 readNext();
258 if (isStartElement()) {
259 if (name() == QLatin1String("file")) {
260 QString lang = attributes().value(QLatin1String("language")).toString();
261 if (lang.isEmpty())
262 lang = QLatin1String("default");
263 m_aboutTextFiles.insert(lang, readElementText());
264 } else if (name() == QLatin1String("icon")) {
265 m_aboutIcon = readElementText();
266 } else {
267 raiseErrorWithLine();
268 }
269 } else if (isEndElement() && name() == QLatin1String("aboutDialog")) {
270 break;
271 }
272 }
273 }
274
readDocFiles()275 void CollectionConfigReader::readDocFiles()
276 {
277 while (!atEnd()) {
278 readNext();
279 if (isStartElement()) {
280 if (name() == QLatin1String("generate")) {
281 readGenerate();
282 } else if (name() == QLatin1String("register")) {
283 readRegister();
284 } else {
285 raiseErrorWithLine();
286 }
287 } else if (isEndElement() && name() == QLatin1String("docFiles")) {
288 break;
289 }
290 }
291 }
292
readGenerate()293 void CollectionConfigReader::readGenerate()
294 {
295 while (!atEnd()) {
296 readNext();
297 if (isStartElement()) {
298 if (name() == QLatin1String("file"))
299 readFiles();
300 else
301 raiseErrorWithLine();
302 } else if (isEndElement() && name() == QLatin1String("generate")) {
303 break;
304 }
305 }
306 }
307
readFiles()308 void CollectionConfigReader::readFiles()
309 {
310 QString input;
311 QString output;
312 while (!atEnd()) {
313 readNext();
314 if (isStartElement()) {
315 if (name() == QLatin1String("input"))
316 input = readElementText();
317 else if (name() == QLatin1String("output"))
318 output = readElementText();
319 else
320 raiseErrorWithLine();
321 } else if (isEndElement() && name() == QLatin1String("file")) {
322 break;
323 }
324 }
325 if (input.isEmpty() || output.isEmpty()) {
326 raiseError(QCG::tr("Missing input or output file for help file generation."));
327 return;
328 }
329 m_filesToGenerate.insert(input, output);
330 }
331
readRegister()332 void CollectionConfigReader::readRegister()
333 {
334 while (!atEnd()) {
335 readNext();
336 if (isStartElement()) {
337 if (name() == QLatin1String("file"))
338 m_filesToRegister.append(readElementText());
339 else
340 raiseErrorWithLine();
341 } else if (isEndElement() && name() == QLatin1String("register")) {
342 break;
343 }
344 }
345 }
346
347 namespace {
absoluteFileName(const QString & basePath,const QString & fileName)348 QString absoluteFileName(const QString &basePath, const QString &fileName)
349 {
350 return QFileInfo(fileName).isAbsolute() ?
351 fileName : basePath + QDir::separator() + fileName;
352 }
353 }
354
main(int argc,char * argv[])355 int main(int argc, char *argv[])
356 {
357 QString error;
358 QString arg;
359 QString collectionFile;
360 QString configFile;
361 QString basePath;
362 bool showHelp = false;
363 bool showVersion = false;
364
365 QCoreApplication app(argc, argv);
366 #ifndef Q_OS_WIN32
367 QTranslator translator;
368 QTranslator qtTranslator;
369 QTranslator qt_helpTranslator;
370 QString sysLocale = QLocale::system().name();
371 QString resourceDir = QLibraryInfo::location(QLibraryInfo::TranslationsPath);
372 if (translator.load(QLatin1String("assistant_") + sysLocale, resourceDir)
373 && qtTranslator.load(QLatin1String("qt_") + sysLocale, resourceDir)
374 && qt_helpTranslator.load(QLatin1String("qt_help_") + sysLocale, resourceDir)) {
375 app.installTranslator(&translator);
376 app.installTranslator(&qtTranslator);
377 app.installTranslator(&qt_helpTranslator);
378 }
379 #endif // Q_OS_WIN32
380
381 for (int i=1; i<argc; ++i) {
382 arg = QString::fromLocal8Bit(argv[i]);
383 if (arg == QLatin1String("-o")) {
384 if (++i < argc) {
385 QFileInfo fi(QString::fromLocal8Bit(argv[i]));
386 collectionFile = fi.absoluteFilePath();
387 } else {
388 error = QCG::tr("Missing output file name.");
389 }
390 } else if (arg == QLatin1String("-h")) {
391 showHelp = true;
392 } else if (arg == QLatin1String("-v")) {
393 showVersion = true;
394 } else {
395 QFileInfo fi(arg);
396 configFile = fi.absoluteFilePath();
397 basePath = fi.absolutePath();
398 }
399 }
400
401 if (showVersion) {
402 fputs(qPrintable(QCG::tr("Qt Collection Generator version 1.0 (Qt %1)\n")
403 .arg(QT_VERSION_STR)), stdout);
404 return 0;
405 }
406
407 if (configFile.isEmpty() && !showHelp)
408 error = QCG::tr("Missing collection config file.");
409
410 QString help = QCG::tr("\nUsage:\n\n"
411 "qcollectiongenerator <collection-config-file> [options]\n\n"
412 " -o <collection-file> Generates a collection file\n"
413 " called <collection-file>. If\n"
414 " this option is not specified\n"
415 " a default name will be used.\n"
416 " -v Displays the version of\n"
417 " qcollectiongenerator.\n\n");
418
419 if (showHelp) {
420 fputs(qPrintable(help), stdout);
421 return 0;
422 }else if (!error.isEmpty()) {
423 fprintf(stderr, "%s\n\n%s", qPrintable(error), qPrintable(help));
424 return -1;
425 }
426
427 QFile file(configFile);
428 if (!file.open(QIODevice::ReadOnly)) {
429 fputs(qPrintable(QCG::tr("Could not open %1.\n").arg(configFile)), stderr);
430 return -1;
431 }
432
433 if (collectionFile.isEmpty()) {
434 QFileInfo fi(configFile);
435 collectionFile = basePath + QDir::separator()
436 + fi.baseName() + QLatin1String(".qhc");
437 }
438
439 fputs(qPrintable(QCG::tr("Reading collection config file...\n")), stdout);
440 CollectionConfigReader config;
441 config.readData(file.readAll());
442 if (config.hasError()) {
443 fputs(qPrintable(QCG::tr("Collection config file error: %1\n")
444 .arg(config.errorString())), stderr);
445 return -1;
446 }
447
448 QMap<QString, QString>::const_iterator it = config.filesToGenerate().constBegin();
449 while (it != config.filesToGenerate().constEnd()) {
450 fputs(qPrintable(QCG::tr("Generating help for %1...\n").arg(it.key())), stdout);
451 QHelpProjectData helpData;
452 if (!helpData.readData(absoluteFileName(basePath, it.key()))) {
453 fprintf(stderr, "%s\n", qPrintable(helpData.errorMessage()));
454 return -1;
455 }
456
457 HelpGenerator helpGenerator;
458 if (!helpGenerator.generate(&helpData, absoluteFileName(basePath, it.value()))) {
459 fprintf(stderr, "%s\n", qPrintable(helpGenerator.error()));
460 return -1;
461 }
462 ++it;
463 }
464
465 fputs(qPrintable(QCG::tr("Creating collection file...\n")), stdout);
466
467 QFileInfo colFi(collectionFile);
468 if (colFi.exists()) {
469 if (!colFi.dir().remove(colFi.fileName())) {
470 fputs(qPrintable(QCG::tr("The file %1 cannot be overwritten.\n")
471 .arg(collectionFile)), stderr);
472 return -1;
473 }
474 }
475
476 QHelpEngineCore helpEngine(collectionFile);
477 if (!helpEngine.setupData()) {
478 fprintf(stderr, "%s\n", qPrintable(helpEngine.error()));
479 return -1;
480 }
481
482 foreach (const QString &file, config.filesToRegister()) {
483 if (!helpEngine.registerDocumentation(absoluteFileName(basePath, file))) {
484 fprintf(stderr, "%s\n", qPrintable(helpEngine.error()));
485 return -1;
486 }
487 }
488 if (!config.filesToRegister().isEmpty())
489 CollectionConfiguration::updateLastRegisterTime(helpEngine);
490
491 if (!config.title().isEmpty())
492 CollectionConfiguration::setWindowTitle(helpEngine, config.title());
493
494 if (!config.homePage().isEmpty()) {
495 CollectionConfiguration::setDefaultHomePage(helpEngine,
496 config.homePage());
497 }
498
499 if (!config.startPage().isEmpty()) {
500 CollectionConfiguration::setLastShownPages(helpEngine,
501 QStringList(config.startPage()));
502 }
503
504 if (!config.currentFilter().isEmpty()) {
505 helpEngine.setCurrentFilter(config.currentFilter());
506 }
507
508 if (!config.cacheDirectory().isEmpty()) {
509 CollectionConfiguration::setCacheDir(helpEngine, config.cacheDirectory(),
510 config.cacheDirRelativeToCollection());
511 }
512
513 CollectionConfiguration::setFilterFunctionalityEnabled(helpEngine,
514 config.enableFilterFunctionality());
515 CollectionConfiguration::setFilterToolbarVisible(helpEngine,
516 !config.hideFilterFunctionality());
517 CollectionConfiguration::setDocumentationManagerEnabled(helpEngine,
518 config.enableDocumentationManager());
519 CollectionConfiguration::setAddressBarEnabled(helpEngine,
520 config.enableAddressBar());
521 CollectionConfiguration::setAddressBarVisible(helpEngine,
522 !config.hideAddressBar());
523 CollectionConfiguration::setCreationTime(helpEngine,
524 QDateTime::currentDateTime().toTime_t());
525 CollectionConfiguration::setFullTextSearchFallbackEnabled(helpEngine,
526 config.fullTextSearchFallbackEnabled());
527
528 if (!config.applicationIcon().isEmpty()) {
529 QFile icon(absoluteFileName(basePath, config.applicationIcon()));
530 if (!icon.open(QIODevice::ReadOnly)) {
531 fputs(qPrintable(QCG::tr("Cannot open %1.\n").arg(icon.fileName())), stderr);
532 return -1;
533 }
534 CollectionConfiguration::setApplicationIcon(helpEngine, icon.readAll());
535 }
536
537 if (config.aboutMenuTexts().count()) {
538 QByteArray ba;
539 QDataStream s(&ba, QIODevice::WriteOnly);
540 QMap<QString, QString>::const_iterator it = config.aboutMenuTexts().constBegin();
541 while (it != config.aboutMenuTexts().constEnd()) {
542 s << it.key();
543 s << it.value();
544 ++it;
545 }
546 CollectionConfiguration::setAboutMenuTexts(helpEngine, ba);
547 }
548
549 if (!config.aboutIcon().isEmpty()) {
550 QFile icon(absoluteFileName(basePath, config.aboutIcon()));
551 if (!icon.open(QIODevice::ReadOnly)) {
552 fputs(qPrintable(QCG::tr("Cannot open %1.\n").arg(icon.fileName())), stderr);
553 return -1;
554 }
555 CollectionConfiguration::setAboutIcon(helpEngine, icon.readAll());
556 }
557
558 if (config.aboutTextFiles().count()) {
559 QByteArray ba;
560 QDataStream s(&ba, QIODevice::WriteOnly);
561 QMap<QString, QString>::const_iterator it = config.aboutTextFiles().constBegin();
562 QMap<QString, QByteArray> imgData;
563
564 QRegExp srcRegExp(QLatin1String("src=(\"(.+)\"|([^\"\\s]+)).*>"));
565 srcRegExp.setMinimal(true);
566 QRegExp imgRegExp(QLatin1String("(<img[^>]+>)"));
567 imgRegExp.setMinimal(true);
568
569 while (it != config.aboutTextFiles().constEnd()) {
570 s << it.key();
571 QFileInfo fi(absoluteFileName(basePath, it.value()));
572 QFile f(fi.absoluteFilePath());
573 if (!f.open(QIODevice::ReadOnly)) {
574 fputs(qPrintable(QCG::tr("Cannot open %1.\n").arg(f.fileName())), stderr);
575 return -1;
576 }
577 QByteArray data = f.readAll();
578 s << data;
579
580 QString contents = QString::fromUtf8(data);
581 int pos = 0;
582 while ((pos = imgRegExp.indexIn(contents, pos)) != -1) {
583 QString imgTag = imgRegExp.cap(1);
584 pos += imgRegExp.matchedLength();
585
586 if (srcRegExp.indexIn(imgTag, 0) != -1) {
587 QString src = srcRegExp.cap(2);
588 if (src.isEmpty())
589 src = srcRegExp.cap(3);
590
591 QFile img(fi.absolutePath() + QDir::separator() + src);
592 if (img.open(QIODevice::ReadOnly)) {
593 if (!imgData.contains(src))
594 imgData.insert(src, img.readAll());
595 } else {
596 fputs(qPrintable(QCG::tr("Cannot open referenced image file %1.\n")
597 .arg(img.fileName())), stderr);
598 }
599 }
600 }
601 ++it;
602 }
603 CollectionConfiguration::setAboutTexts(helpEngine, ba);
604 if (imgData.count()) {
605 QByteArray imageData;
606 QBuffer buffer(&imageData);
607 buffer.open(QIODevice::WriteOnly);
608 QDataStream out(&buffer);
609 out << imgData;
610 CollectionConfiguration::setAboutImages(helpEngine, imageData);
611 }
612 }
613
614 return 0;
615 }
616