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 ¬ifiers = 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 ¶meter = 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