text: /****************************************************************************
  **
  ** Copyright (C) 2015 The Qt Company Ltd.
  ** Contact: http://www.qt.io/licensing/
  **
  ** This file is part of the Qt Assistant of the Qt Toolkit.
  **
  ** $QT_BEGIN_LICENSE:LGPL21$
  ** 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 http://www.qt.io/terms-conditions. For further
  ** information use the contact form at http://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 2.1 or version 3 as published by the Free
  ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
  ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
  ** following information to ensure the GNU Lesser General Public License
  ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
  ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
  **
  ** As a special exception, The Qt Company gives you certain additional
  ** rights. These rights are described in The Qt Company LGPL Exception
  ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
  **
  ** $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 <QtCore/QDataStream>
  #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)
  {
      s << depth;
      s << item->reference();
      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->exec(QLatin1String("INSERT INTO MetaDataTable VALUES('CreationDate', '2012-12-20T12:00:00')"));
  
      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 (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);
  
          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 (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()) {
              QList<int> filterValues = it.value().toList();
              std::sort(filterValues.begin(), filterValues.end());
              QList<int>::const_iterator si = filterValues.constBegin();
              while (si != filterValues.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;
          }
          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
  
  