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