source: trunk/tools/assistant/lib/qhelpgenerator.cpp@ 966

Last change on this file since 966 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 30.0 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the Qt Assistant of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qhelpgenerator_p.h"
43#include "qhelpdatainterface_p.h"
44
45#include <math.h>
46#include <QtCore/QFile>
47#include <QtCore/QFileInfo>
48#include <QtCore/QDir>
49#include <QtCore/QDebug>
50#include <QtCore/QSet>
51#include <QtCore/QVariant>
52#include <QtCore/QDateTime>
53#include <QtCore/QTextCodec>
54#include <QtSql/QSqlQuery>
55
56QT_BEGIN_NAMESPACE
57
58class QHelpGeneratorPrivate
59{
60public:
61 QHelpGeneratorPrivate();
62 ~QHelpGeneratorPrivate();
63
64 QString error;
65 QSqlQuery *query;
66
67 int namespaceId;
68 int virtualFolderId;
69
70 QMap<QString, int> fileMap;
71 QMap<int, QSet<int> > fileFilterMap;
72
73 double progress;
74 double oldProgress;
75 double contentStep;
76 double fileStep;
77 double indexStep;
78};
79
80QHelpGeneratorPrivate::QHelpGeneratorPrivate()
81{
82 query = 0;
83 namespaceId = -1;
84 virtualFolderId = -1;
85}
86
87QHelpGeneratorPrivate::~QHelpGeneratorPrivate()
88{
89}
90
91
92
93/*!
94 \internal
95 \class QHelpGenerator
96 \since 4.4
97 \brief The QHelpGenerator class generates a new
98 Qt compressed help file (.qch).
99
100 The help generator takes a help data structure as
101 input for generating a new Qt compressed help files. Since
102 the generation may takes some time, the generator emits
103 various signals to inform about its current state.
104*/
105
106/*!
107 \fn void QHelpGenerator::statusChanged(const QString &msg)
108
109 This signal is emitted when the generation status changes.
110 The status is basically a specific task like inserting
111 files or building up the keyword index. The parameter
112 \a msg contains the detailed status description.
113*/
114
115/*!
116 \fn void QHelpGenerator::progressChanged(double progress)
117
118 This signal is emitted when the progress changes. The
119 \a progress ranges from 0 to 100.
120*/
121
122/*!
123 \fn void QHelpGenerator::warning(const QString &msg)
124
125 This signal is emitted when a non critical error occurs,
126 e.g. when a referenced file cannot be found. \a msg
127 contains the exact warning message.
128*/
129
130/*!
131 Constructs a new help generator with the give \a parent.
132*/
133QHelpGenerator::QHelpGenerator(QObject *parent)
134 : QObject(parent)
135{
136 d = new QHelpGeneratorPrivate;
137}
138
139/*!
140 Destructs the help generator.
141*/
142QHelpGenerator::~QHelpGenerator()
143{
144 delete d;
145}
146
147/*!
148 Takes the \a helpData and generates a new documentation
149 set from it. The Qt compressed help file is written to \a
150 outputFileName. Returns true on success, otherwise false.
151*/
152bool QHelpGenerator::generate(QHelpDataInterface *helpData,
153 const QString &outputFileName)
154{
155 emit progressChanged(0);
156 d->error.clear();
157 if (!helpData || helpData->namespaceName().isEmpty()) {
158 d->error = tr("Invalid help data!");
159 return false;
160 }
161
162 QString outFileName = outputFileName;
163 if (outFileName.isEmpty()) {
164 d->error = tr("No output file name specified!");
165 return false;
166 }
167
168 QFileInfo fi(outFileName);
169 if (fi.exists()) {
170 if (!fi.dir().remove(fi.fileName())) {
171 d->error = tr("The file %1 cannot be overwritten!").arg(outFileName);
172 return false;
173 }
174 }
175
176 setupProgress(helpData);
177
178 emit statusChanged(tr("Building up file structure..."));
179 bool openingOk = true;
180 {
181 QSqlDatabase db = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), QLatin1String("builder"));
182 db.setDatabaseName(outFileName);
183 openingOk = db.open();
184 if (openingOk)
185 d->query = new QSqlQuery(db);
186 }
187
188 if (!openingOk) {
189 d->error = tr("Cannot open data base file %1!").arg(outFileName);
190 cleanupDB();
191 return false;
192 }
193
194 d->query->exec(QLatin1String("PRAGMA synchronous=OFF"));
195 d->query->exec(QLatin1String("PRAGMA cache_size=3000"));
196
197 addProgress(1.0);
198 createTables();
199 insertFileNotFoundFile();
200 insertMetaData(helpData->metaData());
201
202 if (!registerVirtualFolder(helpData->virtualFolder(), helpData->namespaceName())) {
203 d->error = tr("Cannot register namespace %1!").arg(helpData->namespaceName());
204 cleanupDB();
205 return false;
206 }
207 addProgress(1.0);
208
209 emit statusChanged(tr("Insert custom filters..."));
210 foreach (const QHelpDataCustomFilter &f, helpData->customFilters()) {
211 if (!registerCustomFilter(f.name, f.filterAttributes, true)) {
212 cleanupDB();
213 return false;
214 }
215 }
216 addProgress(1.0);
217
218 int i = 1;
219 QList<QHelpDataFilterSection>::const_iterator it = helpData->filterSections().constBegin();
220 while (it != helpData->filterSections().constEnd()) {
221 emit statusChanged(tr("Insert help data for filter section (%1 of %2)...")
222 .arg(i++).arg(helpData->filterSections().count()));
223 insertFilterAttributes((*it).filterAttributes());
224 QByteArray ba;
225 QDataStream s(&ba, QIODevice::WriteOnly);
226 foreach (QHelpDataContentItem *itm, (*it).contents())
227 writeTree(s, itm, 0);
228 if (!insertFiles((*it).files(), helpData->rootPath(), (*it).filterAttributes())
229 || !insertContents(ba, (*it).filterAttributes())
230 || !insertKeywords((*it).indices(), (*it).filterAttributes())) {
231 cleanupDB();
232 return false;
233 }
234 ++it;
235 }
236
237 cleanupDB();
238 emit progressChanged(100);
239 emit statusChanged(tr("Documentation successfully generated."));
240 return true;
241}
242
243void QHelpGenerator::setupProgress(QHelpDataInterface *helpData)
244{
245 d->progress = 0;
246 d->oldProgress = 0;
247
248 int numberOfFiles = 0;
249 int numberOfIndices = 0;
250 QList<QHelpDataFilterSection>::const_iterator it = helpData->filterSections().constBegin();
251 while (it != helpData->filterSections().constEnd()) {
252 numberOfFiles += (*it).files().count();
253 numberOfIndices += (*it).indices().count();
254 ++it;
255 }
256 // init 2%
257 // filters 1%
258 // contents 10%
259 // files 60%
260 // indices 27%
261 d->contentStep = 10.0/(double)helpData->customFilters().count();
262 d->fileStep = 60.0/(double)numberOfFiles;
263 d->indexStep = 27.0/(double)numberOfIndices;
264}
265
266void QHelpGenerator::addProgress(double step)
267{
268 d->progress += step;
269 if ((d->progress-d->oldProgress) >= 1.0 && d->progress <= 100.0) {
270 d->oldProgress = d->progress;
271 emit progressChanged(ceil(d->progress));
272 }
273}
274
275void QHelpGenerator::cleanupDB()
276{
277 if (d->query) {
278 d->query->clear();
279 delete d->query;
280 d->query = 0;
281 }
282 QSqlDatabase::removeDatabase(QLatin1String("builder"));
283}
284
285void QHelpGenerator::writeTree(QDataStream &s, QHelpDataContentItem *item, int depth)
286{
287 QString fReference = QDir::cleanPath(item->reference());
288 if (fReference.startsWith(QLatin1String("./")))
289 fReference = fReference.mid(2);
290
291 s << depth;
292 s << fReference;
293 s << item->title();
294 foreach (QHelpDataContentItem *i, item->children())
295 writeTree(s, i, depth+1);
296}
297
298/*!
299 Returns the last error message.
300*/
301QString QHelpGenerator::error() const
302{
303 return d->error;
304}
305
306bool QHelpGenerator::createTables()
307{
308 if (!d->query)
309 return false;
310
311 d->query->exec(QLatin1String("SELECT COUNT(*) FROM sqlite_master WHERE TYPE=\'table\'"
312 "AND Name=\'NamespaceTable\'"));
313 d->query->next();
314 if (d->query->value(0).toInt() > 0) {
315 d->error = tr("Some tables already exist!");
316 return false;
317 }
318
319 QStringList tables;
320 tables << QLatin1String("CREATE TABLE NamespaceTable ("
321 "Id INTEGER PRIMARY KEY,"
322 "Name TEXT )")
323 << QLatin1String("CREATE TABLE FilterAttributeTable ("
324 "Id INTEGER PRIMARY KEY, "
325 "Name TEXT )")
326 << QLatin1String("CREATE TABLE FilterNameTable ("
327 "Id INTEGER PRIMARY KEY, "
328 "Name TEXT )")
329 << QLatin1String("CREATE TABLE FilterTable ("
330 "NameId INTEGER, "
331 "FilterAttributeId INTEGER )")
332 << QLatin1String("CREATE TABLE IndexTable ("
333 "Id INTEGER PRIMARY KEY, "
334 "Name TEXT, "
335 "Identifier TEXT, "
336 "NamespaceId INTEGER, "
337 "FileId INTEGER, "
338 "Anchor TEXT )")
339 << QLatin1String("CREATE TABLE IndexItemTable ("
340 "Id INTEGER, "
341 "IndexId INTEGER )")
342 << QLatin1String("CREATE TABLE IndexFilterTable ("
343 "FilterAttributeId INTEGER, "
344 "IndexId INTEGER )")
345 << QLatin1String("CREATE TABLE ContentsTable ("
346 "Id INTEGER PRIMARY KEY, "
347 "NamespaceId INTEGER, "
348 "Data BLOB )")
349 << QLatin1String("CREATE TABLE ContentsFilterTable ("
350 "FilterAttributeId INTEGER, "
351 "ContentsId INTEGER )")
352 << QLatin1String("CREATE TABLE FileAttributeSetTable ("
353 "Id INTEGER, "
354 "FilterAttributeId INTEGER )")
355 << QLatin1String("CREATE TABLE FileDataTable ("
356 "Id INTEGER PRIMARY KEY, "
357 "Data BLOB )")
358 << QLatin1String("CREATE TABLE FileFilterTable ("
359 "FilterAttributeId INTEGER, "
360 "FileId INTEGER )")
361 << QLatin1String("CREATE TABLE FileNameTable ("
362 "FolderId INTEGER, "
363 "Name TEXT, "
364 "FileId INTEGER, "
365 "Title TEXT )")
366 << QLatin1String("CREATE TABLE FolderTable("
367 "Id INTEGER PRIMARY KEY, "
368 "Name Text, "
369 "NamespaceID INTEGER )")
370 << QLatin1String("CREATE TABLE MetaDataTable("
371 "Name Text, "
372 "Value BLOB )");
373
374 foreach (const QString &q, tables) {
375 if (!d->query->exec(q)) {
376 d->error = tr("Cannot create tables!");
377 return false;
378 }
379 }
380
381 d->query->exec(QLatin1String("INSERT INTO MetaDataTable VALUES('qchVersion', '1.0')"));
382
383 d->query->prepare(QLatin1String("INSERT INTO MetaDataTable VALUES('CreationDate', ?)"));
384 d->query->bindValue(0, QDateTime::currentDateTime().toString(Qt::ISODate));
385 d->query->exec();
386
387 return true;
388}
389
390bool QHelpGenerator::insertFileNotFoundFile()
391{
392 if (!d->query)
393 return false;
394
395 d->query->exec(QLatin1String("SELECT id FROM FileNameTable WHERE Name=\'\'"));
396 if (d->query->next() && d->query->isValid())
397 return true;
398
399 d->query->prepare(QLatin1String("INSERT INTO FileDataTable VALUES (Null, ?)"));
400 d->query->bindValue(0, QByteArray());
401 if (!d->query->exec())
402 return false;
403
404 int fileId = d->query->lastInsertId().toInt();
405 d->query->prepare(QLatin1String("INSERT INTO FileNameTable (FolderId, Name, FileId, Title) "
406 " VALUES (0, '', ?, '')"));
407 d->query->bindValue(0, fileId);
408 if (fileId > -1 && d->query->exec()) {
409 d->fileMap.insert(QString(), fileId);
410 return true;
411 }
412 return false;
413}
414
415bool QHelpGenerator::registerVirtualFolder(const QString &folderName, const QString &ns)
416{
417 if (!d->query || folderName.isEmpty() || ns.isEmpty())
418 return false;
419
420 d->query->prepare(QLatin1String("SELECT Id FROM FolderTable WHERE Name=?"));
421 d->query->bindValue(0, folderName);
422 d->query->exec();
423 d->query->next();
424 if (d->query->isValid() && d->query->value(0).toInt() > 0)
425 return true;
426
427 d->namespaceId = -1;
428 d->query->prepare(QLatin1String("SELECT Id FROM NamespaceTable WHERE Name=?"));
429 d->query->bindValue(0, ns);
430 d->query->exec();
431 while (d->query->next()) {
432 d->namespaceId = d->query->value(0).toInt();
433 break;
434 }
435
436 if (d->namespaceId < 0) {
437 d->query->prepare(QLatin1String("INSERT INTO NamespaceTable VALUES(NULL, ?)"));
438 d->query->bindValue(0, ns);
439 if (d->query->exec())
440 d->namespaceId = d->query->lastInsertId().toInt();
441 }
442
443 if (d->namespaceId > 0) {
444 d->query->prepare(QLatin1String("SELECT Id FROM FolderTable WHERE Name=?"));
445 d->query->bindValue(0, folderName);
446 d->query->exec();
447 while (d->query->next())
448 d->virtualFolderId = d->query->value(0).toInt();
449
450 if (d->virtualFolderId > 0)
451 return true;
452
453 d->query->prepare(QLatin1String("INSERT INTO FolderTable (NamespaceId, Name) "
454 "VALUES (?, ?)"));
455 d->query->bindValue(0, d->namespaceId);
456 d->query->bindValue(1, folderName);
457 if (d->query->exec()) {
458 d->virtualFolderId = d->query->lastInsertId().toInt();
459 return d->virtualFolderId > 0;
460 }
461 }
462 d->error = tr("Cannot register virtual folder!");
463 return false;
464}
465
466bool QHelpGenerator::insertFiles(const QStringList &files, const QString &rootPath,
467 const QStringList &filterAttributes)
468{
469 if (!d->query)
470 return false;
471
472 emit statusChanged(tr("Insert files..."));
473 QList<int> filterAtts;
474 foreach (const QString &filterAtt, filterAttributes) {
475 d->query->prepare(QLatin1String("SELECT Id FROM FilterAttributeTable "
476 "WHERE Name=?"));
477 d->query->bindValue(0, filterAtt);
478 d->query->exec();
479 if (d->query->next())
480 filterAtts.append(d->query->value(0).toInt());
481 }
482
483 int filterSetId = -1;
484 d->query->exec(QLatin1String("SELECT MAX(Id) FROM FileAttributeSetTable"));
485 if (d->query->next())
486 filterSetId = d->query->value(0).toInt();
487 if (filterSetId < 0)
488 return false;
489 ++filterSetId;
490 foreach (const int &attId, filterAtts) {
491 d->query->prepare(QLatin1String("INSERT INTO FileAttributeSetTable "
492 "VALUES(?, ?)"));
493 d->query->bindValue(0, filterSetId);
494 d->query->bindValue(1, attId);
495 d->query->exec();
496 }
497
498 int tableFileId = 1;
499 d->query->exec(QLatin1String("SELECT MAX(Id) FROM FileDataTable"));
500 if (d->query->next())
501 tableFileId = d->query->value(0).toInt() + 1;
502
503 QString title;
504 QString charSet;
505 FileNameTableData fileNameData;
506 QList<QByteArray> fileDataList;
507 QMap<int, QSet<int> > tmpFileFilterMap;
508 QList<FileNameTableData> fileNameDataList;
509
510 int i = 0;
511 foreach (const QString &file, files) {
512 const QString fileName = QDir::cleanPath(file);
513 if (fileName.startsWith(QLatin1String("../"))) {
514 emit warning(tr("The referenced file %1 must be inside or within a "
515 "subdirectory of (%2). Skipping it.").arg(fileName).arg(rootPath));
516 continue;
517 }
518
519 QFile fi(rootPath + QDir::separator() + fileName);
520 if (!fi.exists()) {
521 emit warning(tr("The file %1 does not exist! Skipping it.")
522 .arg(QDir::cleanPath(rootPath + QDir::separator() + fileName)));
523 continue;
524 }
525
526 if (!fi.open(QIODevice::ReadOnly)) {
527 emit warning(tr("Cannot open file %1! Skipping it.")
528 .arg(QDir::cleanPath(rootPath + QDir::separator() + fileName)));
529 continue;
530 }
531
532 QByteArray data = fi.readAll();
533 if (fileName.endsWith(QLatin1String(".html"))
534 || fileName.endsWith(QLatin1String(".htm"))) {
535 charSet = QHelpGlobal::codecFromData(data);
536 QTextStream stream(&data);
537 stream.setCodec(QTextCodec::codecForName(charSet.toLatin1().constData()));
538 title = QHelpGlobal::documentTitle(stream.readAll());
539 } else {
540 title = fileName.mid(fileName.lastIndexOf(QLatin1Char('/')) + 1);
541 }
542
543 int fileId = -1;
544 QMap<QString, int>::Iterator fileMapIt = d->fileMap.find(fileName);
545 if (fileMapIt == d->fileMap.end()) {
546 fileDataList.append(qCompress(data));
547
548 fileNameData.name = fileName;
549 fileNameData.fileId = tableFileId;
550 fileNameData.title = title;
551 fileNameDataList.append(fileNameData);
552
553 d->fileMap.insert(fileName, tableFileId);
554 d->fileFilterMap.insert(tableFileId, filterAtts.toSet());
555 tmpFileFilterMap.insert(tableFileId, filterAtts.toSet());
556
557 ++tableFileId;
558 } else {
559 fileId = fileMapIt.value();
560 QSet<int> &fileFilterSet = d->fileFilterMap[fileId];
561 QSet<int> &tmpFileFilterSet = tmpFileFilterMap[fileId];
562 foreach (const int &filter, filterAtts) {
563 if (!fileFilterSet.contains(filter)
564 && !tmpFileFilterSet.contains(filter)) {
565 fileFilterSet.insert(filter);
566 tmpFileFilterSet.insert(filter);
567 }
568 }
569 }
570 }
571
572 if (!tmpFileFilterMap.isEmpty()) {
573 d->query->exec(QLatin1String("BEGIN"));
574 QMap<int, QSet<int> >::const_iterator it = tmpFileFilterMap.constBegin();
575 while (it != tmpFileFilterMap.constEnd()) {
576 QSet<int>::const_iterator si = it.value().constBegin();
577 while (si != it.value().constEnd()) {
578 d->query->prepare(QLatin1String("INSERT INTO FileFilterTable "
579 "VALUES(?, ?)"));
580 d->query->bindValue(0, *si);
581 d->query->bindValue(1, it.key());
582 d->query->exec();
583 ++si;
584 }
585 ++it;
586 }
587
588 QList<QByteArray>::const_iterator fileIt = fileDataList.constBegin();
589 while (fileIt != fileDataList.constEnd()) {
590 d->query->prepare(QLatin1String("INSERT INTO FileDataTable VALUES "
591 "(Null, ?)"));
592 d->query->bindValue(0, *fileIt);
593 d->query->exec();
594 ++fileIt;
595 if (++i%20 == 0)
596 addProgress(d->fileStep*20.0);
597 }
598
599 QList<FileNameTableData>::const_iterator fileNameIt =
600 fileNameDataList.constBegin();
601 while (fileNameIt != fileNameDataList.constEnd()) {
602 d->query->prepare(QLatin1String("INSERT INTO FileNameTable "
603 "(FolderId, Name, FileId, Title) VALUES (?, ?, ?, ?)"));
604 d->query->bindValue(0, 1);
605 d->query->bindValue(1, (*fileNameIt).name);
606 d->query->bindValue(2, (*fileNameIt).fileId);
607 d->query->bindValue(3, (*fileNameIt).title);
608 d->query->exec();
609 ++fileNameIt;
610 }
611 d->query->exec(QLatin1String("COMMIT"));
612 }
613
614 d->query->exec(QLatin1String("SELECT MAX(Id) FROM FileDataTable"));
615 if (d->query->next()
616 && d->query->value(0).toInt() == tableFileId-1) {
617 addProgress(d->fileStep*(i%20));
618 return true;
619 }
620 return false;
621}
622
623bool QHelpGenerator::registerCustomFilter(const QString &filterName,
624 const QStringList &filterAttribs, bool forceUpdate)
625{
626 if (!d->query)
627 return false;
628
629 d->query->exec(QLatin1String("SELECT Id, Name FROM FilterAttributeTable"));
630 QStringList idsToInsert = filterAttribs;
631 QMap<QString, int> attributeMap;
632 while (d->query->next()) {
633 attributeMap.insert(d->query->value(1).toString(),
634 d->query->value(0).toInt());
635 idsToInsert.removeAll(d->query->value(1).toString());
636 }
637
638 foreach (const QString &id, idsToInsert) {
639 d->query->prepare(QLatin1String("INSERT INTO FilterAttributeTable VALUES(NULL, ?)"));
640 d->query->bindValue(0, id);
641 d->query->exec();
642 attributeMap.insert(id, d->query->lastInsertId().toInt());
643 }
644
645 int nameId = -1;
646 d->query->prepare(QLatin1String("SELECT Id FROM FilterNameTable WHERE Name=?"));
647 d->query->bindValue(0, filterName);
648 d->query->exec();
649 while (d->query->next()) {
650 nameId = d->query->value(0).toInt();
651 break;
652 }
653
654 if (nameId < 0) {
655 d->query->prepare(QLatin1String("INSERT INTO FilterNameTable VALUES(NULL, ?)"));
656 d->query->bindValue(0, filterName);
657 if (d->query->exec())
658 nameId = d->query->lastInsertId().toInt();
659 } else if (!forceUpdate) {
660 d->error = tr("The filter %1 is already registered!").arg(filterName);
661 return false;
662 }
663
664 if (nameId < 0) {
665 d->error = tr("Cannot register filter %1!").arg(filterName);
666 return false;
667 }
668
669 d->query->prepare(QLatin1String("DELETE FROM FilterTable WHERE NameId=?"));
670 d->query->bindValue(0, nameId);
671 d->query->exec();
672
673 foreach (const QString &att, filterAttribs) {
674 d->query->prepare(QLatin1String("INSERT INTO FilterTable VALUES(?, ?)"));
675 d->query->bindValue(0, nameId);
676 d->query->bindValue(1, attributeMap[att]);
677 if (!d->query->exec())
678 return false;
679 }
680 return true;
681}
682
683bool QHelpGenerator::insertKeywords(const QList<QHelpDataIndexItem> &keywords,
684 const QStringList &filterAttributes)
685{
686 if (!d->query)
687 return false;
688
689 emit statusChanged(tr("Insert indices..."));
690 int indexId = 1;
691 d->query->exec(QLatin1String("SELECT MAX(Id) FROM IndexTable"));
692 if (d->query->next())
693 indexId = d->query->value(0).toInt() + 1;
694
695 QList<int> filterAtts;
696 foreach (const QString &filterAtt, filterAttributes) {
697 d->query->prepare(QLatin1String("SELECT Id FROM FilterAttributeTable WHERE Name=?"));
698 d->query->bindValue(0, filterAtt);
699 d->query->exec();
700 if (d->query->next())
701 filterAtts.append(d->query->value(0).toInt());
702 }
703
704 int pos = -1;
705 QString fileName;
706 QString anchor;
707 QString fName;
708 int fileId = 1;
709 QList<int> indexFilterTable;
710
711 int i = 0;
712 d->query->exec(QLatin1String("BEGIN"));
713 QSet<QString> indices;
714 foreach (const QHelpDataIndexItem &itm, keywords) {
715 // Identical ids make no sense and just confuse the Assistant user,
716 // so we ignore all repetitions.
717 if (indices.contains(itm.identifier))
718 continue;
719
720 // Still empty ids should be ignored, as otherwise we will include only
721 // the first keyword with an empty id.
722 if (!itm.identifier.isEmpty())
723 indices.insert(itm.identifier);
724
725 pos = itm.reference.indexOf(QLatin1Char('#'));
726 fileName = itm.reference.left(pos);
727 if (pos > -1)
728 anchor = itm.reference.mid(pos+1);
729 else
730 anchor.clear();
731
732 fName = QDir::cleanPath(fileName);
733 if (fName.startsWith(QLatin1String("./")))
734 fName = fName.mid(2);
735
736 QMap<QString, int>::ConstIterator it = d->fileMap.find(fName);
737 if (it != d->fileMap.end())
738 fileId = it.value();
739 else
740 fileId = 1;
741
742 d->query->prepare(QLatin1String("INSERT INTO IndexTable (Name, Identifier, NamespaceId, FileId, Anchor) "
743 "VALUES(?, ?, ?, ?, ?)"));
744 d->query->bindValue(0, itm.name);
745 d->query->bindValue(1, itm.identifier);
746 d->query->bindValue(2, d->namespaceId);
747 d->query->bindValue(3, fileId);
748 d->query->bindValue(4, anchor);
749 d->query->exec();
750
751 indexFilterTable.append(indexId++);
752 if (++i%100 == 0)
753 addProgress(d->indexStep*100.0);
754 }
755 d->query->exec(QLatin1String("COMMIT"));
756
757 d->query->exec(QLatin1String("BEGIN"));
758 foreach (int idx, indexFilterTable) {
759 foreach (int a, filterAtts) {
760 d->query->prepare(QLatin1String("INSERT INTO IndexFilterTable (FilterAttributeId, IndexId) "
761 "VALUES(?, ?)"));
762 d->query->bindValue(0, a);
763 d->query->bindValue(1, idx);
764 d->query->exec();
765 }
766 }
767 d->query->exec(QLatin1String("COMMIT"));
768
769 d->query->exec(QLatin1String("SELECT COUNT(Id) FROM IndexTable"));
770 if (d->query->next() && d->query->value(0).toInt() >= indices.count())
771 return true;
772 return false;
773}
774
775bool QHelpGenerator::insertContents(const QByteArray &ba,
776 const QStringList &filterAttributes)
777{
778 if (!d->query)
779 return false;
780
781 emit statusChanged(tr("Insert contents..."));
782 d->query->prepare(QLatin1String("INSERT INTO ContentsTable (NamespaceId, Data) "
783 "VALUES(?, ?)"));
784 d->query->bindValue(0, d->namespaceId);
785 d->query->bindValue(1, ba);
786 d->query->exec();
787 int contentId = d->query->lastInsertId().toInt();
788 if (contentId < 1) {
789 d->error = tr("Cannot insert contents!");
790 return false;
791 }
792
793 // associate the filter attributes
794 foreach (const QString &filterAtt, filterAttributes) {
795 d->query->prepare(QLatin1String("INSERT INTO ContentsFilterTable (FilterAttributeId, ContentsId) "
796 "SELECT Id, ? FROM FilterAttributeTable WHERE Name=?"));
797 d->query->bindValue(0, contentId);
798 d->query->bindValue(1, filterAtt);
799 d->query->exec();
800 if (!d->query->isActive()) {
801 d->error = tr("Cannot register contents!");
802 return false;
803 }
804 }
805 addProgress(d->contentStep);
806 return true;
807}
808
809bool QHelpGenerator::insertFilterAttributes(const QStringList &attributes)
810{
811 if (!d->query)
812 return false;
813
814 d->query->exec(QLatin1String("SELECT Name FROM FilterAttributeTable"));
815 QSet<QString> atts;
816 while (d->query->next())
817 atts.insert(d->query->value(0).toString());
818
819 foreach (const QString &s, attributes) {
820 if (!atts.contains(s)) {
821 d->query->prepare(QLatin1String("INSERT INTO FilterAttributeTable VALUES(NULL, ?)"));
822 d->query->bindValue(0, s);
823 d->query->exec();
824 }
825 }
826 return true;
827}
828
829bool QHelpGenerator::insertMetaData(const QMap<QString, QVariant> &metaData)
830{
831 if (!d->query)
832 return false;
833
834 QMap<QString, QVariant>::const_iterator it = metaData.constBegin();
835 while (it != metaData.constEnd()) {
836 d->query->prepare(QLatin1String("INSERT INTO MetaDataTable VALUES(?, ?)"));
837 d->query->bindValue(0, it.key());
838 d->query->bindValue(1, it.value());
839 d->query->exec();
840 ++it;
841 }
842 return true;
843}
844
845bool QHelpGenerator::checkLinks(const QHelpDataInterface &helpData)
846{
847 /*
848 * Step 1: Gather the canoncal file paths of all files in the project.
849 * We use a set, because there will be a lot of look-ups.
850 */
851 QSet<QString> files;
852 foreach (const QHelpDataFilterSection &filterSection, helpData.filterSections()) {
853 foreach (const QString &file, filterSection.files()) {
854 QFileInfo fileInfo(helpData.rootPath() + QDir::separator() + file);
855 const QString &canonicalFileName = fileInfo.canonicalFilePath();
856 if (!fileInfo.exists())
857 emit warning(tr("File '%1' does not exist.").arg(file));
858 else
859 files.insert(canonicalFileName);
860 }
861 }
862
863 /*
864 * Step 2: Check the hypertext and image references of all HTML files.
865 * Note that we don't parse the files, but simply grep for the
866 * respective HTML elements. Therefore. contents that are e.g.
867 * commented out can cause false warning.
868 */
869 bool allLinksOk = true;
870 foreach (const QString &fileName, files) {
871 if (!fileName.endsWith(QLatin1String("html"))
872 && !fileName.endsWith(QLatin1String("htm")))
873 continue;
874 QFile htmlFile(fileName);
875 if (!htmlFile.open(QIODevice::ReadOnly)) {
876 emit warning(tr("File '%1' cannot be opened.").arg(fileName));
877 continue;
878 }
879 const QRegExp linkPattern(QLatin1String("<(?:a href|img src)=\"?([^#\">]+)[#\">]"));
880 QTextStream stream(&htmlFile);
881 const QString codec = QHelpGlobal::codecFromData(htmlFile.read(1000));
882 stream.setCodec(QTextCodec::codecForName(codec.toLatin1().constData()));
883 const QString &content = stream.readAll();
884 QStringList invalidLinks;
885 for (int pos = linkPattern.indexIn(content); pos != -1;
886 pos = linkPattern.indexIn(content, pos + 1)) {
887 const QString& linkedFileName = linkPattern.cap(1);
888 if (linkedFileName.contains(QLatin1String("://")))
889 continue;
890 const QString curDir = QFileInfo(fileName).dir().path();
891 const QString &canonicalLinkedFileName =
892 QFileInfo(curDir + QDir::separator() + linkedFileName).canonicalFilePath();
893 if (!files.contains(canonicalLinkedFileName)
894 && !invalidLinks.contains(canonicalLinkedFileName)) {
895 emit warning(tr("File '%1' contains an invalid link to file '%2'").
896 arg(fileName).arg(linkedFileName));
897 allLinksOk = false;
898 invalidLinks.append(canonicalLinkedFileName);
899 }
900 }
901 }
902
903 if (!allLinksOk)
904 d->error = tr("Invalid links in HTML files.");
905 return allLinksOk;
906}
907
908QT_END_NAMESPACE
909
Note: See TracBrowser for help on using the repository browser.