Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
manifestwriter.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
4
5#include "config.h"
6#include "examplenode.h"
7#include "generator.h"
8#include "qdocdatabase.h"
9
10#include <QtCore/qmap.h>
11#include <QtCore/qset.h>
12#include <QtCore/qxmlstream.h>
13
15
16/*!
17 \internal
18
19 For each attribute in a map of attributes, checks if the attribute is
20 found in \a usedAttributes. If it is not found, issues a warning specific
21 to the attribute.
22 */
23void warnAboutUnusedAttributes(const QStringList &usedAttributes, const ExampleNode *example)
24{
25 QMap<QString, QString> attributesToWarnFor;
26 bool missingImageWarning = Config::instance().get(CONFIG_EXAMPLES + Config::dot + CONFIG_WARNABOUTMISSINGIMAGES).asBool();
27 bool missingProjectFileWarning = Config::instance().get(CONFIG_EXAMPLES + Config::dot + CONFIG_WARNABOUTMISSINGPROJECTFILES).asBool();
28
29 if (missingImageWarning)
30 attributesToWarnFor.insert(QStringLiteral("imageUrl"),
31 QStringLiteral("Example documentation should have at least one '\\image'"));
32 if (missingProjectFileWarning)
33 attributesToWarnFor.insert(QStringLiteral("projectPath"),
34 QStringLiteral("Example has no project file"));
35
36 if (attributesToWarnFor.empty())
37 return;
38
39 for (auto it = attributesToWarnFor.cbegin(); it != attributesToWarnFor.cend(); ++it) {
40 if (!usedAttributes.contains(it.key()))
41 example->doc().location().warning(example->name() + ": " + it.value());
42 }
43}
44
45/*!
46 \internal
47
48 Write the description element. The description for an example is set
49 with the \brief command. If no brief is available, the element is set
50 to "No description available".
51 */
52
53void writeDescription(QXmlStreamWriter *writer, const ExampleNode *example)
54{
55 Q_ASSERT(writer && example);
56 writer->writeStartElement("description");
57 const Text brief = example->doc().briefText();
58 if (!brief.isEmpty())
59 writer->writeCDATA(brief.toString());
60 else
61 writer->writeCDATA(QString("No description available"));
62 writer->writeEndElement(); // description
63}
64
65/*!
66 \internal
67
68 Returns a list of \a files that Qt Creator should open for the \a exampleName.
69 */
70QMap<int, QString> getFilesToOpen(const QStringList &files, const QString &exampleName)
71{
72 QMap<int, QString> filesToOpen;
73 for (const QString &file : files) {
74 QFileInfo fileInfo(file);
75 QString fileName = fileInfo.fileName().toLower();
76 // open .qml, .cpp and .h files with a
77 // basename matching the example (project) name
78 // QMap key indicates the priority -
79 // the lowest value will be the top-most file
80 if ((fileInfo.baseName().compare(exampleName, Qt::CaseInsensitive) == 0)) {
81 if (fileName.endsWith(".qml"))
82 filesToOpen.insert(0, file);
83 else if (fileName.endsWith(".cpp"))
84 filesToOpen.insert(1, file);
85 else if (fileName.endsWith(".h"))
86 filesToOpen.insert(2, file);
87 }
88 // main.qml takes precedence over main.cpp
89 else if (fileName.endsWith("main.qml")) {
90 filesToOpen.insert(3, file);
91 } else if (fileName.endsWith("main.cpp")) {
92 filesToOpen.insert(4, file);
93 }
94 }
95
96 return filesToOpen;
97}
98
99/*!
100 \internal
101 \brief Writes the lists of files to open for the example.
102
103 Writes out the \a filesToOpen and the full \a installPath through \a writer.
104 */
105void writeFilesToOpen(QXmlStreamWriter &writer, const QString &installPath,
106 const QMap<int, QString> &filesToOpen)
107{
108 for (auto it = filesToOpen.constEnd(); it != filesToOpen.constBegin();) {
109 writer.writeStartElement("fileToOpen");
110 if (--it == filesToOpen.constBegin()) {
111 writer.writeAttribute(QStringLiteral("mainFile"), QStringLiteral("true"));
112 }
113 writer.writeCharacters(installPath + it.value());
114 writer.writeEndElement();
115 }
116}
117
118/*!
119 \internal
120 \brief Writes example metadata into \a writer.
121
122 For instance,
123
124
125 \ meta category {Application Example}
126
127 becomes
128
129 <meta>
130 <entry name="category">Application Example</entry>
131 <meta>
132*/
133static void writeMetaInformation(QXmlStreamWriter &writer, const QStringMultiMap &map)
134{
135 if (map.isEmpty())
136 return;
137
138 writer.writeStartElement("meta");
139 for (auto it = map.constBegin(); it != map.constEnd(); ++it) {
140 writer.writeStartElement("entry");
141 writer.writeAttribute(QStringLiteral("name"), it.key());
142 writer.writeCharacters(it.value());
143 writer.writeEndElement(); // tag
144 }
145 writer.writeEndElement(); // meta
146}
147
148/*!
149 \class ManifestWriter
150 \internal
151 \brief The ManifestWriter is responsible for writing manifest files.
152 */
154{
155 Config &config = Config::instance();
156 m_project = config.get(CONFIG_PROJECT).asString();
157 m_outputDirectory = config.getOutputDir();
159
160 const QString prefix = CONFIG_QHP + Config::dot + m_project + Config::dot;
161 m_manifestDir =
162 QLatin1String("qthelp://") + config.get(prefix + QLatin1String("namespace")).asString();
163 m_manifestDir +=
164 QLatin1Char('/') + config.get(prefix + QLatin1String("virtualFolder")).asString()
165 + QLatin1Char('/');
167 m_examplesPath = config.get(CONFIG_EXAMPLESINSTALLPATH).asString();
168 if (!m_examplesPath.isEmpty())
169 m_examplesPath += QLatin1Char('/');
170}
171
172template <typename F>
173void ManifestWriter::processManifestMetaContent(const QString &fullName, F matchFunc)
174{
175 for (const auto &index : m_manifestMetaContent) {
176 const auto &names = index.m_names;
177 for (const QString &name : names) {
178 bool match;
179 qsizetype wildcard = name.indexOf(QChar('*'));
180 switch (wildcard) {
181 case -1: // no wildcard used, exact match required
182 match = (fullName == name);
183 break;
184 case 0: // '*' matches all examples
185 match = true;
186 break;
187 default: // match with wildcard at the end
188 match = fullName.startsWith(name.left(wildcard));
189 }
190 if (match)
191 matchFunc(index);
192 }
193 }
194}
195
196/*!
197 This function outputs one or more manifest files in XML.
198 They are used by Creator.
199 */
201{
203 m_qdb->exampleNodeMap().clear();
204 m_manifestMetaContent.clear();
205}
206
207/*
208 Returns Qt module name as lower case tag, stripping Qt prefix:
209 QtQuickControls -> quickcontrols
210 QtOpenGL -> opengl
211 QtQuick3D -> quick3d
212 */
213static QString moduleNameAsTag(const QString &module)
214{
215 QString moduleName = module;
216 if (moduleName.startsWith("Qt"))
217 moduleName = moduleName.mid(2);
218 // Some examples are in QtDoc module, but 'doc' as tag makes little sense
219 if (moduleName == "Doc")
220 return QString();
221 return moduleName.toLower();
222}
223
224/*
225 Return tags that were added with
226 \ meta {tag} {tag1[,tag2,...]}
227 or
228 \ meta {tags} {tag1[,tag2,...]}
229 from example metadata
230 */
232{
233 Q_ASSERT(example);
234
235 QSet<QString> tags;
236 const QStringMultiMap *metaTagMap = example->doc().metaTagMap();
237 if (metaTagMap) {
238 QStringList originalTags = metaTagMap->values("tag");
239 originalTags << metaTagMap->values("tags");
240 for (const auto &tag : originalTags) {
241 const auto &tagList = tag.toLower().split(QLatin1Char(','), Qt::SkipEmptyParts);
242 tags += QSet<QString>(tagList.constBegin(), tagList.constEnd());
243 }
244 }
245 return tags;
246}
247
248/*
249 Writes the contents of tags into writer, formatted as
250 <tags>tag1,tag2..</tags>
251 */
252static void writeTagsElement(QXmlStreamWriter *writer, const QSet<QString> &tags)
253{
254 Q_ASSERT(writer);
255 if (tags.isEmpty())
256 return;
257
258 writer->writeStartElement("tags");
259 QStringList sortedTags = tags.values();
260 sortedTags.sort();
261 writer->writeCharacters(sortedTags.join(","));
262 writer->writeEndElement(); // tags
263}
264
265/*!
266 This function is called by generateExampleManifestFiles(), once
267 for each manifest file to be generated.
268 */
270{
271 const ExampleNodeMap &exampleNodeMap = m_qdb->exampleNodeMap();
272 if (exampleNodeMap.isEmpty())
273 return;
274
275 const QString outputFileName = "examples-manifest.xml";
276 QFile outputFile(m_outputDirectory + QLatin1Char('/') + outputFileName);
277 if (!outputFile.open(QFile::WriteOnly | QFile::Text))
278 return;
279
280 QXmlStreamWriter writer(&outputFile);
281 writer.setAutoFormatting(true);
282 writer.writeStartDocument();
283 writer.writeStartElement("instructionals");
284 writer.writeAttribute("module", m_project);
285 writer.writeStartElement("examples");
286
287 for (const auto &example : exampleNodeMap.values()) {
288 QMap<QString, QString> usedAttributes;
289 QSet<QString> tags;
290 const QString installPath = retrieveExampleInstallationPath(example);
291 const QString fullName = m_project + QLatin1Char('/') + example->title();
292
293 processManifestMetaContent(
294 fullName, [&](const ManifestMetaFilter &filter) { tags += filter.m_tags; });
295 tags += tagsAddedWithMetaCommand(example);
296 // omit from the manifest if explicitly marked broken
297 if (tags.contains("broken"))
298 continue;
299
300 // attributes that are always written for the element
301 usedAttributes.insert("name", example->title());
302 usedAttributes.insert("docUrl", m_manifestDir + Generator::currentGenerator()->fileBase(example) + ".html");
303
304 if (!example->projectFile().isEmpty())
305 usedAttributes.insert("projectPath", installPath + example->projectFile());
306 if (!example->imageFileName().isEmpty())
307 usedAttributes.insert("imageUrl", m_manifestDir + example->imageFileName());
308
309 processManifestMetaContent(fullName, [&](const ManifestMetaFilter &filter) {
310 const auto attributes = filter.m_attributes;
311 for (const auto &attribute : attributes) {
312 const QLatin1Char div(':');
313 QStringList attrList = attribute.split(div);
314 if (attrList.size() == 1)
315 attrList.append(QStringLiteral("true"));
316 QString attrName = attrList.takeFirst();
317 if (!usedAttributes.contains(attrName))
318 usedAttributes.insert(attrName, attrList.join(div));
319 }
320 });
321
322 writer.writeStartElement("example");
323 for (auto it = usedAttributes.cbegin(); it != usedAttributes.cend(); ++it)
324 writer.writeAttribute(it.key(), it.value());
325
326 warnAboutUnusedAttributes(usedAttributes.keys(), example);
327 writeDescription(&writer, example);
328
329 const QString moduleNameTag = moduleNameAsTag(m_project);
330 if (!moduleNameTag.isEmpty())
331 tags << moduleNameTag;
332 writeTagsElement(&writer, tags);
333
334 const QString exampleName = example->name().mid(example->name().lastIndexOf('/') + 1);
335 const auto files = example->files();
336 const QMap<int, QString> filesToOpen = getFilesToOpen(files, exampleName);
337 writeFilesToOpen(writer, installPath, filesToOpen);
338
339 if (const QStringMultiMap *metaTagMapP = example->doc().metaTagMap()) {
340 // Write \meta elements into the XML, except for 'tag', 'installpath',
341 // as they are handled separately
342 QStringMultiMap map = *metaTagMapP;
343 erase_if(map, [](QStringMultiMap::iterator iter) {
344 return iter.key() == "tag" || iter.key() == "tags" || iter.key() == "installpath";
345 });
346 writeMetaInformation(writer, map);
347 }
348
349 writer.writeEndElement(); // example
350 }
351
352 writer.writeEndElement(); // examples
353
354 if (!m_exampleCategories.isEmpty()) {
355 writer.writeStartElement("categories");
356 for (const auto &examplecategory : m_exampleCategories) {
357 writer.writeStartElement("category");
358 writer.writeCharacters(examplecategory);
359 writer.writeEndElement();
360 }
361 writer.writeEndElement(); // categories
362 }
363
364 writer.writeEndElement(); // instructionals
365 writer.writeEndDocument();
366 outputFile.close();
367}
368
369/*!
370 Reads metacontent - additional attributes and tags to apply
371 when generating manifest files, read from config.
372
373 The manifest metacontent map is cleared immediately after
374 the manifest files have been generated.
375 */
377{
378 Config &config = Config::instance();
379 const QStringList names{config.get(CONFIG_MANIFESTMETA
380 + Config::dot
381 + QStringLiteral("filters")).asStringList()};
382
383 for (const auto &manifest : names) {
384 ManifestMetaFilter filter;
385 QString prefix = CONFIG_MANIFESTMETA + Config::dot + manifest + Config::dot;
386 filter.m_names = config.get(prefix + QStringLiteral("names")).asStringSet();
387 filter.m_attributes = config.get(prefix + QStringLiteral("attributes")).asStringSet();
388 filter.m_tags = config.get(prefix + QStringLiteral("tags")).asStringSet();
389 m_manifestMetaContent.append(filter);
390 }
391
392 m_exampleCategories = config.get(CONFIG_MANIFESTMETA
393 + QStringLiteral(".examplecategories")).asStringList();
394}
395
396/*!
397 Retrieve the install path for the \a example as specified with
398 the \\meta command, or fall back to the one defined in .qdocconf.
399 */
401{
402 QString installPath;
403 if (example->doc().metaTagMap())
404 installPath = example->doc().metaTagMap()->value(QLatin1String("installpath"));
405 if (installPath.isEmpty())
406 installPath = m_examplesPath;
407 if (!installPath.isEmpty() && !installPath.endsWith(QLatin1Char('/')))
408 installPath += QLatin1Char('/');
409
410 return installPath;
411}
412
413QT_END_NAMESPACE
The Config class contains the configuration variables for controlling how qdoc produces documentation...
Definition config.h:84
The ManifestWriter is responsible for writing manifest files.
void generateManifestFiles()
This function outputs one or more manifest files in XML.
void readManifestMetaContent()
Reads metacontent - additional attributes and tags to apply when generating manifest files,...
void generateExampleManifestFile()
This function is called by generateExampleManifestFiles(), once for each manifest file to be generate...
QString retrieveExampleInstallationPath(const ExampleNode *example) const
Retrieve the install path for the example as specified with the \meta command, or fall back to the on...
const Doc & doc() const
Returns a reference to the node's Doc data member.
Definition node.h:271
This class provides exclusive access to the qdoc database, which consists of a forrest of trees and a...
static QDocDatabase * qdocDB()
Creates the singleton.
ExampleNodeMap & exampleNodeMap()
Definition text.h:12
bool isEmpty() const
Definition text.h:31
#define CONFIG_QHP
Definition config.h:375
#define CONFIG_EXAMPLES
Definition config.h:336
#define CONFIG_WARNABOUTMISSINGPROJECTFILES
Definition config.h:399
#define CONFIG_PROJECT
Definition config.h:373
#define CONFIG_EXAMPLESINSTALLPATH
Definition config.h:337
#define CONFIG_WARNABOUTMISSINGIMAGES
Definition config.h:398
#define CONFIG_MANIFESTMETA
Definition config.h:363
QMultiMap< QString, QString > QStringMultiMap
Definition doc.h:28
static QSet< QString > tagsAddedWithMetaCommand(const ExampleNode *example)
static QString moduleNameAsTag(const QString &module)
static void writeMetaInformation(QXmlStreamWriter &writer, const QStringMultiMap &map)
Writes example metadata into writer.
static void writeTagsElement(QXmlStreamWriter *writer, const QSet< QString > &tags)
QMap< int, QString > getFilesToOpen(const QStringList &files, const QString &exampleName)
void writeFilesToOpen(QXmlStreamWriter &writer, const QString &installPath, const QMap< int, QString > &filesToOpen)
Writes the lists of files to open for the example.
QT_BEGIN_NAMESPACE void warnAboutUnusedAttributes(const QStringList &usedAttributes, const ExampleNode *example)
void writeDescription(QXmlStreamWriter *writer, const ExampleNode *example)
command. If no brief is available, the element is set to "No description available".
Combined button and popup list for selecting options.
QMultiMap< QString, const ExampleNode * > ExampleNodeMap
Definition tree.h:53