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