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