summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaul Wicking <paul.wicking@qt.io>2020-09-10 14:28:40 +0200
committerPaul Wicking <paul.wicking@qt.io>2020-09-11 09:38:33 +0200
commit93e805a02417290f225810ce7c9263b2fe56b4e5 (patch)
treede42b565e4fd3495a9a9df77f3d1234a89054f02
parentf72b7a68221898333c3ea032d493a9efd5a68af6 (diff)
QDoc: Extract ManifestWriter from HTML generator
Fixes: QTBUG-86390 Change-Id: I9cdcb591bbd1cf6beccf27e53ab9af06b87ed4be Reviewed-by: Topi Reiniƶ <topi.reinio@qt.io>
-rw-r--r--src/qdoc/.prev_CMakeLists.txt1
-rw-r--r--src/qdoc/CMakeLists.txt1
-rw-r--r--src/qdoc/htmlgenerator.cpp290
-rw-r--r--src/qdoc/htmlgenerator.h7
-rw-r--r--src/qdoc/manifestwriter.cpp328
-rw-r--r--src/qdoc/manifestwriter.h67
-rw-r--r--src/qdoc/qdoc.pro2
7 files changed, 412 insertions, 284 deletions
diff --git a/src/qdoc/.prev_CMakeLists.txt b/src/qdoc/.prev_CMakeLists.txt
index eebdf59a1..e65c0e9a4 100644
--- a/src/qdoc/.prev_CMakeLists.txt
+++ b/src/qdoc/.prev_CMakeLists.txt
@@ -42,6 +42,7 @@ qt_add_tool(${target_name}
loggingcategory.h
macro.h
main.cpp
+ manifestwriter.cpp manifestwriter.h
namespacenode.cpp namespacenode.h
node.cpp node.h
openedlist.cpp openedlist.h
diff --git a/src/qdoc/CMakeLists.txt b/src/qdoc/CMakeLists.txt
index bc7e29d54..f5904e29b 100644
--- a/src/qdoc/CMakeLists.txt
+++ b/src/qdoc/CMakeLists.txt
@@ -50,6 +50,7 @@ qt_add_tool(${target_name}
loggingcategory.h
macro.h
main.cpp
+ manifestwriter.cpp manifestwriter.h
namespacenode.cpp namespacenode.h
node.cpp node.h
openedlist.cpp openedlist.h
diff --git a/src/qdoc/htmlgenerator.cpp b/src/qdoc/htmlgenerator.cpp
index 6dc7dc93d..3cc08c84b 100644
--- a/src/qdoc/htmlgenerator.cpp
+++ b/src/qdoc/htmlgenerator.cpp
@@ -39,6 +39,7 @@
#include "examplenode.h"
#include "functionnode.h"
#include "helpprojectwriter.h"
+#include "manifestwriter.h"
#include "node.h"
#include "propertynode.h"
#include "qdocdatabase.h"
@@ -79,7 +80,7 @@ static void addLink(const QString &linkTarget, QStringView nestedStuff, QString
/*!
Destroys the HTML output generator. Deletes the singleton
- instance of HelpProjectWriter.
+ instance of HelpProjectWriter and the ManifestWriter instance.
*/
HtmlGenerator::~HtmlGenerator()
{
@@ -87,6 +88,11 @@ HtmlGenerator::~HtmlGenerator()
delete m_helpProjectWriter;
m_helpProjectWriter = nullptr;
}
+
+ if (m_manifestWriter) {
+ delete m_manifestWriter;
+ m_manifestWriter = nullptr;
+ }
}
/*!
@@ -172,21 +178,14 @@ void HtmlGenerator::initializeGenerator()
else
m_helpProjectWriter = new HelpProjectWriter(m_project.toLower() + ".qhp", this);
+ if (!m_manifestWriter)
+ m_manifestWriter = new ManifestWriter();
+
// Documentation template handling
m_headerScripts =
config->getString(HtmlGenerator::format() + Config::dot + CONFIG_HEADERSCRIPTS);
m_headerStyles = config->getString(HtmlGenerator::format() + Config::dot + CONFIG_HEADERSTYLES);
- QString prefix = CONFIG_QHP + Config::dot + m_project + Config::dot;
- m_manifestDir =
- QLatin1String("qthelp://") + config->getString(prefix + QLatin1String("namespace"));
- m_manifestDir += QLatin1Char('/') + config->getString(prefix + QLatin1String("virtualFolder"))
- + QLatin1Char('/');
- readManifestMetaContent();
- m_examplesPath = config->getString(CONFIG_EXAMPLESINSTALLPATH);
- if (!m_examplesPath.isEmpty())
- m_examplesPath += QLatin1Char('/');
-
// Retrieve the config for the navigation bar
m_homepage = config->getString(CONFIG_NAVIGATION + Config::dot + CONFIG_HOMEPAGE);
@@ -259,7 +258,7 @@ void HtmlGenerator::generateDocs()
if (!config->preparing()) {
m_helpProjectWriter->generate();
- generateManifestFiles();
+ m_manifestWriter->generateManifestFiles();
/*
Generate the XML tag file, if it was requested.
*/
@@ -3660,271 +3659,4 @@ void HtmlGenerator::generateExtractionMark(const Node *node, ExtractionMarkType
}
}
-/*!
- This function outputs one or more manifest files in XML.
- They are used by Creator.
- */
-void HtmlGenerator::generateManifestFiles()
-{
- generateManifestFile("examples", "example");
- generateManifestFile("demos", "demo");
- m_qdb->exampleNodeMap().clear();
- m_manifestMetaContent.clear();
-}
-
-/*!
- Retrieve the install path for the \a example as specified with
- the \meta command, or fall back to the one defined in .qdocconf.
- */
-QString HtmlGenerator::retrieveInstallPath(const ExampleNode *example)
-{
- QString installPath;
- if (example->doc().metaTagMap())
- installPath = example->doc().metaTagMap()->value(QLatin1String("installpath"));
- if (installPath.isEmpty())
- installPath = m_examplesPath;
- if (!installPath.isEmpty() && !installPath.endsWith(QLatin1Char('/')))
- installPath += QLatin1Char('/');
-
- return installPath;
-}
-
-/*!
- This function is called by generateManifestFiles(), once
- for each manifest file to be generated. \a manifest is the
- type of manifest file.
- */
-void HtmlGenerator::generateManifestFile(const QString &manifest, const QString &element)
-{
- ExampleNodeMap &exampleNodeMap = m_qdb->exampleNodeMap();
- if (exampleNodeMap.isEmpty())
- return;
- QString fileName = manifest + "-manifest.xml";
- QFile file(outputDir() + QLatin1Char('/') + fileName);
- bool demos = false;
- if (manifest == QLatin1String("demos"))
- demos = true;
-
- bool proceed = false;
- for (auto map = exampleNodeMap.begin(); map != exampleNodeMap.end(); ++map) {
- const ExampleNode *en = map.value();
- if (demos == en->name().startsWith("demos")) {
- proceed = true;
- break;
- }
- }
- if (!proceed || !file.open(QFile::WriteOnly | QFile::Text))
- return;
-
- QXmlStreamWriter writer(&file);
- writer.setAutoFormatting(true);
- writer.writeStartDocument();
- writer.writeStartElement("instructionals");
- writer.writeAttribute("module", m_project);
- writer.writeStartElement(manifest);
-
- QStringList usedAttributes;
- for (auto map = exampleNodeMap.begin(); map != exampleNodeMap.end(); ++map) {
- const ExampleNode *en = map.value();
- if (demos) {
- if (!en->name().startsWith("demos"))
- continue;
- } else if (en->name().startsWith("demos")) {
- continue;
- }
-
- const QString installPath = retrieveInstallPath(en);
- // attributes that are always written for the element
- usedAttributes.clear();
- usedAttributes << "name"
- << "docUrl"
- << "projectPath";
-
- writer.writeStartElement(element);
- writer.writeAttribute("name", en->title());
- QString docUrl = m_manifestDir + appendObsoleteToFileBase(en) + ".html";
- writer.writeAttribute("docUrl", docUrl);
- const auto exampleFiles = en->files();
- if (en->projectFile().isEmpty())
- Location().warning("Example does not have a project file: ", en->name());
- else
- writer.writeAttribute("projectPath", installPath + en->projectFile());
- if (en->imageFileName().isEmpty()) {
- Location().warning("Example does not have an image file: ", en->name());
- } else {
- writer.writeAttribute("imageUrl", m_manifestDir + en->imageFileName());
- usedAttributes << "imageUrl";
- }
-
- QString fullName = m_project + QLatin1Char('/') + en->title();
- QSet<QString> tags;
- for (const auto &index : m_manifestMetaContent) {
- const auto &names = index.names;
- for (const QString &name : names) {
- bool match = false;
- int wildcard = name.indexOf(QChar('*'));
- switch (wildcard) {
- case -1: // no wildcard, exact match
- match = (fullName == name);
- break;
- case 0: // '*' matches all
- match = true;
- break;
- default: // match with wildcard at the end
- match = fullName.startsWith(name.left(wildcard));
- }
- if (match) {
- tags += index.tags;
- const auto attributes = index.attributes;
- for (const QString &attr : attributes) {
- QLatin1Char div(':');
- QStringList attrList = attr.split(div);
- if (attrList.count() == 1)
- attrList.append(QStringLiteral("true"));
- QString attrName = attrList.takeFirst();
- if (!usedAttributes.contains(attrName)) {
- writer.writeAttribute(attrName, attrList.join(div));
- usedAttributes << attrName;
- }
- }
- }
- }
- }
-
- writer.writeStartElement("description");
- Text brief = en->doc().briefText();
- if (!brief.isEmpty())
- writer.writeCDATA(brief.toString());
- else
- writer.writeCDATA(QString("No description available"));
- writer.writeEndElement(); // description
-
- // Add words from module name as tags
- // QtQuickControls -> qt,quick,controls
- // QtOpenGL -> qt,opengl
- QRegularExpression re("([A-Z]+[a-z0-9]*(3D|GL)?)");
- int pos = 0;
- QRegularExpressionMatch match;
- while ((match = re.match(m_project, pos)).hasMatch()) {
- tags << match.captured(1).toLower();
- pos = match.capturedEnd();
- }
-
- // Include tags added via \meta {tag} {tag1[,tag2,...]}
- // within \example topic
- QStringMultiMap *metaTagMap = en->doc().metaTagMap();
- if (metaTagMap) {
- for (const auto &tag : metaTagMap->values("tag")) {
- const auto &tagList = tag.toLower().split(QLatin1Char(','));
- tags += QSet<QString>(tagList.cbegin(), tagList.cend());
- }
- }
-
- const auto &titleWords = en->title().toLower().split(QLatin1Char(' '));
- tags += QSet<QString>(titleWords.cbegin(), titleWords.cend());
-
- // Clean up tags, exclude invalid and common words
- QSet<QString>::iterator tag_it = tags.begin();
- QSet<QString> modified;
- while (tag_it != tags.end()) {
- QString s = *tag_it;
- if (s.at(0) == '(')
- s.remove(0, 1).chop(1);
- if (s.endsWith(QLatin1Char(':')))
- s.chop(1);
-
- if (s.length() < 2 || s.at(0).isDigit() || s.at(0) == '-' || s == QLatin1String("qt")
- || s == QLatin1String("the") || s == QLatin1String("and")
- || s.startsWith(QLatin1String("example")) || s.startsWith(QLatin1String("chapter")))
- tag_it = tags.erase(tag_it);
- else if (s != *tag_it) {
- modified << s;
- tag_it = tags.erase(tag_it);
- } else
- ++tag_it;
- }
- tags += modified;
-
- if (!tags.isEmpty()) {
- writer.writeStartElement("tags");
- bool wrote_one = false;
- QStringList sortedTags = tags.values();
- sortedTags.sort();
- for (const auto &tag : qAsConst(sortedTags)) {
- if (wrote_one)
- writer.writeCharacters(",");
- writer.writeCharacters(tag);
- wrote_one = true;
- }
- writer.writeEndElement(); // tags
- }
-
- QString ename = en->name().mid(en->name().lastIndexOf('/') + 1);
- QMap<int, QString> filesToOpen;
- const auto files = en->files();
- for (const QString &file : files) {
- QFileInfo fileInfo(file);
- QString fileName = fileInfo.fileName().toLower();
- // open .qml, .cpp and .h files with a
- // basename matching the example (project) name
- // QMap key indicates the priority -
- // the lowest value will be the top-most file
- if ((fileInfo.baseName().compare(ename, Qt::CaseInsensitive) == 0)) {
- if (fileName.endsWith(".qml"))
- filesToOpen.insert(0, file);
- else if (fileName.endsWith(".cpp"))
- filesToOpen.insert(1, file);
- else if (fileName.endsWith(".h"))
- filesToOpen.insert(2, file);
- }
- // main.qml takes precedence over main.cpp
- else if (fileName.endsWith("main.qml")) {
- filesToOpen.insert(3, file);
- } else if (fileName.endsWith("main.cpp")) {
- filesToOpen.insert(4, file);
- }
- }
-
- for (auto it = filesToOpen.constEnd(); it != filesToOpen.constBegin();) {
- writer.writeStartElement("fileToOpen");
- if (--it == filesToOpen.constBegin()) {
- writer.writeAttribute(QStringLiteral("mainFile"), QStringLiteral("true"));
- }
- writer.writeCharacters(installPath + it.value());
- writer.writeEndElement();
- }
-
- writer.writeEndElement(); // example
- }
-
- writer.writeEndElement(); // examples
- writer.writeEndElement(); // instructionals
- writer.writeEndDocument();
- file.close();
-}
-
-/*!
- Reads metacontent - additional attributes and tags to apply
- when generating manifest files, read from config. Takes the
- configuration class \a config as a parameter.
-
- The manifest metacontent map is cleared immediately after
- the manifest files have been generated.
- */
-void HtmlGenerator::readManifestMetaContent()
-{
- Config &config = Config::instance();
- const QStringList names =
- config.getStringList(CONFIG_MANIFESTMETA + Config::dot + QStringLiteral("filters"));
-
- for (const auto &manifest : names) {
- ManifestMetaFilter filter;
- QString prefix = CONFIG_MANIFESTMETA + Config::dot + manifest + Config::dot;
- filter.names = config.getStringSet(prefix + QStringLiteral("names"));
- filter.attributes = config.getStringSet(prefix + QStringLiteral("attributes"));
- filter.tags = config.getStringSet(prefix + QStringLiteral("tags"));
- m_manifestMetaContent.append(filter);
- }
-}
-
QT_END_NAMESPACE
diff --git a/src/qdoc/htmlgenerator.h b/src/qdoc/htmlgenerator.h
index 83a3248a7..07cc4c0d7 100644
--- a/src/qdoc/htmlgenerator.h
+++ b/src/qdoc/htmlgenerator.h
@@ -42,6 +42,7 @@ class Aggregate;
class Config;
class ExampleNode;
class HelpProjectWriter;
+class ManifestWriter;
class HtmlGenerator : public XmlGenerator
{
@@ -53,7 +54,6 @@ public:
void terminateGenerator() override;
QString format() override;
void generateDocs() override;
- void generateManifestFiles();
QString protectEnc(const QString &string);
static QString protect(const QString &string);
@@ -69,9 +69,6 @@ protected:
void generateCollectionNode(CollectionNode *cn, CodeMarker *marker) override;
void generateGenericCollectionPage(CollectionNode *cn, CodeMarker *marker) override;
QString fileExtension() const override;
-
- void generateManifestFile(const QString &manifest, const QString &element);
- void readManifestMetaContent();
void generateKeywordAnchors(const Node *node);
private:
@@ -85,7 +82,6 @@ private:
QSet<QString> tags;
};
- QString retrieveInstallPath(const ExampleNode *exampleNode);
void generateNavigationBar(const QString &title, const Node *node, CodeMarker *marker,
const QString &buildversion, bool tableItems = false);
void generateHeader(const QString &title, const Node *node = nullptr,
@@ -164,6 +160,7 @@ private:
QString m_codePrefix {};
QString m_codeSuffix {};
HelpProjectWriter *m_helpProjectWriter { nullptr };
+ ManifestWriter *m_manifestWriter { nullptr };
bool m_inObsoleteLink { false };
QRegularExpression m_funcLeftParen { "\\S(\\()" };
QString m_headerScripts {};
diff --git a/src/qdoc/manifestwriter.cpp b/src/qdoc/manifestwriter.cpp
new file mode 100644
index 000000000..d19c6d6ff
--- /dev/null
+++ b/src/qdoc/manifestwriter.cpp
@@ -0,0 +1,328 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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 General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** 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-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include "manifestwriter.h"
+
+#include "config.h"
+#include "examplenode.h"
+#include "generator.h"
+#include "qdocdatabase.h"
+
+#include <QtCore/qxmlstream.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class ManifestWriter
+ \internal
+ \brief The ManifestWriter is responsible for writing manifest files.
+ */
+ManifestWriter::ManifestWriter()
+{
+ Config &config = Config::instance();
+ m_project = config.getString(CONFIG_PROJECT);
+ m_outputDirectory = config.getOutputDir();
+ m_qdb = QDocDatabase::qdocDB();
+
+ const QString prefix = CONFIG_QHP + Config::dot + m_project + Config::dot;
+ m_manifestDir =
+ QLatin1String("qthelp://") + config.getString(prefix + QLatin1String("namespace"));
+ m_manifestDir += QLatin1Char('/') + config.getString(prefix + QLatin1String("virtualFolder"))
+ + QLatin1Char('/');
+ readManifestMetaContent();
+ m_examplesPath = config.getString(CONFIG_EXAMPLESINSTALLPATH);
+ if (!m_examplesPath.isEmpty())
+ m_examplesPath += QLatin1Char('/');
+}
+
+/*!
+ This function outputs one or more manifest files in XML.
+ They are used by Creator.
+ */
+void ManifestWriter::generateManifestFiles()
+{
+ generateManifestFile("examples", "example");
+ generateManifestFile("demos", "demo");
+ m_qdb->exampleNodeMap().clear();
+ m_manifestMetaContent.clear();
+}
+
+/*!
+ This function is called by generateManifestFiles(), once
+ for each manifest file to be generated. \a manifest is the
+ type of manifest file.
+ */
+void ManifestWriter::generateManifestFile(const QString &manifest, const QString &element)
+{
+ const ExampleNodeMap &exampleNodeMap = m_qdb->exampleNodeMap();
+ if (exampleNodeMap.isEmpty())
+ return;
+ const QString fileName = manifest + "-manifest.xml";
+ QFile file(m_outputDirectory + QLatin1Char('/') + fileName);
+ bool demos = false;
+ if (manifest == QLatin1String("demos"))
+ demos = true;
+
+ bool proceed = false;
+ for (auto map = exampleNodeMap.begin(); map != exampleNodeMap.end(); ++map) {
+ const ExampleNode *en = map.value();
+ if (demos == en->name().startsWith("demos")) {
+ proceed = true;
+ break;
+ }
+ }
+ if (!proceed || !file.open(QFile::WriteOnly | QFile::Text))
+ return;
+
+ QXmlStreamWriter writer(&file);
+ writer.setAutoFormatting(true);
+ writer.writeStartDocument();
+ writer.writeStartElement("instructionals");
+ writer.writeAttribute("module", m_project);
+ writer.writeStartElement(manifest);
+
+ QStringList usedAttributes;
+ for (auto map = exampleNodeMap.begin(); map != exampleNodeMap.end(); ++map) {
+ const ExampleNode *en = map.value();
+ if (demos) {
+ if (!en->name().startsWith("demos"))
+ continue;
+ } else if (en->name().startsWith("demos")) {
+ continue;
+ }
+
+ const QString installPath = retrieveInstallPath(en);
+ // attributes that are always written for the element
+ usedAttributes.clear();
+ usedAttributes << "name"
+ << "docUrl"
+ << "projectPath";
+
+ writer.writeStartElement(element);
+ writer.writeAttribute("name", en->title());
+ QString docUrl = m_manifestDir + Generator::fileBase(en) + ".html";
+ writer.writeAttribute("docUrl", docUrl);
+ const auto exampleFiles = en->files();
+ if (en->projectFile().isEmpty())
+ Location().warning("Example does not have a project file: ", en->name());
+ else
+ writer.writeAttribute("projectPath", installPath + en->projectFile());
+ if (en->imageFileName().isEmpty()) {
+ Location().warning("Example does not have an image file: ", en->name());
+ } else {
+ writer.writeAttribute("imageUrl", m_manifestDir + en->imageFileName());
+ usedAttributes << "imageUrl";
+ }
+
+ QString fullName = m_project + QLatin1Char('/') + en->title();
+ QSet<QString> tags;
+ for (const auto &index : m_manifestMetaContent) {
+ const auto &names = index.names;
+ for (const QString &name : names) {
+ bool match;
+ int wildcard = name.indexOf(QChar('*'));
+ switch (wildcard) {
+ case -1: // no wildcard, exact match
+ match = (fullName == name);
+ break;
+ case 0: // '*' matches all
+ match = true;
+ break;
+ default: // match with wildcard at the end
+ match = fullName.startsWith(name.left(wildcard));
+ }
+ if (match) {
+ tags += index.tags;
+ const auto attributes = index.attributes;
+ for (const QString &attr : attributes) {
+ QLatin1Char div(':');
+ QStringList attrList = attr.split(div);
+ if (attrList.count() == 1)
+ attrList.append(QStringLiteral("true"));
+ QString attrName = attrList.takeFirst();
+ if (!usedAttributes.contains(attrName)) {
+ writer.writeAttribute(attrName, attrList.join(div));
+ usedAttributes << attrName;
+ }
+ }
+ }
+ }
+ }
+
+ writer.writeStartElement("description");
+ Text brief = en->doc().briefText();
+ if (!brief.isEmpty())
+ writer.writeCDATA(brief.toString());
+ else
+ writer.writeCDATA(QString("No description available"));
+ writer.writeEndElement(); // description
+
+ // Add words from module name as tags
+ // QtQuickControls -> qt,quick,controls
+ // QtOpenGL -> qt,opengl
+ QRegularExpression re("([A-Z]+[a-z0-9]*(3D|GL)?)");
+ int pos = 0;
+ QRegularExpressionMatch match;
+ while ((match = re.match(m_project, pos)).hasMatch()) {
+ tags << match.captured(1).toLower();
+ pos = match.capturedEnd();
+ }
+
+ // Include tags added via \meta {tag} {tag1[,tag2,...]}
+ // within \example topic
+ const QStringMultiMap *metaTagMap = en->doc().metaTagMap();
+ if (metaTagMap) {
+ for (const auto &tag : metaTagMap->values("tag")) {
+ const auto &tagList = tag.toLower().split(QLatin1Char(','));
+ tags += QSet<QString>(tagList.cbegin(), tagList.cend());
+ }
+ }
+
+ const auto &titleWords = en->title().toLower().split(QLatin1Char(' '));
+ tags += QSet<QString>(titleWords.cbegin(), titleWords.cend());
+
+ // Clean up tags, exclude invalid and common words
+ QSet<QString>::iterator tag_it = tags.begin();
+ QSet<QString> modified;
+ while (tag_it != tags.end()) {
+ QString s = *tag_it;
+ if (s.at(0) == '(')
+ s.remove(0, 1).chop(1);
+ if (s.endsWith(QLatin1Char(':')))
+ s.chop(1);
+
+ if (s.length() < 2 || s.at(0).isDigit() || s.at(0) == '-' || s == QLatin1String("qt")
+ || s == QLatin1String("the") || s == QLatin1String("and")
+ || s.startsWith(QLatin1String("example")) || s.startsWith(QLatin1String("chapter")))
+ tag_it = tags.erase(tag_it);
+ else if (s != *tag_it) {
+ modified << s;
+ tag_it = tags.erase(tag_it);
+ } else
+ ++tag_it;
+ }
+ tags += modified;
+
+ if (!tags.isEmpty()) {
+ writer.writeStartElement("tags");
+ bool wrote_one = false;
+ QStringList sortedTags = tags.values();
+ sortedTags.sort();
+ for (const auto &tag : qAsConst(sortedTags)) {
+ if (wrote_one)
+ writer.writeCharacters(",");
+ writer.writeCharacters(tag);
+ wrote_one = true;
+ }
+ writer.writeEndElement(); // tags
+ }
+
+ QString exampleName = en->name().mid(en->name().lastIndexOf('/') + 1);
+ QMap<int, QString> filesToOpen;
+ const auto files = en->files();
+ for (const QString &file : files) {
+ QFileInfo fileInfo(file);
+ QString fileName = fileInfo.fileName().toLower();
+ // open .qml, .cpp and .h files with a
+ // basename matching the example (project) name
+ // QMap key indicates the priority -
+ // the lowest value will be the top-most file
+ if ((fileInfo.baseName().compare(exampleName, Qt::CaseInsensitive) == 0)) {
+ if (fileName.endsWith(".qml"))
+ filesToOpen.insert(0, file);
+ else if (fileName.endsWith(".cpp"))
+ filesToOpen.insert(1, file);
+ else if (fileName.endsWith(".h"))
+ filesToOpen.insert(2, file);
+ }
+ // main.qml takes precedence over main.cpp
+ else if (fileName.endsWith("main.qml")) {
+ filesToOpen.insert(3, file);
+ } else if (fileName.endsWith("main.cpp")) {
+ filesToOpen.insert(4, file);
+ }
+ }
+
+ for (auto it = filesToOpen.constEnd(); it != filesToOpen.constBegin();) {
+ writer.writeStartElement("fileToOpen");
+ if (--it == filesToOpen.constBegin()) {
+ writer.writeAttribute(QStringLiteral("mainFile"), QStringLiteral("true"));
+ }
+ writer.writeCharacters(installPath + it.value());
+ writer.writeEndElement();
+ }
+
+ writer.writeEndElement(); // example
+ }
+
+ writer.writeEndElement(); // examples
+ writer.writeEndElement(); // instructionals
+ writer.writeEndDocument();
+ file.close();
+}
+
+/*!
+ Reads metacontent - additional attributes and tags to apply
+ when generating manifest files, read from config.
+
+ The manifest metacontent map is cleared immediately after
+ the manifest files have been generated.
+ */
+void ManifestWriter::readManifestMetaContent()
+{
+ Config &config = Config::instance();
+ const QStringList names =
+ config.getStringList(CONFIG_MANIFESTMETA + Config::dot + QStringLiteral("filters"));
+
+ for (const auto &manifest : names) {
+ ManifestMetaFilter filter;
+ QString prefix = CONFIG_MANIFESTMETA + Config::dot + manifest + Config::dot;
+ filter.names = config.getStringSet(prefix + QStringLiteral("names"));
+ filter.attributes = config.getStringSet(prefix + QStringLiteral("attributes"));
+ filter.tags = config.getStringSet(prefix + QStringLiteral("tags"));
+ m_manifestMetaContent.append(filter);
+ }
+}
+
+/*!
+ Retrieve the install path for the \a example as specified with
+ the \meta command, or fall back to the one defined in .qdocconf.
+ */
+QString ManifestWriter::retrieveInstallPath(const ExampleNode *example)
+{
+ QString installPath;
+ if (example->doc().metaTagMap())
+ installPath = example->doc().metaTagMap()->value(QLatin1String("installpath"));
+ if (installPath.isEmpty())
+ installPath = m_examplesPath;
+ if (!installPath.isEmpty() && !installPath.endsWith(QLatin1Char('/')))
+ installPath += QLatin1Char('/');
+
+ return installPath;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/manifestwriter.h b/src/qdoc/manifestwriter.h
new file mode 100644
index 000000000..907a7742c
--- /dev/null
+++ b/src/qdoc/manifestwriter.h
@@ -0,0 +1,67 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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 General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** 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-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef MANIFESTWRITER_H
+#define MANIFESTWRITER_H
+
+#include <QtCore/qlist.h>
+#include <QtCore/qset.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class ExampleNode;
+class QDocDatabase;
+
+class ManifestWriter
+{
+ struct ManifestMetaFilter
+ {
+ QSet<QString> names;
+ QSet<QString> attributes;
+ QSet<QString> tags;
+ };
+
+public:
+ ManifestWriter();
+ void generateManifestFiles();
+ void generateManifestFile(const QString &manifest, const QString &element);
+ void readManifestMetaContent();
+ QString retrieveInstallPath(const ExampleNode *example);
+
+private:
+ QString m_manifestDir {};
+ QString m_examplesPath {};
+ QString m_outputDirectory {};
+ QString m_project {};
+ QDocDatabase *m_qdb { nullptr };
+ QList<ManifestMetaFilter> m_manifestMetaContent {};
+};
+
+QT_END_NAMESPACE
+
+#endif // MANIFESTWRITER_H
diff --git a/src/qdoc/qdoc.pro b/src/qdoc/qdoc.pro
index a1f47300d..a6a31eb77 100644
--- a/src/qdoc/qdoc.pro
+++ b/src/qdoc/qdoc.pro
@@ -62,6 +62,7 @@ HEADERS += access.h \
location.h \
loggingcategory.h \
macro.h \
+ manifestwriter.h \
namespacenode.h \
node.h \
openedlist.h \
@@ -118,6 +119,7 @@ SOURCES += aggregate.cpp \
htmlgenerator.cpp \
location.cpp \
main.cpp \
+ manifestwriter.cpp \
namespacenode.cpp \
node.cpp \
openedlist.cpp \