/**************************************************************************** | |
** | |
** 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 | |