summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJarek Kobus <jaroslaw.kobus@qt.io>2018-07-10 15:58:15 +0200
committerJarek Kobus <jaroslaw.kobus@qt.io>2018-07-12 06:38:00 +0000
commitf585bd18736e68589cc8b6da058822ff9487652b (patch)
tree4afd269c72b35a01bdc511af32fbbb5969f341e8
parent4ec978d546eaa7fde57843a6227be205454e5f27 (diff)
Move the QHelpGenerator into the shared folder
Don't export it anymore. Change-Id: I5e3f00df9190d3284ae8169a3ce35ddb42d9be83 Reviewed-by: Kai Koehne <kai.koehne@qt.io>
-rw-r--r--src/assistant/help/help.pro2
-rw-r--r--src/assistant/help/qhelp_global.h2
-rw-r--r--src/assistant/help/qhelpgenerator.cpp850
-rw-r--r--src/assistant/help/qhelpgenerator_p.h112
-rw-r--r--src/assistant/shared/helpgenerator.cpp809
-rw-r--r--src/assistant/shared/helpgenerator.h4
-rw-r--r--tests/auto/qhelpgenerator/qhelpgenerator.pro4
-rw-r--r--tests/auto/qhelpgenerator/tst_qhelpgenerator.cpp8
8 files changed, 812 insertions, 979 deletions
diff --git a/src/assistant/help/help.pro b/src/assistant/help/help.pro
index cd8aa64b7..efb3c2a2a 100644
--- a/src/assistant/help/help.pro
+++ b/src/assistant/help/help.pro
@@ -15,7 +15,6 @@ SOURCES += qhelpenginecore.cpp \
qhelpdbreader.cpp \
qhelpcontentwidget.cpp \
qhelpindexwidget.cpp \
- qhelpgenerator.cpp \
qhelpdatainterface.cpp \
qhelpprojectdata.cpp \
qhelpcollectionhandler.cpp \
@@ -34,7 +33,6 @@ HEADERS += qhelpenginecore.h \
qhelpdbreader_p.h \
qhelpcontentwidget.h \
qhelpindexwidget.h \
- qhelpgenerator_p.h \
qhelpdatainterface_p.h \
qhelpprojectdata_p.h \
qhelpcollectionhandler_p.h \
diff --git a/src/assistant/help/qhelp_global.h b/src/assistant/help/qhelp_global.h
index 7cfb27d5d..49828c8d7 100644
--- a/src/assistant/help/qhelp_global.h
+++ b/src/assistant/help/qhelp_global.h
@@ -55,7 +55,7 @@ QT_BEGIN_NAMESPACE
# define QHELP_EXPORT Q_DECL_IMPORT
#endif
-class QHelpGlobal {
+class QHELP_EXPORT QHelpGlobal {
public:
static QString uniquifyConnectionName(const QString &name, void *pointer);
static QString documentTitle(const QString &content);
diff --git a/src/assistant/help/qhelpgenerator.cpp b/src/assistant/help/qhelpgenerator.cpp
deleted file mode 100644
index 85adfcac5..000000000
--- a/src/assistant/help/qhelpgenerator.cpp
+++ /dev/null
@@ -1,850 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the Qt Assistant of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include "qhelpgenerator_p.h"
-#include "qhelpdatainterface_p.h"
-
-#include <QtCore/QtMath>
-#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 <QtCore/QDataStream>
-#include <QtSql/QSqlQuery>
-
-QT_BEGIN_NAMESPACE
-
-class QHelpGeneratorPrivate
-{
-public:
- QString error;
- QSqlQuery *query = nullptr;
-
- int namespaceId = -1;
- int virtualFolderId = -1;
-
- QMap<QString, int> fileMap;
- QMap<int, QSet<int> > fileFilterMap;
-
- double progress;
- double oldProgress;
- double contentStep;
- double fileStep;
- double indexStep;
-};
-
-/*!
- \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..."));
- for (const QHelpDataCustomFilter &f : helpData->customFilters()) {
- if (!registerCustomFilter(f.name, f.filterAttributes, true)) {
- cleanupDB();
- return false;
- }
- }
- addProgress(1.0);
-
- int i = 1;
- for (const QHelpDataFilterSection &fs : helpData->filterSections()) {
- emit statusChanged(tr("Insert help data for filter section (%1 of %2)...")
- .arg(i++).arg(helpData->filterSections().count()));
- insertFilterAttributes(fs.filterAttributes());
- QByteArray ba;
- QDataStream s(&ba, QIODevice::WriteOnly);
- for (QHelpDataContentItem *itm : fs.contents())
- writeTree(s, itm, 0);
- if (!insertFiles(fs.files(), helpData->rootPath(), fs.filterAttributes())
- || !insertContents(ba, fs.filterAttributes())
- || !insertKeywords(fs.indices(), fs.filterAttributes())) {
- cleanupDB();
- return false;
- }
- }
-
- 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;
- for (const QHelpDataFilterSection &fs : helpData->filterSections()) {
- numberOfFiles += fs.files().count();
- numberOfIndices += fs.indices().count();
- }
- // 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(qCeil(d->progress));
- }
-}
-
-void QHelpGenerator::cleanupDB()
-{
- if (d->query) {
- d->query->clear();
- delete d->query;
- d->query = nullptr;
- }
- QSqlDatabase::removeDatabase(QLatin1String("builder"));
-}
-
-void QHelpGenerator::writeTree(QDataStream &s, QHelpDataContentItem *item, int depth)
-{
- s << depth;
- s << item->reference();
- s << item->title();
- for (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;
- }
-
- const QStringList tables = QStringList()
- << 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 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 )");
-
- for (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')"));
-
- 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;
-
- const 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;
- for (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;
- for (int attId : qAsConst(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;
- QList<QByteArray> fileDataList;
- QMap<int, QSet<int> > tmpFileFilterMap;
- QList<FileNameTableData> fileNameDataList;
-
- int i = 0;
- for (const QString &file : files) {
- const QString fileName = QDir::cleanPath(file);
-
- 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;
- const auto &it = d->fileMap.constFind(fileName);
- if (it == d->fileMap.cend()) {
- fileDataList.append(qCompress(data));
-
- FileNameTableData fileNameData;
- 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 = it.value();
- QSet<int> &fileFilterSet = d->fileFilterMap[fileId];
- QSet<int> &tmpFileFilterSet = tmpFileFilterMap[fileId];
- for (int filter : qAsConst(filterAtts)) {
- if (!fileFilterSet.contains(filter)
- && !tmpFileFilterSet.contains(filter)) {
- fileFilterSet.insert(filter);
- tmpFileFilterSet.insert(filter);
- }
- }
- }
- }
-
- if (!tmpFileFilterMap.isEmpty()) {
- d->query->exec(QLatin1String("BEGIN"));
- for (auto it = tmpFileFilterMap.cbegin(), end = tmpFileFilterMap.cend(); it != end; ++it) {
- QList<int> filterValues = it.value().toList();
- std::sort(filterValues.begin(), filterValues.end());
- for (int fv : qAsConst(filterValues)) {
- d->query->prepare(QLatin1String("INSERT INTO FileFilterTable "
- "VALUES(?, ?)"));
- d->query->bindValue(0, fv);
- d->query->bindValue(1, it.key());
- d->query->exec();
- }
- }
-
- for (const QByteArray &fileData : qAsConst(fileDataList)) {
- d->query->prepare(QLatin1String("INSERT INTO FileDataTable VALUES "
- "(Null, ?)"));
- d->query->bindValue(0, fileData);
- d->query->exec();
- if (++i % 20 == 0)
- addProgress(d->fileStep * 20.0);
- }
-
- for (const FileNameTableData &fnd : qAsConst(fileNameDataList)) {
- d->query->prepare(QLatin1String("INSERT INTO FileNameTable "
- "(FolderId, Name, FileId, Title) VALUES (?, ?, ?, ?)"));
- d->query->bindValue(0, 1);
- d->query->bindValue(1, fnd.name);
- d->query->bindValue(2, fnd.fileId);
- d->query->bindValue(3, fnd.title);
- d->query->exec();
- }
- 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());
- }
-
- for (const QString &id : qAsConst(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();
-
- for (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;
- for (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());
- }
-
- QList<int> indexFilterTable;
-
- int i = 0;
- d->query->exec(QLatin1String("BEGIN"));
- QSet<QString> indices;
- for (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);
-
- const int pos = itm.reference.indexOf(QLatin1Char('#'));
- const QString &fileName = itm.reference.left(pos);
- const QString anchor = pos < 0 ? QString() : itm.reference.mid(pos + 1);
-
- const QString &fName = QDir::cleanPath(fileName);
-
- const auto &it = d->fileMap.constFind(fName);
- const int fileId = it == d->fileMap.cend() ? 1 : it.value();
-
- 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"));
- for (int idx : qAsConst(indexFilterTable)) {
- for (int a : qAsConst(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
- for (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());
-
- for (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;
-
- for (auto it = metaData.cbegin(), end = metaData.cend(); it != end; ++it) {
- d->query->prepare(QLatin1String("INSERT INTO MetaDataTable VALUES(?, ?)"));
- d->query->bindValue(0, it.key());
- d->query->bindValue(1, it.value());
- d->query->exec();
- }
- 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;
- for (const QHelpDataFilterSection &filterSection : helpData.filterSections()) {
- for (const QString &file : filterSection.files()) {
- const 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;
- for (const QString &fileName : qAsConst(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
-
diff --git a/src/assistant/help/qhelpgenerator_p.h b/src/assistant/help/qhelpgenerator_p.h
deleted file mode 100644
index 449752530..000000000
--- a/src/assistant/help/qhelpgenerator_p.h
+++ /dev/null
@@ -1,112 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the Qt Assistant of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#ifndef QHELPGENERATOR_H
-#define QHELPGENERATOR_H
-
-//
-// W A R N I N G
-// -------------
-//
-// This file is not part of the Qt API. It exists for the convenience
-// of the help generator tools. This header file may change from version
-// to version without notice, or even be removed.
-//
-// We mean it.
-//
-
-#include "qhelp_global.h"
-#include "qhelpdatainterface_p.h"
-
-#include <QtCore/QObject>
-
-QT_BEGIN_NAMESPACE
-
-class QHelpGeneratorPrivate;
-
-class QHELP_EXPORT QHelpGenerator : public QObject
-{
- Q_OBJECT
-
-public:
- QHelpGenerator(QObject *parent = nullptr);
- ~QHelpGenerator();
-
- bool generate(QHelpDataInterface *helpData,
- const QString &outputFileName);
- bool checkLinks(const QHelpDataInterface &helpData);
- QString error() const;
-
-Q_SIGNALS:
- void statusChanged(const QString &msg);
- void progressChanged(double progress);
- void warning(const QString &msg);
-
-private:
- struct FileNameTableData
- {
- QString name;
- int fileId;
- QString title;
- };
-
- void writeTree(QDataStream &s, QHelpDataContentItem *item, int depth);
- bool createTables();
- bool insertFileNotFoundFile();
- bool registerCustomFilter(const QString &filterName,
- const QStringList &filterAttribs, bool forceUpdate = false);
- bool registerVirtualFolder(const QString &folderName, const QString &ns);
- bool insertFilterAttributes(const QStringList &attributes);
- bool insertKeywords(const QList<QHelpDataIndexItem> &keywords,
- const QStringList &filterAttributes);
- bool insertFiles(const QStringList &files, const QString &rootPath,
- const QStringList &filterAttributes);
- bool insertContents(const QByteArray &ba,
- const QStringList &filterAttributes);
- bool insertMetaData(const QMap<QString, QVariant> &metaData);
- void cleanupDB();
- void setupProgress(QHelpDataInterface *helpData);
- void addProgress(double step);
-
- QHelpGeneratorPrivate *d;
-};
-
-QT_END_NAMESPACE
-
-#endif
diff --git a/src/assistant/shared/helpgenerator.cpp b/src/assistant/shared/helpgenerator.cpp
index 3f6aedf50..45a44f4fe 100644
--- a/src/assistant/shared/helpgenerator.cpp
+++ b/src/assistant/shared/helpgenerator.cpp
@@ -38,37 +38,830 @@
****************************************************************************/
#include "helpgenerator.h"
+#include <private/qhelpdatainterface_p.h>
+#include <qhelp_global.h>
+
+#include <QtCore/QtMath>
+#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 <QtCore/QDataStream>
+#include <QtSql/QSqlQuery>
-#include <private/qhelpgenerator_p.h>
#include <stdio.h>
QT_BEGIN_NAMESPACE
+class HelpGeneratorPrivate : public QObject
+{
+ Q_OBJECT
+
+public:
+ HelpGeneratorPrivate(QObject *parent = nullptr) : QObject(parent) {}
+
+ bool generate(QHelpDataInterface *helpData,
+ const QString &outputFileName);
+ bool checkLinks(const QHelpDataInterface &helpData);
+ QString error() const;
+
+Q_SIGNALS:
+ void statusChanged(const QString &msg);
+ void progressChanged(double progress);
+ void warning(const QString &msg);
+
+private:
+ struct FileNameTableData
+ {
+ QString name;
+ int fileId;
+ QString title;
+ };
+
+ void writeTree(QDataStream &s, QHelpDataContentItem *item, int depth);
+ bool createTables();
+ bool insertFileNotFoundFile();
+ bool registerCustomFilter(const QString &filterName,
+ const QStringList &filterAttribs, bool forceUpdate = false);
+ bool registerVirtualFolder(const QString &folderName, const QString &ns);
+ bool insertFilterAttributes(const QStringList &attributes);
+ bool insertKeywords(const QList<QHelpDataIndexItem> &keywords,
+ const QStringList &filterAttributes);
+ bool insertFiles(const QStringList &files, const QString &rootPath,
+ const QStringList &filterAttributes);
+ bool insertContents(const QByteArray &ba,
+ const QStringList &filterAttributes);
+ bool insertMetaData(const QMap<QString, QVariant> &metaData);
+ void cleanupDB();
+ void setupProgress(QHelpDataInterface *helpData);
+ void addProgress(double step);
+
+ QString m_error;
+ QSqlQuery *m_query = nullptr;
+
+ int m_namespaceId = -1;
+ int m_virtualFolderId = -1;
+
+ QMap<QString, int> m_fileMap;
+ QMap<int, QSet<int> > m_fileFilterMap;
+
+ double m_progress;
+ double m_oldProgress;
+ double m_contentStep;
+ double m_fileStep;
+ double m_indexStep;
+};
+
+/*!
+ 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 HelpGeneratorPrivate::generate(QHelpDataInterface *helpData,
+ const QString &outputFileName)
+{
+ emit progressChanged(0);
+ m_error.clear();
+ if (!helpData || helpData->namespaceName().isEmpty()) {
+ m_error = tr("Invalid help data.");
+ return false;
+ }
+
+ QString outFileName = outputFileName;
+ if (outFileName.isEmpty()) {
+ m_error = tr("No output file name specified.");
+ return false;
+ }
+
+ QFileInfo fi(outFileName);
+ if (fi.exists()) {
+ if (!fi.dir().remove(fi.fileName())) {
+ m_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)
+ m_query = new QSqlQuery(db);
+ }
+
+ if (!openingOk) {
+ m_error = tr("Cannot open data base file %1.").arg(outFileName);
+ cleanupDB();
+ return false;
+ }
+
+ m_query->exec(QLatin1String("PRAGMA synchronous=OFF"));
+ m_query->exec(QLatin1String("PRAGMA cache_size=3000"));
+
+ addProgress(1.0);
+ createTables();
+ insertFileNotFoundFile();
+ insertMetaData(helpData->metaData());
+
+ if (!registerVirtualFolder(helpData->virtualFolder(), helpData->namespaceName())) {
+ m_error = tr("Cannot register namespace \"%1\".").arg(helpData->namespaceName());
+ cleanupDB();
+ return false;
+ }
+ addProgress(1.0);
+
+ emit statusChanged(tr("Insert custom filters..."));
+ for (const QHelpDataCustomFilter &f : helpData->customFilters()) {
+ if (!registerCustomFilter(f.name, f.filterAttributes, true)) {
+ cleanupDB();
+ return false;
+ }
+ }
+ addProgress(1.0);
+
+ int i = 1;
+ for (const QHelpDataFilterSection &fs : helpData->filterSections()) {
+ emit statusChanged(tr("Insert help data for filter section (%1 of %2)...")
+ .arg(i++).arg(helpData->filterSections().count()));
+ insertFilterAttributes(fs.filterAttributes());
+ QByteArray ba;
+ QDataStream s(&ba, QIODevice::WriteOnly);
+ for (QHelpDataContentItem *itm : fs.contents())
+ writeTree(s, itm, 0);
+ if (!insertFiles(fs.files(), helpData->rootPath(), fs.filterAttributes())
+ || !insertContents(ba, fs.filterAttributes())
+ || !insertKeywords(fs.indices(), fs.filterAttributes())) {
+ cleanupDB();
+ return false;
+ }
+ }
+
+ cleanupDB();
+ emit progressChanged(100);
+ emit statusChanged(tr("Documentation successfully generated."));
+ return true;
+}
+
+void HelpGeneratorPrivate::setupProgress(QHelpDataInterface *helpData)
+{
+ m_progress = 0;
+ m_oldProgress = 0;
+
+ int numberOfFiles = 0;
+ int numberOfIndices = 0;
+ for (const QHelpDataFilterSection &fs : helpData->filterSections()) {
+ numberOfFiles += fs.files().count();
+ numberOfIndices += fs.indices().count();
+ }
+ // init 2%
+ // filters 1%
+ // contents 10%
+ // files 60%
+ // indices 27%
+ m_contentStep = 10.0/double(helpData->customFilters().count());
+ m_fileStep = 60.0/double(numberOfFiles);
+ m_indexStep = 27.0/double(numberOfIndices);
+}
+
+void HelpGeneratorPrivate::addProgress(double step)
+{
+ m_progress += step;
+ if ((m_progress - m_oldProgress) >= 1.0 && m_progress <= 100.0) {
+ m_oldProgress = m_progress;
+ emit progressChanged(qCeil(m_progress));
+ }
+}
+
+void HelpGeneratorPrivate::cleanupDB()
+{
+ if (m_query) {
+ m_query->clear();
+ delete m_query;
+ m_query = nullptr;
+ }
+ QSqlDatabase::removeDatabase(QLatin1String("builder"));
+}
+
+void HelpGeneratorPrivate::writeTree(QDataStream &s, QHelpDataContentItem *item, int depth)
+{
+ s << depth;
+ s << item->reference();
+ s << item->title();
+ for (QHelpDataContentItem *i : item->children())
+ writeTree(s, i, depth + 1);
+}
+
+/*!
+ Returns the last error message.
+*/
+QString HelpGeneratorPrivate::error() const
+{
+ return m_error;
+}
+
+bool HelpGeneratorPrivate::createTables()
+{
+ if (!m_query)
+ return false;
+
+ m_query->exec(QLatin1String("SELECT COUNT(*) FROM sqlite_master WHERE TYPE=\'table\'"
+ "AND Name=\'NamespaceTable\'"));
+ m_query->next();
+ if (m_query->value(0).toInt() > 0) {
+ m_error = tr("Some tables already exist.");
+ return false;
+ }
+
+ const QStringList tables = QStringList()
+ << 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 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 )");
+
+ for (const QString &q : tables) {
+ if (!m_query->exec(q)) {
+ m_error = tr("Cannot create tables.");
+ return false;
+ }
+ }
+
+ m_query->exec(QLatin1String("INSERT INTO MetaDataTable VALUES('qchVersion', '1.0')"));
+
+ return true;
+}
+
+bool HelpGeneratorPrivate::insertFileNotFoundFile()
+{
+ if (!m_query)
+ return false;
+
+ m_query->exec(QLatin1String("SELECT id FROM FileNameTable WHERE Name=\'\'"));
+ if (m_query->next() && m_query->isValid())
+ return true;
+
+ m_query->prepare(QLatin1String("INSERT INTO FileDataTable VALUES (Null, ?)"));
+ m_query->bindValue(0, QByteArray());
+ if (!m_query->exec())
+ return false;
+
+ const int fileId = m_query->lastInsertId().toInt();
+ m_query->prepare(QLatin1String("INSERT INTO FileNameTable (FolderId, Name, FileId, Title) "
+ " VALUES (0, '', ?, '')"));
+ m_query->bindValue(0, fileId);
+ if (fileId > -1 && m_query->exec()) {
+ m_fileMap.insert(QString(), fileId);
+ return true;
+ }
+ return false;
+}
+
+bool HelpGeneratorPrivate::registerVirtualFolder(const QString &folderName, const QString &ns)
+{
+ if (!m_query || folderName.isEmpty() || ns.isEmpty())
+ return false;
+
+ m_query->prepare(QLatin1String("SELECT Id FROM FolderTable WHERE Name=?"));
+ m_query->bindValue(0, folderName);
+ m_query->exec();
+ m_query->next();
+ if (m_query->isValid() && m_query->value(0).toInt() > 0)
+ return true;
+
+ m_namespaceId = -1;
+ m_query->prepare(QLatin1String("SELECT Id FROM NamespaceTable WHERE Name=?"));
+ m_query->bindValue(0, ns);
+ m_query->exec();
+ while (m_query->next()) {
+ m_namespaceId = m_query->value(0).toInt();
+ break;
+ }
+
+ if (m_namespaceId < 0) {
+ m_query->prepare(QLatin1String("INSERT INTO NamespaceTable VALUES(NULL, ?)"));
+ m_query->bindValue(0, ns);
+ if (m_query->exec())
+ m_namespaceId = m_query->lastInsertId().toInt();
+ }
+
+ if (m_namespaceId > 0) {
+ m_query->prepare(QLatin1String("SELECT Id FROM FolderTable WHERE Name=?"));
+ m_query->bindValue(0, folderName);
+ m_query->exec();
+ while (m_query->next())
+ m_virtualFolderId = m_query->value(0).toInt();
+
+ if (m_virtualFolderId > 0)
+ return true;
+
+ m_query->prepare(QLatin1String("INSERT INTO FolderTable (NamespaceId, Name) "
+ "VALUES (?, ?)"));
+ m_query->bindValue(0, m_namespaceId);
+ m_query->bindValue(1, folderName);
+ if (m_query->exec()) {
+ m_virtualFolderId = m_query->lastInsertId().toInt();
+ return m_virtualFolderId > 0;
+ }
+ }
+ m_error = tr("Cannot register virtual folder.");
+ return false;
+}
+
+bool HelpGeneratorPrivate::insertFiles(const QStringList &files, const QString &rootPath,
+ const QStringList &filterAttributes)
+{
+ if (!m_query)
+ return false;
+
+ emit statusChanged(tr("Insert files..."));
+ QList<int> filterAtts;
+ for (const QString &filterAtt : filterAttributes) {
+ m_query->prepare(QLatin1String("SELECT Id FROM FilterAttributeTable "
+ "WHERE Name=?"));
+ m_query->bindValue(0, filterAtt);
+ m_query->exec();
+ if (m_query->next())
+ filterAtts.append(m_query->value(0).toInt());
+ }
+
+ int filterSetId = -1;
+ m_query->exec(QLatin1String("SELECT MAX(Id) FROM FileAttributeSetTable"));
+ if (m_query->next())
+ filterSetId = m_query->value(0).toInt();
+ if (filterSetId < 0)
+ return false;
+ ++filterSetId;
+ for (int attId : qAsConst(filterAtts)) {
+ m_query->prepare(QLatin1String("INSERT INTO FileAttributeSetTable "
+ "VALUES(?, ?)"));
+ m_query->bindValue(0, filterSetId);
+ m_query->bindValue(1, attId);
+ m_query->exec();
+ }
+
+ int tableFileId = 1;
+ m_query->exec(QLatin1String("SELECT MAX(Id) FROM FileDataTable"));
+ if (m_query->next())
+ tableFileId = m_query->value(0).toInt() + 1;
+
+ QString title;
+ QString charSet;
+ QList<QByteArray> fileDataList;
+ QMap<int, QSet<int> > tmpFileFilterMap;
+ QList<FileNameTableData> fileNameDataList;
+
+ int i = 0;
+ for (const QString &file : files) {
+ const QString fileName = QDir::cleanPath(file);
+
+ 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;
+ const auto &it = m_fileMap.constFind(fileName);
+ if (it == m_fileMap.cend()) {
+ fileDataList.append(qCompress(data));
+
+ FileNameTableData fileNameData;
+ fileNameData.name = fileName;
+ fileNameData.fileId = tableFileId;
+ fileNameData.title = title;
+ fileNameDataList.append(fileNameData);
+
+ m_fileMap.insert(fileName, tableFileId);
+ m_fileFilterMap.insert(tableFileId, filterAtts.toSet());
+ tmpFileFilterMap.insert(tableFileId, filterAtts.toSet());
+
+ ++tableFileId;
+ } else {
+ fileId = it.value();
+ QSet<int> &fileFilterSet = m_fileFilterMap[fileId];
+ QSet<int> &tmpFileFilterSet = tmpFileFilterMap[fileId];
+ for (int filter : qAsConst(filterAtts)) {
+ if (!fileFilterSet.contains(filter)
+ && !tmpFileFilterSet.contains(filter)) {
+ fileFilterSet.insert(filter);
+ tmpFileFilterSet.insert(filter);
+ }
+ }
+ }
+ }
+
+ if (!tmpFileFilterMap.isEmpty()) {
+ m_query->exec(QLatin1String("BEGIN"));
+ for (auto it = tmpFileFilterMap.cbegin(), end = tmpFileFilterMap.cend(); it != end; ++it) {
+ QList<int> filterValues = it.value().toList();
+ std::sort(filterValues.begin(), filterValues.end());
+ for (int fv : qAsConst(filterValues)) {
+ m_query->prepare(QLatin1String("INSERT INTO FileFilterTable "
+ "VALUES(?, ?)"));
+ m_query->bindValue(0, fv);
+ m_query->bindValue(1, it.key());
+ m_query->exec();
+ }
+ }
+
+ for (const QByteArray &fileData : qAsConst(fileDataList)) {
+ m_query->prepare(QLatin1String("INSERT INTO FileDataTable VALUES "
+ "(Null, ?)"));
+ m_query->bindValue(0, fileData);
+ m_query->exec();
+ if (++i % 20 == 0)
+ addProgress(m_fileStep * 20.0);
+ }
+
+ for (const FileNameTableData &fnd : qAsConst(fileNameDataList)) {
+ m_query->prepare(QLatin1String("INSERT INTO FileNameTable "
+ "(FolderId, Name, FileId, Title) VALUES (?, ?, ?, ?)"));
+ m_query->bindValue(0, 1);
+ m_query->bindValue(1, fnd.name);
+ m_query->bindValue(2, fnd.fileId);
+ m_query->bindValue(3, fnd.title);
+ m_query->exec();
+ }
+ m_query->exec(QLatin1String("COMMIT"));
+ }
+
+ m_query->exec(QLatin1String("SELECT MAX(Id) FROM FileDataTable"));
+ if (m_query->next()
+ && m_query->value(0).toInt() == tableFileId - 1) {
+ addProgress(m_fileStep*(i % 20));
+ return true;
+ }
+ return false;
+}
+
+bool HelpGeneratorPrivate::registerCustomFilter(const QString &filterName,
+ const QStringList &filterAttribs, bool forceUpdate)
+{
+ if (!m_query)
+ return false;
+
+ m_query->exec(QLatin1String("SELECT Id, Name FROM FilterAttributeTable"));
+ QStringList idsToInsert = filterAttribs;
+ QMap<QString, int> attributeMap;
+ while (m_query->next()) {
+ attributeMap.insert(m_query->value(1).toString(),
+ m_query->value(0).toInt());
+ idsToInsert.removeAll(m_query->value(1).toString());
+ }
+
+ for (const QString &id : qAsConst(idsToInsert)) {
+ m_query->prepare(QLatin1String("INSERT INTO FilterAttributeTable VALUES(NULL, ?)"));
+ m_query->bindValue(0, id);
+ m_query->exec();
+ attributeMap.insert(id, m_query->lastInsertId().toInt());
+ }
+
+ int nameId = -1;
+ m_query->prepare(QLatin1String("SELECT Id FROM FilterNameTable WHERE Name=?"));
+ m_query->bindValue(0, filterName);
+ m_query->exec();
+ while (m_query->next()) {
+ nameId = m_query->value(0).toInt();
+ break;
+ }
+
+ if (nameId < 0) {
+ m_query->prepare(QLatin1String("INSERT INTO FilterNameTable VALUES(NULL, ?)"));
+ m_query->bindValue(0, filterName);
+ if (m_query->exec())
+ nameId = m_query->lastInsertId().toInt();
+ } else if (!forceUpdate) {
+ m_error = tr("The filter %1 is already registered.").arg(filterName);
+ return false;
+ }
+
+ if (nameId < 0) {
+ m_error = tr("Cannot register filter %1.").arg(filterName);
+ return false;
+ }
+
+ m_query->prepare(QLatin1String("DELETE FROM FilterTable WHERE NameId=?"));
+ m_query->bindValue(0, nameId);
+ m_query->exec();
+
+ for (const QString &att : filterAttribs) {
+ m_query->prepare(QLatin1String("INSERT INTO FilterTable VALUES(?, ?)"));
+ m_query->bindValue(0, nameId);
+ m_query->bindValue(1, attributeMap[att]);
+ if (!m_query->exec())
+ return false;
+ }
+ return true;
+}
+
+bool HelpGeneratorPrivate::insertKeywords(const QList<QHelpDataIndexItem> &keywords,
+ const QStringList &filterAttributes)
+{
+ if (!m_query)
+ return false;
+
+ emit statusChanged(tr("Insert indices..."));
+ int indexId = 1;
+ m_query->exec(QLatin1String("SELECT MAX(Id) FROM IndexTable"));
+ if (m_query->next())
+ indexId = m_query->value(0).toInt() + 1;
+
+ QList<int> filterAtts;
+ for (const QString &filterAtt : filterAttributes) {
+ m_query->prepare(QLatin1String("SELECT Id FROM FilterAttributeTable WHERE Name=?"));
+ m_query->bindValue(0, filterAtt);
+ m_query->exec();
+ if (m_query->next())
+ filterAtts.append(m_query->value(0).toInt());
+ }
+
+ QList<int> indexFilterTable;
+
+ int i = 0;
+ m_query->exec(QLatin1String("BEGIN"));
+ QSet<QString> indices;
+ for (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);
+
+ const int pos = itm.reference.indexOf(QLatin1Char('#'));
+ const QString &fileName = itm.reference.left(pos);
+ const QString anchor = pos < 0 ? QString() : itm.reference.mid(pos + 1);
+
+ const QString &fName = QDir::cleanPath(fileName);
+
+ const auto &it = m_fileMap.constFind(fName);
+ const int fileId = it == m_fileMap.cend() ? 1 : it.value();
+
+ m_query->prepare(QLatin1String("INSERT INTO IndexTable (Name, Identifier, NamespaceId, FileId, Anchor) "
+ "VALUES(?, ?, ?, ?, ?)"));
+ m_query->bindValue(0, itm.name);
+ m_query->bindValue(1, itm.identifier);
+ m_query->bindValue(2, m_namespaceId);
+ m_query->bindValue(3, fileId);
+ m_query->bindValue(4, anchor);
+ m_query->exec();
+
+ indexFilterTable.append(indexId++);
+ if (++i % 100 == 0)
+ addProgress(m_indexStep * 100.0);
+ }
+ m_query->exec(QLatin1String("COMMIT"));
+
+ m_query->exec(QLatin1String("BEGIN"));
+ for (int idx : qAsConst(indexFilterTable)) {
+ for (int a : qAsConst(filterAtts)) {
+ m_query->prepare(QLatin1String("INSERT INTO IndexFilterTable (FilterAttributeId, IndexId) "
+ "VALUES(?, ?)"));
+ m_query->bindValue(0, a);
+ m_query->bindValue(1, idx);
+ m_query->exec();
+ }
+ }
+ m_query->exec(QLatin1String("COMMIT"));
+
+ m_query->exec(QLatin1String("SELECT COUNT(Id) FROM IndexTable"));
+ if (m_query->next() && m_query->value(0).toInt() >= indices.count())
+ return true;
+ return false;
+}
+
+bool HelpGeneratorPrivate::insertContents(const QByteArray &ba,
+ const QStringList &filterAttributes)
+{
+ if (!m_query)
+ return false;
+
+ emit statusChanged(tr("Insert contents..."));
+ m_query->prepare(QLatin1String("INSERT INTO ContentsTable (NamespaceId, Data) "
+ "VALUES(?, ?)"));
+ m_query->bindValue(0, m_namespaceId);
+ m_query->bindValue(1, ba);
+ m_query->exec();
+ int contentId = m_query->lastInsertId().toInt();
+ if (contentId < 1) {
+ m_error = tr("Cannot insert contents.");
+ return false;
+ }
+
+ // associate the filter attributes
+ for (const QString &filterAtt : filterAttributes) {
+ m_query->prepare(QLatin1String("INSERT INTO ContentsFilterTable (FilterAttributeId, ContentsId) "
+ "SELECT Id, ? FROM FilterAttributeTable WHERE Name=?"));
+ m_query->bindValue(0, contentId);
+ m_query->bindValue(1, filterAtt);
+ m_query->exec();
+ if (!m_query->isActive()) {
+ m_error = tr("Cannot register contents.");
+ return false;
+ }
+ }
+ addProgress(m_contentStep);
+ return true;
+}
+
+bool HelpGeneratorPrivate::insertFilterAttributes(const QStringList &attributes)
+{
+ if (!m_query)
+ return false;
+
+ m_query->exec(QLatin1String("SELECT Name FROM FilterAttributeTable"));
+ QSet<QString> atts;
+ while (m_query->next())
+ atts.insert(m_query->value(0).toString());
+
+ for (const QString &s : attributes) {
+ if (!atts.contains(s)) {
+ m_query->prepare(QLatin1String("INSERT INTO FilterAttributeTable VALUES(NULL, ?)"));
+ m_query->bindValue(0, s);
+ m_query->exec();
+ }
+ }
+ return true;
+}
+
+bool HelpGeneratorPrivate::insertMetaData(const QMap<QString, QVariant> &metaData)
+{
+ if (!m_query)
+ return false;
+
+ for (auto it = metaData.cbegin(), end = metaData.cend(); it != end; ++it) {
+ m_query->prepare(QLatin1String("INSERT INTO MetaDataTable VALUES(?, ?)"));
+ m_query->bindValue(0, it.key());
+ m_query->bindValue(1, it.value());
+ m_query->exec();
+ }
+ return true;
+}
+
+bool HelpGeneratorPrivate::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;
+ for (const QHelpDataFilterSection &filterSection : helpData.filterSections()) {
+ for (const QString &file : filterSection.files()) {
+ const 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;
+ for (const QString &fileName : qAsConst(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)
+ m_error = tr("Invalid links in HTML files.");
+ return allLinksOk;
+}
+
+//////////////////////////////
+
HelpGenerator::HelpGenerator(bool silent)
{
- generator = new QHelpGenerator(this);
+ m_private = new HelpGeneratorPrivate(this);
if (!silent) {
- connect(generator, &QHelpGenerator::statusChanged,
+ connect(m_private, &HelpGeneratorPrivate::statusChanged,
this, &HelpGenerator::printStatus);
}
- connect(generator, &QHelpGenerator::warning,
+ connect(m_private, &HelpGeneratorPrivate::warning,
this, &HelpGenerator::printWarning);
}
bool HelpGenerator::generate(QHelpDataInterface *helpData,
const QString &outputFileName)
{
- return generator->generate(helpData, outputFileName);
+ return m_private->generate(helpData, outputFileName);
}
bool HelpGenerator::checkLinks(const QHelpDataInterface &helpData)
{
- return generator->checkLinks(helpData);
+ return m_private->checkLinks(helpData);
}
QString HelpGenerator::error() const
{
- return generator->error();
+ return m_private->error();
}
void HelpGenerator::printStatus(const QString &msg)
@@ -82,3 +875,5 @@ void HelpGenerator::printWarning(const QString &msg)
}
QT_END_NAMESPACE
+
+#include "helpgenerator.moc"
diff --git a/src/assistant/shared/helpgenerator.h b/src/assistant/shared/helpgenerator.h
index 8e5ed5f51..4ca84c73a 100644
--- a/src/assistant/shared/helpgenerator.h
+++ b/src/assistant/shared/helpgenerator.h
@@ -45,7 +45,7 @@
QT_BEGIN_NAMESPACE
class QHelpDataInterface;
-class QHelpGenerator;
+class HelpGeneratorPrivate;
class HelpGenerator : public QObject
{
@@ -63,7 +63,7 @@ private slots:
void printWarning(const QString &msg);
private:
- QHelpGenerator *generator;
+ HelpGeneratorPrivate *m_private;
};
QT_END_NAMESPACE
diff --git a/tests/auto/qhelpgenerator/qhelpgenerator.pro b/tests/auto/qhelpgenerator/qhelpgenerator.pro
index 9134f53b3..06bc1c631 100644
--- a/tests/auto/qhelpgenerator/qhelpgenerator.pro
+++ b/tests/auto/qhelpgenerator/qhelpgenerator.pro
@@ -1,7 +1,9 @@
TARGET = tst_qhelpgenerator
CONFIG += testcase
-SOURCES += tst_qhelpgenerator.cpp
+HEADERS += ../../../src/assistant/shared/helpgenerator.h
+SOURCES += tst_qhelpgenerator.cpp \
+ ../../../src/assistant/shared/helpgenerator.cpp
QT += help-private sql testlib
DEFINES += SRCDIR=\\\"$$PWD\\\"
diff --git a/tests/auto/qhelpgenerator/tst_qhelpgenerator.cpp b/tests/auto/qhelpgenerator/tst_qhelpgenerator.cpp
index 6c94e431c..b5ecc5118 100644
--- a/tests/auto/qhelpgenerator/tst_qhelpgenerator.cpp
+++ b/tests/auto/qhelpgenerator/tst_qhelpgenerator.cpp
@@ -31,8 +31,8 @@
#include <QtSql/QSqlDatabase>
#include <QtSql/QSqlQuery>
-#include <QtHelp/private/qhelpgenerator_p.h>
#include <QtHelp/private/qhelpprojectdata_p.h>
+#include "../../../src/assistant/shared/helpgenerator.h"
class tst_QHelpGenerator : public QObject
{
@@ -76,7 +76,7 @@ void tst_QHelpGenerator::generateHelp()
if (!data.readData(inputFile))
QFAIL("Cannot read qthp file!");
- QHelpGenerator generator;
+ HelpGenerator generator;
QCOMPARE(generator.generate(&data, m_outputFile), true);
{
@@ -201,8 +201,8 @@ void tst_QHelpGenerator::generateTwice()
if (!data.readData(inputFile))
QFAIL("Cannot read qhp file!");
- QHelpGenerator generator1;
- QHelpGenerator generator2;
+ HelpGenerator generator1;
+ HelpGenerator generator2;
QString outputFile1 = path + QLatin1String("/data/test1.qch");
QString outputFile2 = path + QLatin1String("/data/test2.qch");
QCOMPARE(generator1.generate(&data, outputFile1), true);