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
3
4#include "qdocindexfiles.h"
5
6#include "access.h"
7#include "atom.h"
8#include "classnode.h"
9#include "collectionnode.h"
10#include "comparisoncategory.h"
11#include "config.h"
12#include "enumnode.h"
13#include "qmlenumnode.h"
14#include "examplenode.h"
15#include "externalpagenode.h"
16#include "functionnode.h"
17#include "generator.h"
18#include "genustypes.h"
19#include "headernode.h"
20#include "location.h"
21#include "utilities.h"
22#include "propertynode.h"
23#include "qdocdatabase.h"
24#include "qmlpropertynode.h"
25#include "typedefnode.h"
26#include "variablenode.h"
27
28#include <QtCore/qxmlstream.h>
29
30#include <algorithm>
31
32QT_BEGIN_NAMESPACE
33
34enum QDocAttr {
35 QDocAttrNone,
36 QDocAttrExample,
37 QDocAttrFile,
38 QDocAttrImage,
39 QDocAttrDocument,
40 QDocAttrExternalPage,
41 QDocAttrAttribution
42};
43
44static Node *root_ = nullptr;
45static IndexSectionWriter *post_ = nullptr;
46
47/*!
48 \class QDocIndexFiles
49
50 This class handles qdoc index files.
51 */
52
53QDocIndexFiles *QDocIndexFiles::s_qdocIndexFiles = nullptr;
54
55/*!
56 Constructs the singleton QDocIndexFiles.
57 */
58QDocIndexFiles::QDocIndexFiles() : m_gen(nullptr)
59{
60 m_qdb = QDocDatabase::qdocDB();
61 m_storeLocationInfo = Config::instance().get(CONFIG_LOCATIONINFO).asBool();
62}
63
64/*!
65 Destroys the singleton QDocIndexFiles.
66 */
67QDocIndexFiles::~QDocIndexFiles()
68{
69 m_qdb = nullptr;
70 m_gen = nullptr;
71}
72
73/*!
74 Creates the singleton. Allows only one instance of the class
75 to be created. Returns a pointer to the singleton.
76 */
77QDocIndexFiles *QDocIndexFiles::qdocIndexFiles()
78{
79 if (s_qdocIndexFiles == nullptr)
80 s_qdocIndexFiles = new QDocIndexFiles;
81 return s_qdocIndexFiles;
82}
83
84/*!
85 Destroys the singleton.
86 */
87void QDocIndexFiles::destroyQDocIndexFiles()
88{
89 if (s_qdocIndexFiles != nullptr) {
90 delete s_qdocIndexFiles;
91 s_qdocIndexFiles = nullptr;
92 }
93}
94
95/*!
96 Reads and parses the list of index files in \a indexFiles.
97 */
98void QDocIndexFiles::readIndexes(const QStringList &indexFiles)
99{
100 for (const QString &file : indexFiles) {
101 qCDebug(lcQdoc) << "Loading index file: " << file;
102 readIndexFile(path: file);
103 }
104}
105
106/*!
107 Reads and parses the index file at \a path.
108 */
109void QDocIndexFiles::readIndexFile(const QString &path)
110{
111 QFile file(path);
112 if (!file.open(flags: QFile::ReadOnly)) {
113 qWarning() << "Could not read index file" << path;
114 return;
115 }
116
117 QXmlStreamReader reader(&file);
118 reader.setNamespaceProcessing(false);
119
120 if (!reader.readNextStartElement())
121 return;
122
123 if (reader.name() != QLatin1String("INDEX"))
124 return;
125
126 QXmlStreamAttributes attrs = reader.attributes();
127
128 QString indexUrl {attrs.value(qualifiedName: QLatin1String("url")).toString()};
129
130 // Decide how we link to nodes loaded from this index file:
131 // If building a set that will be installed AND the URL of
132 // the dependency is identical to ours, assume that also
133 // the dependent html files are available under the same
134 // directory tree. Otherwise, link using the full index URL.
135 if (!Config::installDir.isEmpty() && indexUrl == Config::instance().get(CONFIG_URL).asString()) {
136 // Generate a relative URL between the install dir and the index file
137 // when the -installdir command line option is set.
138 QDir installDir(path.section(asep: '/', astart: 0, aend: -3) + '/' + Generator::outputSubdir());
139 indexUrl = installDir.relativeFilePath(fileName: path).section(asep: '/', astart: 0, aend: -2);
140 }
141 m_project = attrs.value(qualifiedName: QLatin1String("project")).toString();
142 QString indexTitle = attrs.value(qualifiedName: QLatin1String("indexTitle")).toString();
143 m_basesList.clear();
144 m_relatedNodes.clear();
145
146 NamespaceNode *root = m_qdb->newIndexTree(module: m_project);
147 if (!root) {
148 qWarning() << "Issue parsing index tree" << path;
149 return;
150 }
151
152 root->tree()->setIndexTitle(indexTitle);
153
154 // Scan all elements in the XML file, constructing a map that contains
155 // base classes for each class found.
156 while (reader.readNextStartElement()) {
157 readIndexSection(reader, current: root, indexUrl);
158 }
159
160 // Now that all the base classes have been found for this index,
161 // arrange them into an inheritance hierarchy.
162 resolveIndex();
163}
164
165/*!
166 Read a <section> element from the index file and create the
167 appropriate node(s).
168 */
169void QDocIndexFiles::readIndexSection(QXmlStreamReader &reader, Node *current,
170 const QString &indexUrl)
171{
172 QXmlStreamAttributes attributes = reader.attributes();
173 QStringView elementName = reader.name();
174
175 QString name = attributes.value(qualifiedName: QLatin1String("name")).toString();
176 QString href = attributes.value(qualifiedName: QLatin1String("href")).toString();
177 Node *node;
178 Location location;
179 Aggregate *parent = nullptr;
180 bool hasReadChildren = false;
181
182 if (current->isAggregate())
183 parent = static_cast<Aggregate *>(current);
184
185 if (attributes.hasAttribute(qualifiedName: QLatin1String("related"))) {
186 bool isIntTypeRelatedValue = false;
187 int relatedIndex = attributes.value(qualifiedName: QLatin1String("related")).toInt(ok: &isIntTypeRelatedValue);
188 if (isIntTypeRelatedValue) {
189 if (adoptRelatedNode(adoptiveParent: parent, index: relatedIndex)) {
190 reader.skipCurrentElement();
191 return;
192 }
193 } else {
194 QList<Node *>::iterator nodeIterator =
195 std::find_if(first: m_relatedNodes.begin(), last: m_relatedNodes.end(), pred: [&](const Node *relatedNode) {
196 return (name == relatedNode->name() && href == relatedNode->url().section(asep: QLatin1Char('/'), astart: -1));
197 });
198
199 if (nodeIterator != m_relatedNodes.end() && parent) {
200 parent->adoptChild(child: *nodeIterator);
201 reader.skipCurrentElement();
202 return;
203 }
204 }
205 }
206
207 QString filePath;
208 int lineNo = 0;
209 if (attributes.hasAttribute(qualifiedName: QLatin1String("filepath"))) {
210 filePath = attributes.value(qualifiedName: QLatin1String("filepath")).toString();
211 lineNo = attributes.value(qualifiedName: "lineno").toInt();
212 }
213 if (elementName == QLatin1String("namespace")) {
214 auto *namespaceNode = new NamespaceNode(parent, name);
215 node = namespaceNode;
216 if (!indexUrl.isEmpty())
217 location = Location(indexUrl + QLatin1Char('/') + name.toLower() + ".html");
218 else if (!indexUrl.isNull())
219 location = Location(name.toLower() + ".html");
220 } else if (elementName == QLatin1String("class") || elementName == QLatin1String("struct")
221 || elementName == QLatin1String("union")) {
222 NodeType type = NodeType::Class;
223 if (elementName == QLatin1String("class"))
224 type = NodeType::Class;
225 else if (elementName == QLatin1String("struct"))
226 type = NodeType::Struct;
227 else if (elementName == QLatin1String("union"))
228 type = NodeType::Union;
229 node = new ClassNode(type, parent, name);
230 if (attributes.hasAttribute(qualifiedName: QLatin1String("bases"))) {
231 QString bases = attributes.value(qualifiedName: QLatin1String("bases")).toString();
232 if (!bases.isEmpty())
233 m_basesList.append(
234 t: std::pair<ClassNode *, QString>(static_cast<ClassNode *>(node), bases));
235 }
236 if (!indexUrl.isEmpty())
237 location = Location(indexUrl + QLatin1Char('/') + name.toLower() + ".html");
238 else if (!indexUrl.isNull())
239 location = Location(name.toLower() + ".html");
240 bool abstract = false;
241 if (attributes.value(qualifiedName: QLatin1String("abstract")) == QLatin1String("true"))
242 abstract = true;
243 node->setAbstract(abstract);
244 } else if (elementName == QLatin1String("header")) {
245 node = new HeaderNode(parent, name);
246
247 if (attributes.hasAttribute(qualifiedName: QLatin1String("location")))
248 name = attributes.value(qualifiedName: QLatin1String("location")).toString();
249
250 if (!indexUrl.isEmpty())
251 location = Location(indexUrl + QLatin1Char('/') + name);
252 else if (!indexUrl.isNull())
253 location = Location(name);
254 } else if (parent && ((elementName == QLatin1String("qmlclass") || elementName == QLatin1String("qmlvaluetype")
255 || elementName == QLatin1String("qmlbasictype")))) {
256 auto *qmlTypeNode = new QmlTypeNode(parent, name,
257 elementName == QLatin1String("qmlclass") ? NodeType::QmlType : NodeType::QmlValueType);
258 qmlTypeNode->setTitle(attributes.value(qualifiedName: QLatin1String("title")).toString());
259 QString logicalModuleName = attributes.value(qualifiedName: QLatin1String("qml-module-name")).toString();
260 if (!logicalModuleName.isEmpty())
261 m_qdb->addToQmlModule(name: logicalModuleName, node: qmlTypeNode);
262 bool abstract = false;
263 if (attributes.value(qualifiedName: QLatin1String("abstract")) == QLatin1String("true"))
264 abstract = true;
265 qmlTypeNode->setAbstract(abstract);
266 QString qmlFullBaseName = attributes.value(qualifiedName: QLatin1String("qml-base-type")).toString();
267 if (!qmlFullBaseName.isEmpty()) {
268 qmlTypeNode->setQmlBaseName(qmlFullBaseName);
269 }
270 if (attributes.hasAttribute(qualifiedName: QLatin1String("location")))
271 name = attributes.value(qualifiedName: "location").toString();
272 if (!indexUrl.isEmpty())
273 location = Location(indexUrl + QLatin1Char('/') + name);
274 else if (!indexUrl.isNull())
275 location = Location(name);
276 node = qmlTypeNode;
277 } else if (parent && elementName == QLatin1String("qmlproperty")) {
278 QString type = attributes.value(qualifiedName: QLatin1String("type")).toString();
279 bool attached = false;
280 if (attributes.value(qualifiedName: QLatin1String("attached")) == QLatin1String("true"))
281 attached = true;
282 bool readonly = false;
283 if (attributes.value(qualifiedName: QLatin1String("writable")) == QLatin1String("false"))
284 readonly = true;
285 auto *qmlPropertyNode = new QmlPropertyNode(parent, name, std::move(type), attached);
286 qmlPropertyNode->markReadOnly(flag: readonly);
287 if (attributes.value(qualifiedName: QLatin1String("required")) == QLatin1String("true"))
288 qmlPropertyNode->setRequired();
289 node = qmlPropertyNode;
290 } else if (elementName == QLatin1String("group")) {
291 auto *collectionNode = m_qdb->addGroup(name);
292 collectionNode->setTitle(attributes.value(qualifiedName: QLatin1String("title")).toString());
293 collectionNode->setSubtitle(attributes.value(qualifiedName: QLatin1String("subtitle")).toString());
294 if (attributes.value(qualifiedName: QLatin1String("seen")) == QLatin1String("true"))
295 collectionNode->markSeen();
296 node = collectionNode;
297 } else if (elementName == QLatin1String("module")) {
298 auto *collectionNode = m_qdb->addModule(name);
299 collectionNode->setTitle(attributes.value(qualifiedName: QLatin1String("title")).toString());
300 collectionNode->setSubtitle(attributes.value(qualifiedName: QLatin1String("subtitle")).toString());
301 if (attributes.value(qualifiedName: QLatin1String("seen")) == QLatin1String("true"))
302 collectionNode->markSeen();
303 node = collectionNode;
304 } else if (elementName == QLatin1String("qmlmodule")) {
305 auto *collectionNode = m_qdb->addQmlModule(name);
306 const QStringList info = QStringList()
307 << name
308 << QString(attributes.value(qualifiedName: QLatin1String("qml-module-version")).toString());
309 collectionNode->setLogicalModuleInfo(info);
310 collectionNode->setTitle(attributes.value(qualifiedName: QLatin1String("title")).toString());
311 collectionNode->setSubtitle(attributes.value(qualifiedName: QLatin1String("subtitle")).toString());
312 if (attributes.value(qualifiedName: QLatin1String("seen")) == QLatin1String("true"))
313 collectionNode->markSeen();
314 node = collectionNode;
315 } else if (elementName == QLatin1String("page")) {
316 QDocAttr subtype = QDocAttrNone;
317 QString attr = attributes.value(qualifiedName: QLatin1String("subtype")).toString();
318 if (attr == QLatin1String("attribution")) {
319 subtype = QDocAttrAttribution;
320 } else if (attr == QLatin1String("example")) {
321 subtype = QDocAttrExample;
322 } else if (attr == QLatin1String("file")) {
323 subtype = QDocAttrFile;
324 } else if (attr == QLatin1String("image")) {
325 subtype = QDocAttrImage;
326 } else if (attr == QLatin1String("page")) {
327 subtype = QDocAttrDocument;
328 } else if (attr == QLatin1String("externalpage")) {
329 subtype = QDocAttrExternalPage;
330 } else
331 goto done;
332
333 if (current->isExample()) {
334 auto *exampleNode = static_cast<ExampleNode *>(current);
335 if (subtype == QDocAttrFile) {
336 exampleNode->appendFile(file&: name);
337 goto done;
338 } else if (subtype == QDocAttrImage) {
339 exampleNode->appendImage(image&: name);
340 goto done;
341 }
342 }
343 PageNode *pageNode = nullptr;
344 if (subtype == QDocAttrExample)
345 pageNode = new ExampleNode(parent, name);
346 else if (subtype == QDocAttrExternalPage)
347 pageNode = new ExternalPageNode(parent, name);
348 else {
349 pageNode = new PageNode(parent, name);
350 if (subtype == QDocAttrAttribution) pageNode->markAttribution();
351 }
352
353 pageNode->setTitle(attributes.value(qualifiedName: QLatin1String("title")).toString());
354
355 if (attributes.hasAttribute(qualifiedName: QLatin1String("location")))
356 name = attributes.value(qualifiedName: QLatin1String("location")).toString();
357
358 if (!indexUrl.isEmpty())
359 location = Location(indexUrl + QLatin1Char('/') + name);
360 else if (!indexUrl.isNull())
361 location = Location(name);
362
363 node = pageNode;
364
365 } else if (elementName == QLatin1String("enum") || elementName == QLatin1String("qmlenum")) {
366 EnumNode *enumNode;
367 if (elementName == QLatin1String("enum"))
368 enumNode = new EnumNode(parent, name, attributes.hasAttribute(qualifiedName: "scoped"));
369 else
370 enumNode = new QmlEnumNode(parent, name);
371
372 if (!indexUrl.isEmpty())
373 location = Location(indexUrl + QLatin1Char('/') + parent->name().toLower() + ".html");
374 else if (!indexUrl.isNull())
375 location = Location(parent->name().toLower() + ".html");
376
377 while (reader.readNextStartElement()) {
378 QXmlStreamAttributes childAttributes = reader.attributes();
379 if (reader.name() == QLatin1String("value")) {
380 EnumItem item(childAttributes.value(qualifiedName: QLatin1String("name")).toString(),
381 childAttributes.value(qualifiedName: QLatin1String("value")).toString(),
382 childAttributes.value(qualifiedName: QLatin1String("since")).toString()
383 );
384 enumNode->addItem(item);
385 } else if (reader.name() == QLatin1String("keyword")) {
386 insertTarget(type: TargetRec::Keyword, attributes: childAttributes, node: enumNode);
387 } else if (reader.name() == QLatin1String("target")) {
388 insertTarget(type: TargetRec::Target, attributes: childAttributes, node: enumNode);
389 }
390 reader.skipCurrentElement();
391 }
392
393 node = enumNode;
394
395 hasReadChildren = true;
396 } else if (elementName == QLatin1String("typedef")) {
397 if (attributes.hasAttribute(qualifiedName: "aliasedtype"))
398 node = new TypeAliasNode(parent, name, attributes.value(qualifiedName: QLatin1String("aliasedtype")).toString());
399 else
400 node = new TypedefNode(parent, name);
401
402 if (!indexUrl.isEmpty())
403 location = Location(indexUrl + QLatin1Char('/') + parent->name().toLower() + ".html");
404 else if (!indexUrl.isNull())
405 location = Location(parent->name().toLower() + ".html");
406 } else if (elementName == QLatin1String("property")) {
407 auto *propNode = new PropertyNode(parent, name);
408 node = propNode;
409 if (attributes.value(qualifiedName: QLatin1String("bindable")) == QLatin1String("true"))
410 propNode->setPropertyType(PropertyNode::PropertyType::BindableProperty);
411
412 propNode->setWritable(attributes.value(qualifiedName: QLatin1String("writable")) != QLatin1String("false"));
413
414 if (!indexUrl.isEmpty())
415 location = Location(indexUrl + QLatin1Char('/') + parent->name().toLower() + ".html");
416 else if (!indexUrl.isNull())
417 location = Location(parent->name().toLower() + ".html");
418
419 } else if (elementName == QLatin1String("function")) {
420 QString t = attributes.value(qualifiedName: QLatin1String("meta")).toString();
421 bool attached = false;
422 FunctionNode::Metaness metaness = FunctionNode::Plain;
423 if (!t.isEmpty())
424 metaness = FunctionNode::getMetaness(value: t);
425 if (attributes.value(qualifiedName: QLatin1String("attached")) == QLatin1String("true"))
426 attached = true;
427 auto *fn = new FunctionNode(metaness, parent, name, attached);
428
429 fn->setReturnType(attributes.value(qualifiedName: QLatin1String("type")).toString());
430
431 const auto &declaredTypeAttr = attributes.value(qualifiedName: QLatin1String("declaredtype"));
432 if (!declaredTypeAttr.isEmpty())
433 fn->setDeclaredReturnType(declaredTypeAttr.toString());
434
435 if (fn->isCppNode()) {
436 fn->setVirtualness(attributes.value(qualifiedName: QLatin1String("virtual")).toString());
437 fn->setConst(attributes.value(qualifiedName: QLatin1String("const")) == QLatin1String("true"));
438 fn->setStatic(attributes.value(qualifiedName: QLatin1String("static")) == QLatin1String("true"));
439 fn->setFinal(attributes.value(qualifiedName: QLatin1String("final")) == QLatin1String("true"));
440 fn->setOverride(attributes.value(qualifiedName: QLatin1String("override")) == QLatin1String("true"));
441
442 if (attributes.value(qualifiedName: QLatin1String("explicit")) == QLatin1String("true"))
443 fn->markExplicit();
444
445 if (attributes.value(qualifiedName: QLatin1String("constexpr")) == QLatin1String("true"))
446 fn->markConstexpr();
447
448 if (attributes.value(qualifiedName: QLatin1String("noexcept")) == QLatin1String("true")) {
449 fn->markNoexcept(expression: attributes.value(qualifiedName: "noexcept_expression").toString());
450 }
451
452 qsizetype refness = attributes.value(qualifiedName: QLatin1String("refness")).toUInt();
453 if (refness == 1)
454 fn->setRef(true);
455 else if (refness == 2)
456 fn->setRefRef(true);
457 /*
458 Theoretically, this should ensure that each function
459 node receives the same overload number and overload
460 flag it was written with, and it should be unnecessary
461 to call normalizeOverloads() for index nodes.
462 */
463 if (attributes.value(qualifiedName: QLatin1String("overload")) == QLatin1String("true"))
464 fn->setOverloadNumber(attributes.value(qualifiedName: QLatin1String("overload-number")).toUInt());
465 else
466 fn->setOverloadNumber(0);
467 }
468
469 /*
470 Note: The "signature" attribute was written to the
471 index file, but it is not read back in. That is ok
472 because we reconstruct the parameter list and the
473 return type, from which the signature was built in
474 the first place and from which it can be rebuilt.
475 */
476 while (reader.readNextStartElement()) {
477 QXmlStreamAttributes childAttributes = reader.attributes();
478 if (reader.name() == QLatin1String("parameter")) {
479 // Do not use the default value for the parameter; it is not
480 // required, and has been known to cause problems.
481 QString type = childAttributes.value(qualifiedName: QLatin1String("type")).toString();
482 QString name = childAttributes.value(qualifiedName: QLatin1String("name")).toString();
483 fn->parameters().append(type, name);
484 } else if (reader.name() == QLatin1String("keyword")) {
485 insertTarget(type: TargetRec::Keyword, attributes: childAttributes, node: fn);
486 } else if (reader.name() == QLatin1String("target")) {
487 insertTarget(type: TargetRec::Target, attributes: childAttributes, node: fn);
488 }
489 reader.skipCurrentElement();
490 }
491
492 node = fn;
493 if (!indexUrl.isEmpty())
494 location = Location(indexUrl + QLatin1Char('/') + parent->name().toLower() + ".html");
495 else if (!indexUrl.isNull())
496 location = Location(parent->name().toLower() + ".html");
497
498 hasReadChildren = true;
499 } else if (elementName == QLatin1String("variable")) {
500 auto *varNode = new VariableNode(parent, name);
501 varNode->setLeftType(attributes.value(qualifiedName: "type").toString());
502 varNode->setStatic((attributes.value(qualifiedName: "static").toString() == "true") ? true : false);
503 node = varNode;
504 if (!indexUrl.isEmpty())
505 location = Location(indexUrl + QLatin1Char('/') + parent->name().toLower() + ".html");
506 else if (!indexUrl.isNull())
507 location = Location(parent->name().toLower() + ".html");
508 } else if (elementName == QLatin1String("keyword")) {
509 insertTarget(type: TargetRec::Keyword, attributes, node: current);
510 goto done;
511 } else if (elementName == QLatin1String("target")) {
512 insertTarget(type: TargetRec::Target, attributes, node: current);
513 goto done;
514 } else if (elementName == QLatin1String("contents")) {
515 insertTarget(type: TargetRec::Contents, attributes, node: current);
516 goto done;
517 } else if (elementName == QLatin1String("proxy")) {
518 node = new ProxyNode(parent, name);
519 if (!indexUrl.isEmpty())
520 location = Location(indexUrl + QLatin1Char('/') + name.toLower() + ".html");
521 else if (!indexUrl.isNull())
522 location = Location(name.toLower() + ".html");
523 } else {
524 goto done;
525 }
526
527 {
528 if (!href.isEmpty()) {
529 node->setUrl(href);
530 // Include the index URL if it exists
531 if (!node->isExternalPage() && !indexUrl.isEmpty())
532 node->setUrl(indexUrl + QLatin1Char('/') + href);
533 }
534
535 const QString access = attributes.value(qualifiedName: QLatin1String("access")).toString();
536 if (access == "protected")
537 node->setAccess(Access::Protected);
538 else if ((access == "private") || (access == "internal"))
539 node->setAccess(Access::Private);
540 else
541 node->setAccess(Access::Public);
542
543 if (attributes.hasAttribute(qualifiedName: QLatin1String("related"))) {
544 node->setRelatedNonmember(true);
545 m_relatedNodes << node;
546 }
547
548 if (attributes.hasAttribute(qualifiedName: QLatin1String("threadsafety"))) {
549 QString threadSafety = attributes.value(qualifiedName: QLatin1String("threadsafety")).toString();
550 if (threadSafety == QLatin1String("non-reentrant"))
551 node->setThreadSafeness(Node::NonReentrant);
552 else if (threadSafety == QLatin1String("reentrant"))
553 node->setThreadSafeness(Node::Reentrant);
554 else if (threadSafety == QLatin1String("thread safe"))
555 node->setThreadSafeness(Node::ThreadSafe);
556 else
557 node->setThreadSafeness(Node::UnspecifiedSafeness);
558 } else
559 node->setThreadSafeness(Node::UnspecifiedSafeness);
560
561 const QString category = attributes.value(qualifiedName: QLatin1String("comparison_category")).toString();
562 node->setComparisonCategory(comparisonCategoryFromString(string: category.toStdString()));
563
564 QString status = attributes.value(qualifiedName: QLatin1String("status")).toString();
565 // TODO: "obsolete" is kept for backward compatibility, remove in the near future
566 if (status == QLatin1String("obsolete") || status == QLatin1String("deprecated"))
567 node->setStatus(Node::Deprecated);
568 else if (status == QLatin1String("preliminary"))
569 node->setStatus(Node::Preliminary);
570 else if (status == QLatin1String("internal"))
571 node->setStatus(Node::Internal);
572 else if (status == QLatin1String("ignored"))
573 node->setStatus(Node::DontDocument);
574 else
575 node->setStatus(Node::Active);
576
577 QString physicalModuleName = attributes.value(qualifiedName: QLatin1String("module")).toString();
578 if (!physicalModuleName.isEmpty())
579 m_qdb->addToModule(name: physicalModuleName, node);
580
581 QString since = attributes.value(qualifiedName: QLatin1String("since")).toString();
582 if (!since.isEmpty()) {
583 node->setSince(since);
584 }
585
586 if (attributes.hasAttribute(qualifiedName: QLatin1String("documented"))) {
587 if (attributes.value(qualifiedName: QLatin1String("documented")) == QLatin1String("true"))
588 node->setHadDoc();
589 }
590
591 QString groupsAttr = attributes.value(qualifiedName: QLatin1String("groups")).toString();
592 if (!groupsAttr.isEmpty()) {
593 const QStringList groupNames = groupsAttr.split(sep: QLatin1Char(','));
594 for (const auto &group : groupNames) {
595 m_qdb->addToGroup(name: group, node);
596 }
597 }
598
599 // Create some content for the node.
600 QSet<QString> emptySet;
601 Location t(filePath);
602 if (!filePath.isEmpty()) {
603 t.setLineNo(lineNo);
604 node->setLocation(t);
605 location = t;
606 }
607 Doc doc(location, location, QString(), emptySet, emptySet); // placeholder
608 node->setDoc(doc);
609 node->setIndexNodeFlag(); // Important: This node came from an index file.
610 QString briefAttr = attributes.value(qualifiedName: QLatin1String("brief")).toString();
611 if (!briefAttr.isEmpty()) {
612 node->setReconstitutedBrief(briefAttr);
613 }
614
615 if (const auto sortKey = attributes.value(qualifiedName: QLatin1String("sortkey")).toString(); !sortKey.isEmpty()) {
616 node->doc().constructExtra();
617 if (auto *metaMap = node->doc().metaTagMap())
618 metaMap->insert(key: "sortkey", value: sortKey);
619 }
620 if (!hasReadChildren) {
621 bool useParent = (elementName == QLatin1String("namespace") && name.isEmpty());
622 while (reader.readNextStartElement()) {
623 if (useParent)
624 readIndexSection(reader, current: parent, indexUrl);
625 else
626 readIndexSection(reader, current: node, indexUrl);
627 }
628 }
629 }
630
631done:
632 while (!reader.isEndElement()) {
633 if (reader.readNext() == QXmlStreamReader::Invalid) {
634 break;
635 }
636 }
637}
638
639void QDocIndexFiles::insertTarget(TargetRec::TargetType type,
640 const QXmlStreamAttributes &attributes, Node *node)
641{
642 int priority;
643 switch (type) {
644 case TargetRec::Keyword:
645 priority = 1;
646 break;
647 case TargetRec::Target:
648 priority = 2;
649 break;
650 case TargetRec::Contents:
651 priority = 3;
652 break;
653 default:
654 return;
655 }
656
657 QString name = attributes.value(qualifiedName: QLatin1String("name")).toString();
658 QString title = attributes.value(qualifiedName: QLatin1String("title")).toString();
659 m_qdb->insertTarget(name, title, type, node, priority);
660}
661
662/*!
663 This function tries to resolve class inheritance immediately
664 after the index file is read. It is not always possible to
665 resolve a class inheritance at this point, because the base
666 class might be in an index file that hasn't been read yet, or
667 it might be in one of the header files that will be read for
668 the current module. These cases will be resolved after all
669 the index files and header and source files have been read,
670 just prior to beginning the generate phase for the current
671 module.
672
673 I don't think this is completely correct because it always
674 sets the access to public.
675 */
676void QDocIndexFiles::resolveIndex()
677{
678 for (const auto &pair : std::as_const(t&: m_basesList)) {
679 const QStringList bases = pair.second.split(sep: QLatin1Char(','));
680 for (const auto &base : bases) {
681 QStringList basePath = base.split(sep: QString("::"));
682 Node *n = m_qdb->findClassNode(path: basePath);
683 if (n)
684 pair.first->addResolvedBaseClass(access: Access::Public, node: static_cast<ClassNode *>(n));
685 else
686 pair.first->addUnresolvedBaseClass(access: Access::Public, path: basePath);
687 }
688 }
689 // No longer needed.
690 m_basesList.clear();
691}
692
693static QString getAccessString(Access t)
694{
695
696 switch (t) {
697 case Access::Public:
698 return QLatin1String("public");
699 case Access::Protected:
700 return QLatin1String("protected");
701 case Access::Private:
702 return QLatin1String("private");
703 default:
704 break;
705 }
706 return QLatin1String("public");
707}
708
709static QString getStatusString(Node::Status t)
710{
711 switch (t) {
712 case Node::Deprecated:
713 return QLatin1String("deprecated");
714 case Node::Preliminary:
715 return QLatin1String("preliminary");
716 case Node::Active:
717 return QLatin1String("active");
718 case Node::Internal:
719 return QLatin1String("internal");
720 case Node::DontDocument:
721 return QLatin1String("ignored");
722 default:
723 break;
724 }
725 return QLatin1String("active");
726}
727
728static QString getThreadSafenessString(Node::ThreadSafeness t)
729{
730 switch (t) {
731 case Node::NonReentrant:
732 return QLatin1String("non-reentrant");
733 case Node::Reentrant:
734 return QLatin1String("reentrant");
735 case Node::ThreadSafe:
736 return QLatin1String("thread safe");
737 case Node::UnspecifiedSafeness:
738 default:
739 break;
740 }
741 return QLatin1String("unspecified");
742}
743
744/*!
745 Returns the index of \a node in the list of related non-member nodes.
746*/
747int QDocIndexFiles::indexForNode(Node *node)
748{
749 qsizetype i = m_relatedNodes.indexOf(t: node);
750 if (i == -1) {
751 i = m_relatedNodes.size();
752 m_relatedNodes << node;
753 }
754 return i;
755}
756
757/*!
758 Adopts the related non-member node identified by \a index to the
759 parent \a adoptiveParent. Returns \c true if successful.
760*/
761bool QDocIndexFiles::adoptRelatedNode(Aggregate *adoptiveParent, int index)
762{
763 Node *related = m_relatedNodes.value(i: index);
764
765 if (adoptiveParent && related) {
766 adoptiveParent->adoptChild(child: related);
767 return true;
768 }
769
770 return false;
771}
772
773/*!
774 Write canonicalized versions of \\target and \\keyword identifiers
775 that appear in the documentation of \a node into the index using
776 \a writer, so that they can be used as link targets in external
777 documentation sets.
778*/
779void QDocIndexFiles::writeTargets(QXmlStreamWriter &writer, Node *node)
780{
781 if (node->doc().hasTargets()) {
782 for (const Atom *target : std::as_const(t: node->doc().targets())) {
783 const QString &title = target->string();
784 const QString &name{Utilities::asAsciiPrintable(name: title)};
785 writer.writeStartElement(qualifiedName: "target");
786 writer.writeAttribute(qualifiedName: "name", value: node->isExternalPage() ? title : name);
787 if (name != title)
788 writer.writeAttribute(qualifiedName: "title", value: title);
789 writer.writeEndElement(); // target
790 }
791 }
792 if (node->doc().hasKeywords()) {
793 for (const Atom *keyword : std::as_const(t: node->doc().keywords())) {
794 const QString &title = keyword->string();
795 const QString &name{Utilities::asAsciiPrintable(name: title)};
796 writer.writeStartElement(qualifiedName: "keyword");
797 writer.writeAttribute(qualifiedName: "name", value: name);
798 if (name != title)
799 writer.writeAttribute(qualifiedName: "title", value: title);
800 writer.writeEndElement(); // keyword
801 }
802 }
803}
804
805/*!
806 Generate the index section with the given \a writer for the \a node
807 specified, returning true if an element was written, and returning
808 false if an element is not written.
809
810 \note Function nodes are processed in generateFunctionSection()
811 */
812bool QDocIndexFiles::generateIndexSection(QXmlStreamWriter &writer, Node *node,
813 IndexSectionWriter *post)
814{
815 if (m_gen == nullptr)
816 m_gen = Generator::currentGenerator();
817
818 Q_ASSERT(m_gen);
819
820 post_ = nullptr;
821 /*
822 Don't include index nodes in a new index file.
823 */
824 if (node->isIndexNode())
825 return false;
826
827 QString nodeName;
828 QString logicalModuleName;
829 QString logicalModuleVersion;
830 QString qmlFullBaseName;
831 QString baseNameAttr;
832 QString moduleNameAttr;
833 QString moduleVerAttr;
834
835 switch (node->nodeType()) {
836 case NodeType::Namespace:
837 nodeName = "namespace";
838 break;
839 case NodeType::Class:
840 nodeName = "class";
841 break;
842 case NodeType::Struct:
843 nodeName = "struct";
844 break;
845 case NodeType::Union:
846 nodeName = "union";
847 break;
848 case NodeType::HeaderFile:
849 nodeName = "header";
850 break;
851 case NodeType::QmlEnum:
852 nodeName = "qmlenum";
853 break;
854 case NodeType::QmlType:
855 case NodeType::QmlValueType:
856 nodeName = (node->nodeType() == NodeType::QmlType) ? "qmlclass" : "qmlvaluetype";
857 logicalModuleName = node->logicalModuleName();
858 baseNameAttr = "qml-base-type";
859 moduleNameAttr = "qml-module-name";
860 moduleVerAttr = "qml-module-version";
861 qmlFullBaseName = node->qmlFullBaseName();
862 break;
863 case NodeType::Page:
864 case NodeType::Example:
865 case NodeType::ExternalPage:
866 nodeName = "page";
867 break;
868 case NodeType::Group:
869 nodeName = "group";
870 break;
871 case NodeType::Module:
872 nodeName = "module";
873 break;
874 case NodeType::QmlModule:
875 nodeName = "qmlmodule";
876 moduleNameAttr = "qml-module-name";
877 moduleVerAttr = "qml-module-version";
878 logicalModuleName = node->logicalModuleName();
879 logicalModuleVersion = node->logicalModuleVersion();
880 break;
881 case NodeType::Enum:
882 nodeName = "enum";
883 break;
884 case NodeType::TypeAlias:
885 case NodeType::Typedef:
886 nodeName = "typedef";
887 break;
888 case NodeType::Property:
889 nodeName = "property";
890 break;
891 case NodeType::Variable:
892 nodeName = "variable";
893 break;
894 case NodeType::SharedComment:
895 if (!node->isPropertyGroup())
896 return false;
897 // Add an entry for property groups so that they can be linked to
898 nodeName = "qmlproperty";
899 break;
900 case NodeType::QmlProperty:
901 nodeName = "qmlproperty";
902 break;
903 case NodeType::Proxy:
904 nodeName = "proxy";
905 break;
906 case NodeType::Function: // Now processed in generateFunctionSection()
907 default:
908 return false;
909 }
910
911 QString objName = node->name();
912 // Special case: only the root node should have an empty name.
913 if (objName.isEmpty() && node != m_qdb->primaryTreeRoot())
914 return false;
915
916 writer.writeStartElement(qualifiedName: nodeName);
917
918 if (!node->isTextPageNode() && !node->isCollectionNode() && !node->isHeader()) {
919 if (node->threadSafeness() != Node::UnspecifiedSafeness)
920 writer.writeAttribute(qualifiedName: "threadsafety", value: getThreadSafenessString(t: node->threadSafeness()));
921 }
922
923 writer.writeAttribute(qualifiedName: "name", value: objName);
924
925 // Write module and base type info for QML types
926 if (!moduleNameAttr.isEmpty()) {
927 if (!logicalModuleName.isEmpty())
928 writer.writeAttribute(qualifiedName: moduleNameAttr, value: logicalModuleName);
929 if (!logicalModuleVersion.isEmpty())
930 writer.writeAttribute(qualifiedName: moduleVerAttr, value: logicalModuleVersion);
931 }
932 if (!baseNameAttr.isEmpty() && !qmlFullBaseName.isEmpty())
933 writer.writeAttribute(qualifiedName: baseNameAttr, value: qmlFullBaseName);
934
935 QString href;
936 if (!node->isExternalPage()) {
937 QString fullName = node->fullDocumentName();
938 if (fullName != objName)
939 writer.writeAttribute(qualifiedName: "fullname", value: fullName);
940 href = m_gen->fullDocumentLocation(node);
941 } else
942 href = node->name();
943 if (node->isQmlNode()) {
944 Aggregate *p = node->parent();
945 if (p && p->isQmlType() && p->isAbstract())
946 href.clear();
947 }
948 if (!href.isEmpty())
949 writer.writeAttribute(qualifiedName: "href", value: href);
950
951 writer.writeAttribute(qualifiedName: "status", value: getStatusString(t: node->status()));
952 if (!node->isTextPageNode() && !node->isCollectionNode() && !node->isHeader()) {
953 writer.writeAttribute(qualifiedName: "access", value: getAccessString(t: node->access()));
954 if (node->isAbstract())
955 writer.writeAttribute(qualifiedName: "abstract", value: "true");
956 }
957 const Location &declLocation = node->declLocation();
958 if (!declLocation.fileName().isEmpty())
959 writer.writeAttribute(qualifiedName: "location", value: declLocation.fileName());
960 if (m_storeLocationInfo && !declLocation.filePath().isEmpty()) {
961 writer.writeAttribute(qualifiedName: "filepath", value: declLocation.filePath());
962 writer.writeAttribute(qualifiedName: "lineno", value: QString("%1").arg(a: declLocation.lineNo()));
963 }
964
965 if (node->isRelatedNonmember())
966 writer.writeAttribute(qualifiedName: "related", value: QString::number(indexForNode(node)));
967
968 if (!node->since().isEmpty())
969 writer.writeAttribute(qualifiedName: "since", value: node->since());
970
971 if (node->hasDoc())
972 writer.writeAttribute(qualifiedName: "documented", value: "true");
973
974 QStringList groups = m_qdb->groupNamesForNode(node);
975 if (!groups.isEmpty())
976 writer.writeAttribute(qualifiedName: "groups", value: groups.join(sep: QLatin1Char(',')));
977
978 if (const auto *metamap = node->doc().metaTagMap(); metamap)
979 if (const auto sortKey = metamap->value(key: "sortkey"); !sortKey.isEmpty())
980 writer.writeAttribute(qualifiedName: "sortkey", value: sortKey);
981
982 QString brief = node->doc().trimmedBriefText(className: node->name()).toString();
983 switch (node->nodeType()) {
984 case NodeType::Class:
985 case NodeType::Struct:
986 case NodeType::Union: {
987 // Classes contain information about their base classes.
988 const auto *classNode = static_cast<const ClassNode *>(node);
989 const QList<RelatedClass> &bases = classNode->baseClasses();
990 QSet<QString> baseStrings;
991 for (const auto &related : bases) {
992 ClassNode *n = related.m_node;
993 if (n)
994 baseStrings.insert(value: n->fullName());
995 else if (!related.m_path.isEmpty())
996 baseStrings.insert(value: related.m_path.join(sep: QLatin1String("::")));
997 }
998 if (!baseStrings.isEmpty()) {
999 QStringList baseStringsAsList = baseStrings.values();
1000 baseStringsAsList.sort();
1001 writer.writeAttribute(qualifiedName: "bases", value: baseStringsAsList.join(sep: QLatin1Char(',')));
1002 }
1003 if (!node->physicalModuleName().isEmpty())
1004 writer.writeAttribute(qualifiedName: "module", value: node->physicalModuleName());
1005 if (!brief.isEmpty())
1006 writer.writeAttribute(qualifiedName: "brief", value: brief);
1007 if (auto category = node->comparisonCategory(); category != ComparisonCategory::None)
1008 writer.writeAttribute(qualifiedName: "comparison_category", value: comparisonCategoryAsString(category));
1009 } break;
1010 case NodeType::HeaderFile: {
1011 const auto *headerNode = static_cast<const HeaderNode *>(node);
1012 if (!headerNode->physicalModuleName().isEmpty())
1013 writer.writeAttribute(qualifiedName: "module", value: headerNode->physicalModuleName());
1014 if (!brief.isEmpty())
1015 writer.writeAttribute(qualifiedName: "brief", value: brief);
1016 writer.writeAttribute(qualifiedName: "title", value: headerNode->title());
1017 writer.writeAttribute(qualifiedName: "fulltitle", value: headerNode->fullTitle());
1018 writer.writeAttribute(qualifiedName: "subtitle", value: headerNode->subtitle());
1019 } break;
1020 case NodeType::Namespace: {
1021 const auto *namespaceNode = static_cast<const NamespaceNode *>(node);
1022 if (!namespaceNode->physicalModuleName().isEmpty())
1023 writer.writeAttribute(qualifiedName: "module", value: namespaceNode->physicalModuleName());
1024 if (!brief.isEmpty())
1025 writer.writeAttribute(qualifiedName: "brief", value: brief);
1026 } break;
1027 case NodeType::QmlValueType:
1028 case NodeType::QmlType: {
1029 const auto *qmlTypeNode = static_cast<const QmlTypeNode *>(node);
1030 writer.writeAttribute(qualifiedName: "title", value: qmlTypeNode->title());
1031 writer.writeAttribute(qualifiedName: "fulltitle", value: qmlTypeNode->fullTitle());
1032 writer.writeAttribute(qualifiedName: "subtitle", value: qmlTypeNode->subtitle());
1033 if (!brief.isEmpty())
1034 writer.writeAttribute(qualifiedName: "brief", value: brief);
1035 } break;
1036 case NodeType::Page:
1037 case NodeType::Example:
1038 case NodeType::ExternalPage: {
1039 if (node->isExample())
1040 writer.writeAttribute(qualifiedName: "subtype", value: "example");
1041 else if (node->isExternalPage())
1042 writer.writeAttribute(qualifiedName: "subtype", value: "externalpage");
1043 else
1044 writer.writeAttribute(qualifiedName: "subtype", value: (static_cast<PageNode*>(node)->isAttribution() ? "attribution" : "page"));
1045
1046 const auto *pageNode = static_cast<const PageNode *>(node);
1047 writer.writeAttribute(qualifiedName: "title", value: pageNode->title());
1048 writer.writeAttribute(qualifiedName: "fulltitle", value: pageNode->fullTitle());
1049 writer.writeAttribute(qualifiedName: "subtitle", value: pageNode->subtitle());
1050 if (!brief.isEmpty())
1051 writer.writeAttribute(qualifiedName: "brief", value: brief);
1052 } break;
1053 case NodeType::Group:
1054 case NodeType::Module:
1055 case NodeType::QmlModule: {
1056 const auto *collectionNode = static_cast<const CollectionNode *>(node);
1057 writer.writeAttribute(qualifiedName: "seen", value: collectionNode->wasSeen() ? "true" : "false");
1058 writer.writeAttribute(qualifiedName: "title", value: collectionNode->title());
1059 if (!collectionNode->subtitle().isEmpty())
1060 writer.writeAttribute(qualifiedName: "subtitle", value: collectionNode->subtitle());
1061 if (!collectionNode->physicalModuleName().isEmpty())
1062 writer.writeAttribute(qualifiedName: "module", value: collectionNode->physicalModuleName());
1063 if (!brief.isEmpty())
1064 writer.writeAttribute(qualifiedName: "brief", value: brief);
1065 } break;
1066 case NodeType::QmlProperty: {
1067 auto *qmlPropertyNode = static_cast<QmlPropertyNode *>(node);
1068 writer.writeAttribute(qualifiedName: "type", value: qmlPropertyNode->dataType());
1069 writer.writeAttribute(qualifiedName: "attached", value: qmlPropertyNode->isAttached() ? "true" : "false");
1070 writer.writeAttribute(qualifiedName: "writable", value: qmlPropertyNode->isReadOnly() ? "false" : "true");
1071 if (qmlPropertyNode->isRequired())
1072 writer.writeAttribute(qualifiedName: "required", value: "true");
1073 if (!brief.isEmpty())
1074 writer.writeAttribute(qualifiedName: "brief", value: brief);
1075 } break;
1076 case NodeType::Property: {
1077 const auto *propertyNode = static_cast<const PropertyNode *>(node);
1078
1079 if (propertyNode->propertyType() == PropertyNode::PropertyType::BindableProperty)
1080 writer.writeAttribute(qualifiedName: "bindable", value: "true");
1081
1082 if (!propertyNode->isWritable())
1083 writer.writeAttribute(qualifiedName: "writable", value: "false");
1084
1085 if (!brief.isEmpty())
1086 writer.writeAttribute(qualifiedName: "brief", value: brief);
1087 // Property access function names
1088 for (qsizetype i{0}; i < (qsizetype)PropertyNode::FunctionRole::NumFunctionRoles; ++i) {
1089 auto role{(PropertyNode::FunctionRole)i};
1090 for (const auto *fnNode : propertyNode->functions(role)) {
1091 writer.writeStartElement(qualifiedName: PropertyNode::roleName(role));
1092 writer.writeAttribute(qualifiedName: "name", value: fnNode->name());
1093 writer.writeEndElement();
1094 }
1095 }
1096 } break;
1097 case NodeType::Variable: {
1098 const auto *variableNode = static_cast<const VariableNode *>(node);
1099 writer.writeAttribute(qualifiedName: "type", value: variableNode->dataType());
1100 writer.writeAttribute(qualifiedName: "static", value: variableNode->isStatic() ? "true" : "false");
1101 if (!brief.isEmpty())
1102 writer.writeAttribute(qualifiedName: "brief", value: brief);
1103 } break;
1104 case NodeType::QmlEnum:
1105 case NodeType::Enum: {
1106 const auto *enumNode = static_cast<const EnumNode *>(node);
1107 if (enumNode->isScoped())
1108 writer.writeAttribute(qualifiedName: "scoped", value: "true");
1109 if (enumNode->flagsType())
1110 writer.writeAttribute(qualifiedName: "typedef", value: enumNode->flagsType()->fullDocumentName());
1111 const auto &items = enumNode->items();
1112 for (const auto &item : items) {
1113 writer.writeStartElement(qualifiedName: "value");
1114 writer.writeAttribute(qualifiedName: "name", value: item.name());
1115 if (node->isEnumType(g: Genus::CPP))
1116 writer.writeAttribute(qualifiedName: "value", value: item.value());
1117 if (!item.since().isEmpty())
1118 writer.writeAttribute(qualifiedName: "since", value: item.since());
1119 writer.writeEndElement(); // value
1120 }
1121 } break;
1122 case NodeType::Typedef: {
1123 const auto *typedefNode = static_cast<const TypedefNode *>(node);
1124 if (typedefNode->associatedEnum())
1125 writer.writeAttribute(qualifiedName: "enum", value: typedefNode->associatedEnum()->fullDocumentName());
1126 } break;
1127 case NodeType::TypeAlias:
1128 writer.writeAttribute(qualifiedName: "aliasedtype", value: static_cast<const TypeAliasNode *>(node)->aliasedType());
1129 break;
1130 case NodeType::Function: // Now processed in generateFunctionSection()
1131 default:
1132 break;
1133 }
1134
1135 writeTargets(writer, node);
1136
1137 /*
1138 Some nodes have a table of contents. For these, we close
1139 the opening tag, create sub-elements for the items in the
1140 table of contents, and then add a closing tag for the
1141 element. Elements for all other nodes are closed in the
1142 opening tag.
1143 */
1144 if (node->isPageNode() || node->isCollectionNode()) {
1145 if (node->doc().hasTableOfContents()) {
1146 for (int i = 0; i < node->doc().tableOfContents().size(); ++i) {
1147 Atom *item = node->doc().tableOfContents()[i];
1148 int level = node->doc().tableOfContentsLevels()[i];
1149 QString title = Text::sectionHeading(sectionBegin: item).toString();
1150 writer.writeStartElement(qualifiedName: "contents");
1151 writer.writeAttribute(qualifiedName: "name", value: Tree::refForAtom(atom: item));
1152 writer.writeAttribute(qualifiedName: "title", value: title);
1153 writer.writeAttribute(qualifiedName: "level", value: QString::number(level));
1154 writer.writeEndElement(); // contents
1155 }
1156 }
1157 }
1158 // WebXMLGenerator - skip the nested <page> elements for example
1159 // files/images, as the generator produces them separately
1160 if (node->isExample() && m_gen->format() != QLatin1String("WebXML")) {
1161 const auto *exampleNode = static_cast<const ExampleNode *>(node);
1162 const auto &files = exampleNode->files();
1163 for (const QString &file : files) {
1164 writer.writeStartElement(qualifiedName: "page");
1165 writer.writeAttribute(qualifiedName: "name", value: file);
1166 QString href = m_gen->linkForExampleFile(path: file);
1167 writer.writeAttribute(qualifiedName: "href", value: href);
1168 writer.writeAttribute(qualifiedName: "status", value: "active");
1169 writer.writeAttribute(qualifiedName: "subtype", value: "file");
1170 writer.writeAttribute(qualifiedName: "title", value: "");
1171 writer.writeAttribute(qualifiedName: "fulltitle", value: Generator::exampleFileTitle(relative: exampleNode, fileName: file));
1172 writer.writeAttribute(qualifiedName: "subtitle", value: file);
1173 writer.writeEndElement(); // page
1174 }
1175 const auto &images = exampleNode->images();
1176 for (const QString &file : images) {
1177 writer.writeStartElement(qualifiedName: "page");
1178 writer.writeAttribute(qualifiedName: "name", value: file);
1179 QString href = m_gen->linkForExampleFile(path: file);
1180 writer.writeAttribute(qualifiedName: "href", value: href);
1181 writer.writeAttribute(qualifiedName: "status", value: "active");
1182 writer.writeAttribute(qualifiedName: "subtype", value: "image");
1183 writer.writeAttribute(qualifiedName: "title", value: "");
1184 writer.writeAttribute(qualifiedName: "fulltitle", value: Generator::exampleFileTitle(relative: exampleNode, fileName: file));
1185 writer.writeAttribute(qualifiedName: "subtitle", value: file);
1186 writer.writeEndElement(); // page
1187 }
1188 }
1189 // Append to the section if the callback object was set
1190 if (post)
1191 post->append(writer, node);
1192
1193 post_ = post;
1194 return true;
1195}
1196
1197/*!
1198 This function writes a <function> element for \a fn to the
1199 index file using \a writer.
1200 */
1201void QDocIndexFiles::generateFunctionSection(QXmlStreamWriter &writer, FunctionNode *fn)
1202{
1203 if (fn->isInternal() && !Config::instance().showInternal())
1204 return;
1205
1206 const QString objName = fn->name();
1207 writer.writeStartElement(qualifiedName: "function");
1208 writer.writeAttribute(qualifiedName: "name", value: objName);
1209
1210 const QString fullName = fn->fullDocumentName();
1211 if (fullName != objName)
1212 writer.writeAttribute(qualifiedName: "fullname", value: fullName);
1213 const QString href = m_gen->fullDocumentLocation(node: fn);
1214 if (!href.isEmpty())
1215 writer.writeAttribute(qualifiedName: "href", value: href);
1216 if (fn->threadSafeness() != Node::UnspecifiedSafeness)
1217 writer.writeAttribute(qualifiedName: "threadsafety", value: getThreadSafenessString(t: fn->threadSafeness()));
1218 writer.writeAttribute(qualifiedName: "status", value: getStatusString(t: fn->status()));
1219 writer.writeAttribute(qualifiedName: "access", value: getAccessString(t: fn->access()));
1220
1221 const Location &declLocation = fn->declLocation();
1222 if (!declLocation.fileName().isEmpty())
1223 writer.writeAttribute(qualifiedName: "location", value: declLocation.fileName());
1224 if (m_storeLocationInfo && !declLocation.filePath().isEmpty()) {
1225 writer.writeAttribute(qualifiedName: "filepath", value: declLocation.filePath());
1226 writer.writeAttribute(qualifiedName: "lineno", value: QString("%1").arg(a: declLocation.lineNo()));
1227 }
1228
1229 if (fn->hasDoc())
1230 writer.writeAttribute(qualifiedName: "documented", value: "true");
1231 if (fn->isRelatedNonmember())
1232 writer.writeAttribute(qualifiedName: "related", value: QString::number(indexForNode(node: fn)));
1233 if (!fn->since().isEmpty())
1234 writer.writeAttribute(qualifiedName: "since", value: fn->since());
1235
1236 const QString brief = fn->doc().trimmedBriefText(className: fn->name()).toString();
1237 writer.writeAttribute(qualifiedName: "meta", value: fn->metanessString());
1238 if (fn->isCppNode()) {
1239 if (!fn->isNonvirtual())
1240 writer.writeAttribute(qualifiedName: "virtual", value: fn->virtualness());
1241
1242 if (fn->isConst())
1243 writer.writeAttribute(qualifiedName: "const", value: "true");
1244 if (fn->isStatic())
1245 writer.writeAttribute(qualifiedName: "static", value: "true");
1246 if (fn->isFinal())
1247 writer.writeAttribute(qualifiedName: "final", value: "true");
1248 if (fn->isOverride())
1249 writer.writeAttribute(qualifiedName: "override", value: "true");
1250 if (fn->isExplicit())
1251 writer.writeAttribute(qualifiedName: "explicit", value: "true");
1252 if (fn->isConstexpr())
1253 writer.writeAttribute(qualifiedName: "constexpr", value: "true");
1254
1255 if (auto noexcept_info = fn->getNoexcept()) {
1256 writer.writeAttribute(qualifiedName: "noexcept", value: "true");
1257 if (!(*noexcept_info).isEmpty()) writer.writeAttribute(qualifiedName: "noexcept_expression", value: *noexcept_info);
1258 }
1259
1260 /*
1261 This ensures that for functions that have overloads,
1262 the first function written is the one that is not an
1263 overload, and the overloads follow it immediately in
1264 the index file numbered from 1 to n.
1265 */
1266 if (fn->isOverload() && (fn->overloadNumber() > 0)) {
1267 writer.writeAttribute(qualifiedName: "overload", value: "true");
1268 writer.writeAttribute(qualifiedName: "overload-number", value: QString::number(fn->overloadNumber()));
1269 }
1270 if (fn->isRef())
1271 writer.writeAttribute(qualifiedName: "refness", value: QString::number(1));
1272 else if (fn->isRefRef())
1273 writer.writeAttribute(qualifiedName: "refness", value: QString::number(2));
1274 if (fn->hasAssociatedProperties()) {
1275 QStringList associatedProperties;
1276 for (const auto *node : fn->associatedProperties()) {
1277 associatedProperties << node->name();
1278 }
1279 associatedProperties.sort();
1280 writer.writeAttribute(qualifiedName: "associated-property",
1281 value: associatedProperties.join(sep: QLatin1Char(',')));
1282 }
1283 }
1284
1285 const auto &return_type = fn->returnType();
1286 if (!return_type.isEmpty())
1287 writer.writeAttribute(qualifiedName: "type", value: std::move(return_type));
1288
1289 const auto &declared_return_type = fn->declaredReturnType();
1290 if (declared_return_type.has_value())
1291 writer.writeAttribute(qualifiedName: "declaredtype", value: declared_return_type.value());
1292
1293 if (fn->isCppNode()) {
1294 if (!brief.isEmpty())
1295 writer.writeAttribute(qualifiedName: "brief", value: brief);
1296
1297 /*
1298 Note: The "signature" attribute is written to the
1299 index file, but it is not read back in by qdoc. However,
1300 we need it for the webxml generator.
1301 */
1302 const QString signature = appendAttributesToSignature(fn);
1303 writer.writeAttribute(qualifiedName: "signature", value: signature);
1304
1305 QStringList groups = m_qdb->groupNamesForNode(node: fn);
1306 if (!groups.isEmpty())
1307 writer.writeAttribute(qualifiedName: "groups", value: groups.join(sep: QLatin1Char(',')));
1308 }
1309
1310 for (int i = 0; i < fn->parameters().count(); ++i) {
1311 const Parameter &parameter = fn->parameters().at(i);
1312 writer.writeStartElement(qualifiedName: "parameter");
1313 writer.writeAttribute(qualifiedName: "type", value: parameter.type());
1314 writer.writeAttribute(qualifiedName: "name", value: parameter.name());
1315 writer.writeAttribute(qualifiedName: "default", value: parameter.defaultValue());
1316 writer.writeEndElement(); // parameter
1317 }
1318
1319 writeTargets(writer, node: fn);
1320
1321 // Append to the section if the callback object was set
1322 if (post_)
1323 post_->append(writer, node: fn);
1324
1325 writer.writeEndElement(); // function
1326}
1327
1328/*!
1329 \internal
1330
1331 Constructs the signature to be written to an index file for the function
1332 represented by FunctionNode \a fn.
1333
1334 'const' is already part of FunctionNode::signature(), which forms the basis
1335 for the signature returned by this method. The method adds, where
1336 applicable, the C++ keywords "final", "override", or "= 0", to the
1337 signature carried by the FunctionNode itself.
1338 */
1339QString QDocIndexFiles::appendAttributesToSignature(const FunctionNode *fn) const noexcept
1340{
1341 QString signature = fn->signature(options: Node::SignatureReturnType);
1342
1343 if (fn->isFinal())
1344 signature += " final";
1345 if (fn->isOverride())
1346 signature += " override";
1347 if (fn->isPureVirtual())
1348 signature += " = 0";
1349
1350 return signature;
1351}
1352
1353/*!
1354 Outputs a <function> element to the index for each FunctionNode in
1355 an \a aggregate, using \a writer.
1356 The \a aggregate has a function map that contains all the
1357 function nodes (a vector of overloads) indexed by function
1358 name.
1359
1360 If a function element represents an overload, it has an
1361 \c overload attribute set to \c true and an \c {overload-number}
1362 attribute set to the function's overload number.
1363 */
1364void QDocIndexFiles::generateFunctionSections(QXmlStreamWriter &writer, Aggregate *aggregate)
1365{
1366 for (auto functions : std::as_const(t&: aggregate->functionMap())) {
1367 std::for_each(first: functions.begin(), last: functions.end(),
1368 f: [this,&writer](FunctionNode *fn) {
1369 generateFunctionSection(writer, fn);
1370 }
1371 );
1372 }
1373}
1374
1375/*!
1376 Generate index sections for the child nodes of the given \a node
1377 using the \a writer specified.
1378*/
1379void QDocIndexFiles::generateIndexSections(QXmlStreamWriter &writer, Node *node,
1380 IndexSectionWriter *post)
1381{
1382 /*
1383 Note that groups, modules, QML modules, and proxies are written
1384 after all the other nodes.
1385 */
1386 if (node->isCollectionNode() || node->isGroup() || node->isModule() ||
1387 node->isQmlModule() || node->isProxyNode())
1388 return;
1389
1390 if (node->isInternal() && !Config::instance().showInternal())
1391 return;
1392
1393 if (generateIndexSection(writer, node, post)) {
1394 if (node->isAggregate()) {
1395 auto *aggregate = static_cast<Aggregate *>(node);
1396 // First write the function children, then write the nonfunction children.
1397 generateFunctionSections(writer, aggregate);
1398 const auto &nonFunctionList = aggregate->nonfunctionList();
1399 for (auto *node : nonFunctionList)
1400 generateIndexSections(writer, node, post);
1401 }
1402
1403 if (node == root_) {
1404 /*
1405 We wait until the end of the index file to output the group, module,
1406 QML module, and proxy nodes. By outputting them at the end, when we read
1407 the index file back in, all the group/module/proxy member
1408 nodes will have already been created. It is then only necessary to
1409 create the collection node and add each member to its member list.
1410 */
1411 const CNMap &groups = m_qdb->groups();
1412 if (!groups.isEmpty()) {
1413 for (auto it = groups.constBegin(); it != groups.constEnd(); ++it) {
1414 if (generateIndexSection(writer, node: it.value(), post))
1415 writer.writeEndElement();
1416 }
1417 }
1418
1419 const CNMap &modules = m_qdb->modules();
1420 if (!modules.isEmpty()) {
1421 for (auto it = modules.constBegin(); it != modules.constEnd(); ++it) {
1422 if (generateIndexSection(writer, node: it.value(), post))
1423 writer.writeEndElement();
1424 }
1425 }
1426
1427 const CNMap &qmlModules = m_qdb->qmlModules();
1428 if (!qmlModules.isEmpty()) {
1429 for (auto it = qmlModules.constBegin(); it != qmlModules.constEnd(); ++it) {
1430 if (generateIndexSection(writer, node: it.value(), post))
1431 writer.writeEndElement();
1432 }
1433 }
1434
1435 for (auto *p : m_qdb->primaryTree()->proxies()) {
1436 if (generateIndexSection(writer, node: p, post)) {
1437 auto aggregate = static_cast<Aggregate *>(p);
1438 generateFunctionSections(writer, aggregate);
1439 for (auto *n : aggregate->nonfunctionList())
1440 generateIndexSections(writer, node: n, post);
1441 writer.writeEndElement();
1442 }
1443 }
1444 }
1445
1446 writer.writeEndElement();
1447 }
1448}
1449
1450/*!
1451 Writes a qdoc module index in XML to a file named \a fileName.
1452 \a url is the \c url attribute of the <INDEX> element.
1453 \a title is the \c title attribute of the <INDEX> element.
1454 \a g is a pointer to the current Generator in use, stored for later use.
1455 */
1456void QDocIndexFiles::generateIndex(const QString &fileName, const QString &url,
1457 const QString &title)
1458{
1459 QFile file(fileName);
1460 if (!file.open(flags: QFile::WriteOnly | QFile::Text))
1461 return;
1462
1463 qCDebug(lcQdoc) << "Writing index file:" << fileName;
1464
1465 m_gen = Generator::currentGenerator();
1466 m_relatedNodes.clear();
1467 QXmlStreamWriter writer(&file);
1468 writer.setAutoFormatting(true);
1469 writer.writeStartDocument();
1470 writer.writeDTD(dtd: "<!DOCTYPE QDOCINDEX>");
1471
1472 writer.writeStartElement(qualifiedName: "INDEX");
1473 writer.writeAttribute(qualifiedName: "url", value: url);
1474 writer.writeAttribute(qualifiedName: "title", value: title);
1475 writer.writeAttribute(qualifiedName: "version", value: m_qdb->version());
1476 writer.writeAttribute(qualifiedName: "project", value: Config::instance().get(CONFIG_PROJECT).asString());
1477
1478 root_ = m_qdb->primaryTreeRoot();
1479 if (!root_->tree()->indexTitle().isEmpty())
1480 writer.writeAttribute(qualifiedName: "indexTitle", value: root_->tree()->indexTitle());
1481
1482 generateIndexSections(writer, node: root_, post: nullptr);
1483
1484 writer.writeEndElement(); // INDEX
1485 writer.writeEndElement(); // QDOCINDEX
1486 writer.writeEndDocument();
1487 file.close();
1488}
1489
1490QT_END_NAMESPACE
1491

source code of qttools/src/qdoc/qdoc/src/qdoc/qdocindexfiles.cpp