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

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