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 |
|
---|
56 | QT_BEGIN_NAMESPACE
|
---|
57 |
|
---|
58 | class QHelpGeneratorPrivate
|
---|
59 | {
|
---|
60 | public:
|
---|
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 |
|
---|
80 | QHelpGeneratorPrivate::QHelpGeneratorPrivate()
|
---|
81 | {
|
---|
82 | query = 0;
|
---|
83 | namespaceId = -1;
|
---|
84 | virtualFolderId = -1;
|
---|
85 | }
|
---|
86 |
|
---|
87 | QHelpGeneratorPrivate::~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 | */
|
---|
133 | QHelpGenerator::QHelpGenerator(QObject *parent)
|
---|
134 | : QObject(parent)
|
---|
135 | {
|
---|
136 | d = new QHelpGeneratorPrivate;
|
---|
137 | }
|
---|
138 |
|
---|
139 | /*!
|
---|
140 | Destructs the help generator.
|
---|
141 | */
|
---|
142 | QHelpGenerator::~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 | */
|
---|
152 | bool 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 |
|
---|
243 | void 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 |
|
---|
266 | void 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 |
|
---|
275 | void 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 |
|
---|
285 | void 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 | */
|
---|
301 | QString QHelpGenerator::error() const
|
---|
302 | {
|
---|
303 | return d->error;
|
---|
304 | }
|
---|
305 |
|
---|
306 | bool 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 |
|
---|
390 | bool 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 |
|
---|
415 | bool 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 |
|
---|
466 | bool 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 |
|
---|
623 | bool 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 |
|
---|
683 | bool 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 |
|
---|
775 | bool 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 |
|
---|
809 | bool 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 |
|
---|
829 | bool 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 |
|
---|
845 | bool 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 |
|
---|
908 | QT_END_NAMESPACE
|
---|
909 |
|
---|