summaryrefslogtreecommitdiff
path: root/src/assistant/help/qhelpgenerator.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/assistant/help/qhelpgenerator.cpp')
-rw-r--r--src/assistant/help/qhelpgenerator.cpp909
1 files changed, 909 insertions, 0 deletions
diff --git a/src/assistant/help/qhelpgenerator.cpp b/src/assistant/help/qhelpgenerator.cpp
new file mode 100644
index 000000000..c605cc960
--- /dev/null
+++ b/src/assistant/help/qhelpgenerator.cpp
@@ -0,0 +1,909 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the Qt Assistant of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qhelpgenerator_p.h"
+#include "qhelpdatainterface_p.h"
+
+#include <math.h>
+#include <QtCore/QFile>
+#include <QtCore/QFileInfo>
+#include <QtCore/QDir>
+#include <QtCore/QDebug>
+#include <QtCore/QSet>
+#include <QtCore/QVariant>
+#include <QtCore/QDateTime>
+#include <QtCore/QTextCodec>
+#include <QtSql/QSqlQuery>
+
+QT_BEGIN_NAMESPACE
+
+class QHelpGeneratorPrivate
+{
+public:
+ QHelpGeneratorPrivate();
+ ~QHelpGeneratorPrivate();
+
+ QString error;
+ QSqlQuery *query;
+
+ int namespaceId;
+ int virtualFolderId;
+
+ QMap<QString, int> fileMap;
+ QMap<int, QSet<int> > fileFilterMap;
+
+ double progress;
+ double oldProgress;
+ double contentStep;
+ double fileStep;
+ double indexStep;
+};
+
+QHelpGeneratorPrivate::QHelpGeneratorPrivate()
+{
+ query = 0;
+ namespaceId = -1;
+ virtualFolderId = -1;
+}
+
+QHelpGeneratorPrivate::~QHelpGeneratorPrivate()
+{
+}
+
+
+
+/*!
+ \internal
+ \class QHelpGenerator
+ \since 4.4
+ \brief The QHelpGenerator class generates a new
+ Qt compressed help file (.qch).
+
+ The help generator takes a help data structure as
+ input for generating a new Qt compressed help files. Since
+ the generation may takes some time, the generator emits
+ various signals to inform about its current state.
+*/
+
+/*!
+ \fn void QHelpGenerator::statusChanged(const QString &msg)
+
+ This signal is emitted when the generation status changes.
+ The status is basically a specific task like inserting
+ files or building up the keyword index. The parameter
+ \a msg contains the detailed status description.
+*/
+
+/*!
+ \fn void QHelpGenerator::progressChanged(double progress)
+
+ This signal is emitted when the progress changes. The
+ \a progress ranges from 0 to 100.
+*/
+
+/*!
+ \fn void QHelpGenerator::warning(const QString &msg)
+
+ This signal is emitted when a non critical error occurs,
+ e.g. when a referenced file cannot be found. \a msg
+ contains the exact warning message.
+*/
+
+/*!
+ Constructs a new help generator with the give \a parent.
+*/
+QHelpGenerator::QHelpGenerator(QObject *parent)
+ : QObject(parent)
+{
+ d = new QHelpGeneratorPrivate;
+}
+
+/*!
+ Destructs the help generator.
+*/
+QHelpGenerator::~QHelpGenerator()
+{
+ delete d;
+}
+
+/*!
+ Takes the \a helpData and generates a new documentation
+ set from it. The Qt compressed help file is written to \a
+ outputFileName. Returns true on success, otherwise false.
+*/
+bool QHelpGenerator::generate(QHelpDataInterface *helpData,
+ const QString &outputFileName)
+{
+ emit progressChanged(0);
+ d->error.clear();
+ if (!helpData || helpData->namespaceName().isEmpty()) {
+ d->error = tr("Invalid help data!");
+ return false;
+ }
+
+ QString outFileName = outputFileName;
+ if (outFileName.isEmpty()) {
+ d->error = tr("No output file name specified!");
+ return false;
+ }
+
+ QFileInfo fi(outFileName);
+ if (fi.exists()) {
+ if (!fi.dir().remove(fi.fileName())) {
+ d->error = tr("The file %1 cannot be overwritten!").arg(outFileName);
+ return false;
+ }
+ }
+
+ setupProgress(helpData);
+
+ emit statusChanged(tr("Building up file structure..."));
+ bool openingOk = true;
+ {
+ QSqlDatabase db = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), QLatin1String("builder"));
+ db.setDatabaseName(outFileName);
+ openingOk = db.open();
+ if (openingOk)
+ d->query = new QSqlQuery(db);
+ }
+
+ if (!openingOk) {
+ d->error = tr("Cannot open data base file %1!").arg(outFileName);
+ cleanupDB();
+ return false;
+ }
+
+ d->query->exec(QLatin1String("PRAGMA synchronous=OFF"));
+ d->query->exec(QLatin1String("PRAGMA cache_size=3000"));
+
+ addProgress(1.0);
+ createTables();
+ insertFileNotFoundFile();
+ insertMetaData(helpData->metaData());
+
+ if (!registerVirtualFolder(helpData->virtualFolder(), helpData->namespaceName())) {
+ d->error = tr("Cannot register namespace %1!").arg(helpData->namespaceName());
+ cleanupDB();
+ return false;
+ }
+ addProgress(1.0);
+
+ emit statusChanged(tr("Insert custom filters..."));
+ foreach (const QHelpDataCustomFilter &f, helpData->customFilters()) {
+ if (!registerCustomFilter(f.name, f.filterAttributes, true)) {
+ cleanupDB();
+ return false;
+ }
+ }
+ addProgress(1.0);
+
+ int i = 1;
+ QList<QHelpDataFilterSection>::const_iterator it = helpData->filterSections().constBegin();
+ while (it != helpData->filterSections().constEnd()) {
+ emit statusChanged(tr("Insert help data for filter section (%1 of %2)...")
+ .arg(i++).arg(helpData->filterSections().count()));
+ insertFilterAttributes((*it).filterAttributes());
+ QByteArray ba;
+ QDataStream s(&ba, QIODevice::WriteOnly);
+ foreach (QHelpDataContentItem *itm, (*it).contents())
+ writeTree(s, itm, 0);
+ if (!insertFiles((*it).files(), helpData->rootPath(), (*it).filterAttributes())
+ || !insertContents(ba, (*it).filterAttributes())
+ || !insertKeywords((*it).indices(), (*it).filterAttributes())) {
+ cleanupDB();
+ return false;
+ }
+ ++it;
+ }
+
+ cleanupDB();
+ emit progressChanged(100);
+ emit statusChanged(tr("Documentation successfully generated."));
+ return true;
+}
+
+void QHelpGenerator::setupProgress(QHelpDataInterface *helpData)
+{
+ d->progress = 0;
+ d->oldProgress = 0;
+
+ int numberOfFiles = 0;
+ int numberOfIndices = 0;
+ QList<QHelpDataFilterSection>::const_iterator it = helpData->filterSections().constBegin();
+ while (it != helpData->filterSections().constEnd()) {
+ numberOfFiles += (*it).files().count();
+ numberOfIndices += (*it).indices().count();
+ ++it;
+ }
+ // init 2%
+ // filters 1%
+ // contents 10%
+ // files 60%
+ // indices 27%
+ d->contentStep = 10.0/(double)helpData->customFilters().count();
+ d->fileStep = 60.0/(double)numberOfFiles;
+ d->indexStep = 27.0/(double)numberOfIndices;
+}
+
+void QHelpGenerator::addProgress(double step)
+{
+ d->progress += step;
+ if ((d->progress-d->oldProgress) >= 1.0 && d->progress <= 100.0) {
+ d->oldProgress = d->progress;
+ emit progressChanged(ceil(d->progress));
+ }
+}
+
+void QHelpGenerator::cleanupDB()
+{
+ if (d->query) {
+ d->query->clear();
+ delete d->query;
+ d->query = 0;
+ }
+ QSqlDatabase::removeDatabase(QLatin1String("builder"));
+}
+
+void QHelpGenerator::writeTree(QDataStream &s, QHelpDataContentItem *item, int depth)
+{
+ QString fReference = QDir::cleanPath(item->reference());
+ if (fReference.startsWith(QLatin1String("./")))
+ fReference = fReference.mid(2);
+
+ s << depth;
+ s << fReference;
+ s << item->title();
+ foreach (QHelpDataContentItem *i, item->children())
+ writeTree(s, i, depth+1);
+}
+
+/*!
+ Returns the last error message.
+*/
+QString QHelpGenerator::error() const
+{
+ return d->error;
+}
+
+bool QHelpGenerator::createTables()
+{
+ if (!d->query)
+ return false;
+
+ d->query->exec(QLatin1String("SELECT COUNT(*) FROM sqlite_master WHERE TYPE=\'table\'"
+ "AND Name=\'NamespaceTable\'"));
+ d->query->next();
+ if (d->query->value(0).toInt() > 0) {
+ d->error = tr("Some tables already exist!");
+ return false;
+ }
+
+ QStringList tables;
+ tables << QLatin1String("CREATE TABLE NamespaceTable ("
+ "Id INTEGER PRIMARY KEY,"
+ "Name TEXT )")
+ << QLatin1String("CREATE TABLE FilterAttributeTable ("
+ "Id INTEGER PRIMARY KEY, "
+ "Name TEXT )")
+ << QLatin1String("CREATE TABLE FilterNameTable ("
+ "Id INTEGER PRIMARY KEY, "
+ "Name TEXT )")
+ << QLatin1String("CREATE TABLE FilterTable ("
+ "NameId INTEGER, "
+ "FilterAttributeId INTEGER )")
+ << QLatin1String("CREATE TABLE IndexTable ("
+ "Id INTEGER PRIMARY KEY, "
+ "Name TEXT, "
+ "Identifier TEXT, "
+ "NamespaceId INTEGER, "
+ "FileId INTEGER, "
+ "Anchor TEXT )")
+ << QLatin1String("CREATE TABLE IndexItemTable ("
+ "Id INTEGER, "
+ "IndexId INTEGER )")
+ << QLatin1String("CREATE TABLE IndexFilterTable ("
+ "FilterAttributeId INTEGER, "
+ "IndexId INTEGER )")
+ << QLatin1String("CREATE TABLE ContentsTable ("
+ "Id INTEGER PRIMARY KEY, "
+ "NamespaceId INTEGER, "
+ "Data BLOB )")
+ << QLatin1String("CREATE TABLE ContentsFilterTable ("
+ "FilterAttributeId INTEGER, "
+ "ContentsId INTEGER )")
+ << QLatin1String("CREATE TABLE FileAttributeSetTable ("
+ "Id INTEGER, "
+ "FilterAttributeId INTEGER )")
+ << QLatin1String("CREATE TABLE FileDataTable ("
+ "Id INTEGER PRIMARY KEY, "
+ "Data BLOB )")
+ << QLatin1String("CREATE TABLE FileFilterTable ("
+ "FilterAttributeId INTEGER, "
+ "FileId INTEGER )")
+ << QLatin1String("CREATE TABLE FileNameTable ("
+ "FolderId INTEGER, "
+ "Name TEXT, "
+ "FileId INTEGER, "
+ "Title TEXT )")
+ << QLatin1String("CREATE TABLE FolderTable("
+ "Id INTEGER PRIMARY KEY, "
+ "Name Text, "
+ "NamespaceID INTEGER )")
+ << QLatin1String("CREATE TABLE MetaDataTable("
+ "Name Text, "
+ "Value BLOB )");
+
+ foreach (const QString &q, tables) {
+ if (!d->query->exec(q)) {
+ d->error = tr("Cannot create tables!");
+ return false;
+ }
+ }
+
+ d->query->exec(QLatin1String("INSERT INTO MetaDataTable VALUES('qchVersion', '1.0')"));
+
+ d->query->prepare(QLatin1String("INSERT INTO MetaDataTable VALUES('CreationDate', ?)"));
+ d->query->bindValue(0, QDateTime::currentDateTime().toString(Qt::ISODate));
+ d->query->exec();
+
+ return true;
+}
+
+bool QHelpGenerator::insertFileNotFoundFile()
+{
+ if (!d->query)
+ return false;
+
+ d->query->exec(QLatin1String("SELECT id FROM FileNameTable WHERE Name=\'\'"));
+ if (d->query->next() && d->query->isValid())
+ return true;
+
+ d->query->prepare(QLatin1String("INSERT INTO FileDataTable VALUES (Null, ?)"));
+ d->query->bindValue(0, QByteArray());
+ if (!d->query->exec())
+ return false;
+
+ int fileId = d->query->lastInsertId().toInt();
+ d->query->prepare(QLatin1String("INSERT INTO FileNameTable (FolderId, Name, FileId, Title) "
+ " VALUES (0, '', ?, '')"));
+ d->query->bindValue(0, fileId);
+ if (fileId > -1 && d->query->exec()) {
+ d->fileMap.insert(QString(), fileId);
+ return true;
+ }
+ return false;
+}
+
+bool QHelpGenerator::registerVirtualFolder(const QString &folderName, const QString &ns)
+{
+ if (!d->query || folderName.isEmpty() || ns.isEmpty())
+ return false;
+
+ d->query->prepare(QLatin1String("SELECT Id FROM FolderTable WHERE Name=?"));
+ d->query->bindValue(0, folderName);
+ d->query->exec();
+ d->query->next();
+ if (d->query->isValid() && d->query->value(0).toInt() > 0)
+ return true;
+
+ d->namespaceId = -1;
+ d->query->prepare(QLatin1String("SELECT Id FROM NamespaceTable WHERE Name=?"));
+ d->query->bindValue(0, ns);
+ d->query->exec();
+ while (d->query->next()) {
+ d->namespaceId = d->query->value(0).toInt();
+ break;
+ }
+
+ if (d->namespaceId < 0) {
+ d->query->prepare(QLatin1String("INSERT INTO NamespaceTable VALUES(NULL, ?)"));
+ d->query->bindValue(0, ns);
+ if (d->query->exec())
+ d->namespaceId = d->query->lastInsertId().toInt();
+ }
+
+ if (d->namespaceId > 0) {
+ d->query->prepare(QLatin1String("SELECT Id FROM FolderTable WHERE Name=?"));
+ d->query->bindValue(0, folderName);
+ d->query->exec();
+ while (d->query->next())
+ d->virtualFolderId = d->query->value(0).toInt();
+
+ if (d->virtualFolderId > 0)
+ return true;
+
+ d->query->prepare(QLatin1String("INSERT INTO FolderTable (NamespaceId, Name) "
+ "VALUES (?, ?)"));
+ d->query->bindValue(0, d->namespaceId);
+ d->query->bindValue(1, folderName);
+ if (d->query->exec()) {
+ d->virtualFolderId = d->query->lastInsertId().toInt();
+ return d->virtualFolderId > 0;
+ }
+ }
+ d->error = tr("Cannot register virtual folder!");
+ return false;
+}
+
+bool QHelpGenerator::insertFiles(const QStringList &files, const QString &rootPath,
+ const QStringList &filterAttributes)
+{
+ if (!d->query)
+ return false;
+
+ emit statusChanged(tr("Insert files..."));
+ QList<int> filterAtts;
+ foreach (const QString &filterAtt, filterAttributes) {
+ d->query->prepare(QLatin1String("SELECT Id FROM FilterAttributeTable "
+ "WHERE Name=?"));
+ d->query->bindValue(0, filterAtt);
+ d->query->exec();
+ if (d->query->next())
+ filterAtts.append(d->query->value(0).toInt());
+ }
+
+ int filterSetId = -1;
+ d->query->exec(QLatin1String("SELECT MAX(Id) FROM FileAttributeSetTable"));
+ if (d->query->next())
+ filterSetId = d->query->value(0).toInt();
+ if (filterSetId < 0)
+ return false;
+ ++filterSetId;
+ foreach (const int &attId, filterAtts) {
+ d->query->prepare(QLatin1String("INSERT INTO FileAttributeSetTable "
+ "VALUES(?, ?)"));
+ d->query->bindValue(0, filterSetId);
+ d->query->bindValue(1, attId);
+ d->query->exec();
+ }
+
+ int tableFileId = 1;
+ d->query->exec(QLatin1String("SELECT MAX(Id) FROM FileDataTable"));
+ if (d->query->next())
+ tableFileId = d->query->value(0).toInt() + 1;
+
+ QString title;
+ QString charSet;
+ FileNameTableData fileNameData;
+ QList<QByteArray> fileDataList;
+ QMap<int, QSet<int> > tmpFileFilterMap;
+ QList<FileNameTableData> fileNameDataList;
+
+ int i = 0;
+ foreach (const QString &file, files) {
+ const QString fileName = QDir::cleanPath(file);
+ if (fileName.startsWith(QLatin1String("../"))) {
+ emit warning(tr("The referenced file %1 must be inside or within a "
+ "subdirectory of (%2). Skipping it.").arg(fileName).arg(rootPath));
+ continue;
+ }
+
+ QFile fi(rootPath + QDir::separator() + fileName);
+ if (!fi.exists()) {
+ emit warning(tr("The file %1 does not exist! Skipping it.")
+ .arg(QDir::cleanPath(rootPath + QDir::separator() + fileName)));
+ continue;
+ }
+
+ if (!fi.open(QIODevice::ReadOnly)) {
+ emit warning(tr("Cannot open file %1! Skipping it.")
+ .arg(QDir::cleanPath(rootPath + QDir::separator() + fileName)));
+ continue;
+ }
+
+ QByteArray data = fi.readAll();
+ if (fileName.endsWith(QLatin1String(".html"))
+ || fileName.endsWith(QLatin1String(".htm"))) {
+ charSet = QHelpGlobal::codecFromData(data);
+ QTextStream stream(&data);
+ stream.setCodec(QTextCodec::codecForName(charSet.toLatin1().constData()));
+ title = QHelpGlobal::documentTitle(stream.readAll());
+ } else {
+ title = fileName.mid(fileName.lastIndexOf(QLatin1Char('/')) + 1);
+ }
+
+ int fileId = -1;
+ QMap<QString, int>::Iterator fileMapIt = d->fileMap.find(fileName);
+ if (fileMapIt == d->fileMap.end()) {
+ fileDataList.append(qCompress(data));
+
+ fileNameData.name = fileName;
+ fileNameData.fileId = tableFileId;
+ fileNameData.title = title;
+ fileNameDataList.append(fileNameData);
+
+ d->fileMap.insert(fileName, tableFileId);
+ d->fileFilterMap.insert(tableFileId, filterAtts.toSet());
+ tmpFileFilterMap.insert(tableFileId, filterAtts.toSet());
+
+ ++tableFileId;
+ } else {
+ fileId = fileMapIt.value();
+ QSet<int> &fileFilterSet = d->fileFilterMap[fileId];
+ QSet<int> &tmpFileFilterSet = tmpFileFilterMap[fileId];
+ foreach (const int &filter, filterAtts) {
+ if (!fileFilterSet.contains(filter)
+ && !tmpFileFilterSet.contains(filter)) {
+ fileFilterSet.insert(filter);
+ tmpFileFilterSet.insert(filter);
+ }
+ }
+ }
+ }
+
+ if (!tmpFileFilterMap.isEmpty()) {
+ d->query->exec(QLatin1String("BEGIN"));
+ QMap<int, QSet<int> >::const_iterator it = tmpFileFilterMap.constBegin();
+ while (it != tmpFileFilterMap.constEnd()) {
+ QSet<int>::const_iterator si = it.value().constBegin();
+ while (si != it.value().constEnd()) {
+ d->query->prepare(QLatin1String("INSERT INTO FileFilterTable "
+ "VALUES(?, ?)"));
+ d->query->bindValue(0, *si);
+ d->query->bindValue(1, it.key());
+ d->query->exec();
+ ++si;
+ }
+ ++it;
+ }
+
+ QList<QByteArray>::const_iterator fileIt = fileDataList.constBegin();
+ while (fileIt != fileDataList.constEnd()) {
+ d->query->prepare(QLatin1String("INSERT INTO FileDataTable VALUES "
+ "(Null, ?)"));
+ d->query->bindValue(0, *fileIt);
+ d->query->exec();
+ ++fileIt;
+ if (++i%20 == 0)
+ addProgress(d->fileStep*20.0);
+ }
+
+ QList<FileNameTableData>::const_iterator fileNameIt =
+ fileNameDataList.constBegin();
+ while (fileNameIt != fileNameDataList.constEnd()) {
+ d->query->prepare(QLatin1String("INSERT INTO FileNameTable "
+ "(FolderId, Name, FileId, Title) VALUES (?, ?, ?, ?)"));
+ d->query->bindValue(0, 1);
+ d->query->bindValue(1, (*fileNameIt).name);
+ d->query->bindValue(2, (*fileNameIt).fileId);
+ d->query->bindValue(3, (*fileNameIt).title);
+ d->query->exec();
+ ++fileNameIt;
+ }
+ d->query->exec(QLatin1String("COMMIT"));
+ }
+
+ d->query->exec(QLatin1String("SELECT MAX(Id) FROM FileDataTable"));
+ if (d->query->next()
+ && d->query->value(0).toInt() == tableFileId-1) {
+ addProgress(d->fileStep*(i%20));
+ return true;
+ }
+ return false;
+}
+
+bool QHelpGenerator::registerCustomFilter(const QString &filterName,
+ const QStringList &filterAttribs, bool forceUpdate)
+{
+ if (!d->query)
+ return false;
+
+ d->query->exec(QLatin1String("SELECT Id, Name FROM FilterAttributeTable"));
+ QStringList idsToInsert = filterAttribs;
+ QMap<QString, int> attributeMap;
+ while (d->query->next()) {
+ attributeMap.insert(d->query->value(1).toString(),
+ d->query->value(0).toInt());
+ idsToInsert.removeAll(d->query->value(1).toString());
+ }
+
+ foreach (const QString &id, idsToInsert) {
+ d->query->prepare(QLatin1String("INSERT INTO FilterAttributeTable VALUES(NULL, ?)"));
+ d->query->bindValue(0, id);
+ d->query->exec();
+ attributeMap.insert(id, d->query->lastInsertId().toInt());
+ }
+
+ int nameId = -1;
+ d->query->prepare(QLatin1String("SELECT Id FROM FilterNameTable WHERE Name=?"));
+ d->query->bindValue(0, filterName);
+ d->query->exec();
+ while (d->query->next()) {
+ nameId = d->query->value(0).toInt();
+ break;
+ }
+
+ if (nameId < 0) {
+ d->query->prepare(QLatin1String("INSERT INTO FilterNameTable VALUES(NULL, ?)"));
+ d->query->bindValue(0, filterName);
+ if (d->query->exec())
+ nameId = d->query->lastInsertId().toInt();
+ } else if (!forceUpdate) {
+ d->error = tr("The filter %1 is already registered!").arg(filterName);
+ return false;
+ }
+
+ if (nameId < 0) {
+ d->error = tr("Cannot register filter %1!").arg(filterName);
+ return false;
+ }
+
+ d->query->prepare(QLatin1String("DELETE FROM FilterTable WHERE NameId=?"));
+ d->query->bindValue(0, nameId);
+ d->query->exec();
+
+ foreach (const QString &att, filterAttribs) {
+ d->query->prepare(QLatin1String("INSERT INTO FilterTable VALUES(?, ?)"));
+ d->query->bindValue(0, nameId);
+ d->query->bindValue(1, attributeMap[att]);
+ if (!d->query->exec())
+ return false;
+ }
+ return true;
+}
+
+bool QHelpGenerator::insertKeywords(const QList<QHelpDataIndexItem> &keywords,
+ const QStringList &filterAttributes)
+{
+ if (!d->query)
+ return false;
+
+ emit statusChanged(tr("Insert indices..."));
+ int indexId = 1;
+ d->query->exec(QLatin1String("SELECT MAX(Id) FROM IndexTable"));
+ if (d->query->next())
+ indexId = d->query->value(0).toInt() + 1;
+
+ QList<int> filterAtts;
+ foreach (const QString &filterAtt, filterAttributes) {
+ d->query->prepare(QLatin1String("SELECT Id FROM FilterAttributeTable WHERE Name=?"));
+ d->query->bindValue(0, filterAtt);
+ d->query->exec();
+ if (d->query->next())
+ filterAtts.append(d->query->value(0).toInt());
+ }
+
+ int pos = -1;
+ QString fileName;
+ QString anchor;
+ QString fName;
+ int fileId = 1;
+ QList<int> indexFilterTable;
+
+ int i = 0;
+ d->query->exec(QLatin1String("BEGIN"));
+ QSet<QString> indices;
+ foreach (const QHelpDataIndexItem &itm, keywords) {
+ // Identical ids make no sense and just confuse the Assistant user,
+ // so we ignore all repetitions.
+ if (indices.contains(itm.identifier))
+ continue;
+
+ // Still empty ids should be ignored, as otherwise we will include only
+ // the first keyword with an empty id.
+ if (!itm.identifier.isEmpty())
+ indices.insert(itm.identifier);
+
+ pos = itm.reference.indexOf(QLatin1Char('#'));
+ fileName = itm.reference.left(pos);
+ if (pos > -1)
+ anchor = itm.reference.mid(pos+1);
+ else
+ anchor.clear();
+
+ fName = QDir::cleanPath(fileName);
+ if (fName.startsWith(QLatin1String("./")))
+ fName = fName.mid(2);
+
+ QMap<QString, int>::ConstIterator it = d->fileMap.find(fName);
+ if (it != d->fileMap.end())
+ fileId = it.value();
+ else
+ fileId = 1;
+
+ d->query->prepare(QLatin1String("INSERT INTO IndexTable (Name, Identifier, NamespaceId, FileId, Anchor) "
+ "VALUES(?, ?, ?, ?, ?)"));
+ d->query->bindValue(0, itm.name);
+ d->query->bindValue(1, itm.identifier);
+ d->query->bindValue(2, d->namespaceId);
+ d->query->bindValue(3, fileId);
+ d->query->bindValue(4, anchor);
+ d->query->exec();
+
+ indexFilterTable.append(indexId++);
+ if (++i%100 == 0)
+ addProgress(d->indexStep*100.0);
+ }
+ d->query->exec(QLatin1String("COMMIT"));
+
+ d->query->exec(QLatin1String("BEGIN"));
+ foreach (int idx, indexFilterTable) {
+ foreach (int a, filterAtts) {
+ d->query->prepare(QLatin1String("INSERT INTO IndexFilterTable (FilterAttributeId, IndexId) "
+ "VALUES(?, ?)"));
+ d->query->bindValue(0, a);
+ d->query->bindValue(1, idx);
+ d->query->exec();
+ }
+ }
+ d->query->exec(QLatin1String("COMMIT"));
+
+ d->query->exec(QLatin1String("SELECT COUNT(Id) FROM IndexTable"));
+ if (d->query->next() && d->query->value(0).toInt() >= indices.count())
+ return true;
+ return false;
+}
+
+bool QHelpGenerator::insertContents(const QByteArray &ba,
+ const QStringList &filterAttributes)
+{
+ if (!d->query)
+ return false;
+
+ emit statusChanged(tr("Insert contents..."));
+ d->query->prepare(QLatin1String("INSERT INTO ContentsTable (NamespaceId, Data) "
+ "VALUES(?, ?)"));
+ d->query->bindValue(0, d->namespaceId);
+ d->query->bindValue(1, ba);
+ d->query->exec();
+ int contentId = d->query->lastInsertId().toInt();
+ if (contentId < 1) {
+ d->error = tr("Cannot insert contents!");
+ return false;
+ }
+
+ // associate the filter attributes
+ foreach (const QString &filterAtt, filterAttributes) {
+ d->query->prepare(QLatin1String("INSERT INTO ContentsFilterTable (FilterAttributeId, ContentsId) "
+ "SELECT Id, ? FROM FilterAttributeTable WHERE Name=?"));
+ d->query->bindValue(0, contentId);
+ d->query->bindValue(1, filterAtt);
+ d->query->exec();
+ if (!d->query->isActive()) {
+ d->error = tr("Cannot register contents!");
+ return false;
+ }
+ }
+ addProgress(d->contentStep);
+ return true;
+}
+
+bool QHelpGenerator::insertFilterAttributes(const QStringList &attributes)
+{
+ if (!d->query)
+ return false;
+
+ d->query->exec(QLatin1String("SELECT Name FROM FilterAttributeTable"));
+ QSet<QString> atts;
+ while (d->query->next())
+ atts.insert(d->query->value(0).toString());
+
+ foreach (const QString &s, attributes) {
+ if (!atts.contains(s)) {
+ d->query->prepare(QLatin1String("INSERT INTO FilterAttributeTable VALUES(NULL, ?)"));
+ d->query->bindValue(0, s);
+ d->query->exec();
+ }
+ }
+ return true;
+}
+
+bool QHelpGenerator::insertMetaData(const QMap<QString, QVariant> &metaData)
+{
+ if (!d->query)
+ return false;
+
+ QMap<QString, QVariant>::const_iterator it = metaData.constBegin();
+ while (it != metaData.constEnd()) {
+ d->query->prepare(QLatin1String("INSERT INTO MetaDataTable VALUES(?, ?)"));
+ d->query->bindValue(0, it.key());
+ d->query->bindValue(1, it.value());
+ d->query->exec();
+ ++it;
+ }
+ return true;
+}
+
+bool QHelpGenerator::checkLinks(const QHelpDataInterface &helpData)
+{
+ /*
+ * Step 1: Gather the canoncal file paths of all files in the project.
+ * We use a set, because there will be a lot of look-ups.
+ */
+ QSet<QString> files;
+ foreach (const QHelpDataFilterSection &filterSection, helpData.filterSections()) {
+ foreach (const QString &file, filterSection.files()) {
+ QFileInfo fileInfo(helpData.rootPath() + QDir::separator() + file);
+ const QString &canonicalFileName = fileInfo.canonicalFilePath();
+ if (!fileInfo.exists())
+ emit warning(tr("File '%1' does not exist.").arg(file));
+ else
+ files.insert(canonicalFileName);
+ }
+ }
+
+ /*
+ * Step 2: Check the hypertext and image references of all HTML files.
+ * Note that we don't parse the files, but simply grep for the
+ * respective HTML elements. Therefore. contents that are e.g.
+ * commented out can cause false warning.
+ */
+ bool allLinksOk = true;
+ foreach (const QString &fileName, files) {
+ if (!fileName.endsWith(QLatin1String("html"))
+ && !fileName.endsWith(QLatin1String("htm")))
+ continue;
+ QFile htmlFile(fileName);
+ if (!htmlFile.open(QIODevice::ReadOnly)) {
+ emit warning(tr("File '%1' cannot be opened.").arg(fileName));
+ continue;
+ }
+ const QRegExp linkPattern(QLatin1String("<(?:a href|img src)=\"?([^#\">]+)[#\">]"));
+ QTextStream stream(&htmlFile);
+ const QString codec = QHelpGlobal::codecFromData(htmlFile.read(1000));
+ stream.setCodec(QTextCodec::codecForName(codec.toLatin1().constData()));
+ const QString &content = stream.readAll();
+ QStringList invalidLinks;
+ for (int pos = linkPattern.indexIn(content); pos != -1;
+ pos = linkPattern.indexIn(content, pos + 1)) {
+ const QString& linkedFileName = linkPattern.cap(1);
+ if (linkedFileName.contains(QLatin1String("://")))
+ continue;
+ const QString curDir = QFileInfo(fileName).dir().path();
+ const QString &canonicalLinkedFileName =
+ QFileInfo(curDir + QDir::separator() + linkedFileName).canonicalFilePath();
+ if (!files.contains(canonicalLinkedFileName)
+ && !invalidLinks.contains(canonicalLinkedFileName)) {
+ emit warning(tr("File '%1' contains an invalid link to file '%2'").
+ arg(fileName).arg(linkedFileName));
+ allLinksOk = false;
+ invalidLinks.append(canonicalLinkedFileName);
+ }
+ }
+ }
+
+ if (!allLinksOk)
+ d->error = tr("Invalid links in HTML files.");
+ return allLinksOk;
+}
+
+QT_END_NAMESPACE
+