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