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 "qdocdatabase.h" |
5 | |
6 | #include "atom.h" |
7 | #include "collectionnode.h" |
8 | #include "functionnode.h" |
9 | #include "generator.h" |
10 | #include "qdocindexfiles.h" |
11 | #include "tree.h" |
12 | |
13 | #include <QtCore/qregularexpression.h> |
14 | #include <stack> |
15 | |
16 | QT_BEGIN_NAMESPACE |
17 | |
18 | static NodeMultiMap emptyNodeMultiMap_; |
19 | |
20 | /*! |
21 | \class QDocForest |
22 | |
23 | A class representing a forest of Tree objects. |
24 | |
25 | This private class manages a collection of Tree objects (a |
26 | forest) for the singleton QDocDatabase object. It is only |
27 | accessed by that singleton QDocDatabase object, which is a |
28 | friend. Each tree in the forest is an instance of class |
29 | Tree, which is a mostly private class. Both QDocForest and |
30 | QDocDatabase are friends of Tree and have full access. |
31 | |
32 | There are two kinds of trees in the forest, differing not |
33 | in structure but in use. One Tree is the primary tree. It |
34 | is the tree representing the module being documented. All |
35 | the other trees in the forest are called index trees. Each |
36 | one represents the contents of the index file for one of |
37 | the modules the current module must be able to link to. |
38 | |
39 | The instances of subclasses of Node in the primary tree |
40 | will contain documentation in an instance of Doc. The |
41 | index trees contain no documentation, and each Node in |
42 | an index tree is marked as an index node. |
43 | |
44 | Each tree is named with the name of its module. |
45 | |
46 | The search order is created by searchOrder(), if it has |
47 | not already been created. The search order and module |
48 | names arrays have parallel structure, i.e. modulNames_[i] |
49 | is the module name of the Tree at searchOrder_[i]. |
50 | |
51 | The primary tree is always the first tree in the search |
52 | order. i.e., when the database is searched, the primary |
53 | tree is always searched first, unless a specific tree is |
54 | being searched. |
55 | */ |
56 | |
57 | /*! |
58 | Destroys the qdoc forest. This requires deleting |
59 | each Tree in the forest. Note that the forest has |
60 | been transferred into the search order array, so |
61 | what is really being used to destroy the forest |
62 | is the search order array. |
63 | */ |
64 | QDocForest::~QDocForest() |
65 | { |
66 | for (auto *entry : m_searchOrder) |
67 | delete entry; |
68 | m_forest.clear(); |
69 | m_searchOrder.clear(); |
70 | m_indexSearchOrder.clear(); |
71 | m_moduleNames.clear(); |
72 | m_primaryTree = nullptr; |
73 | } |
74 | |
75 | /*! |
76 | Initializes the forest prior to a traversal and |
77 | returns a pointer to the primary tree. If the |
78 | forest is empty, it returns \nullptr. |
79 | */ |
80 | Tree *QDocForest::firstTree() |
81 | { |
82 | m_currentIndex = 0; |
83 | return (!searchOrder().isEmpty() ? searchOrder()[0] : nullptr); |
84 | } |
85 | |
86 | /*! |
87 | Increments the forest's current tree index. If the current |
88 | tree index is still within the forest, the function returns |
89 | the pointer to the current tree. Otherwise it returns \nullptr. |
90 | */ |
91 | Tree *QDocForest::nextTree() |
92 | { |
93 | ++m_currentIndex; |
94 | return (m_currentIndex < searchOrder().size() ? searchOrder()[m_currentIndex] : nullptr); |
95 | } |
96 | |
97 | /*! |
98 | \fn Tree *QDocForest::primaryTree() |
99 | |
100 | Returns the pointer to the primary tree. |
101 | */ |
102 | |
103 | /*! |
104 | Finds the tree for module \a t in the forest and |
105 | sets the primary tree to be that tree. After the |
106 | primary tree is set, that tree is removed from the |
107 | forest. |
108 | |
109 | \node It gets re-inserted into the forest after the |
110 | search order is built. |
111 | */ |
112 | void QDocForest::setPrimaryTree(const QString &t) |
113 | { |
114 | QString T = t.toLower(); |
115 | m_primaryTree = findTree(t: T); |
116 | m_forest.remove(key: T); |
117 | if (m_primaryTree == nullptr) |
118 | qCCritical(lcQdoc) << "Error: Could not set primary tree to" << t; |
119 | } |
120 | |
121 | /*! |
122 | If the search order array is empty, create the search order. |
123 | If the search order array is not empty, do nothing. |
124 | */ |
125 | void QDocForest::setSearchOrder(const QStringList &t) |
126 | { |
127 | if (!m_searchOrder.isEmpty()) |
128 | return; |
129 | |
130 | /* Allocate space for the search order. */ |
131 | m_searchOrder.reserve(asize: m_forest.size() + 1); |
132 | m_searchOrder.clear(); |
133 | m_moduleNames.reserve(asize: m_forest.size() + 1); |
134 | m_moduleNames.clear(); |
135 | |
136 | /* The primary tree is always first in the search order. */ |
137 | QString primaryName = primaryTree()->physicalModuleName(); |
138 | m_searchOrder.append(t: m_primaryTree); |
139 | m_moduleNames.append(t: primaryName); |
140 | m_forest.remove(key: primaryName); |
141 | |
142 | for (const QString &m : t) { |
143 | if (primaryName != m) { |
144 | auto it = m_forest.find(key: m); |
145 | if (it != m_forest.end()) { |
146 | m_searchOrder.append(t: it.value()); |
147 | m_moduleNames.append(t: m); |
148 | m_forest.remove(key: m); |
149 | } |
150 | } |
151 | } |
152 | /* |
153 | If any trees remain in the forest, just add them |
154 | to the search order sequentially, because we don't |
155 | know any better at this point. |
156 | */ |
157 | if (!m_forest.isEmpty()) { |
158 | for (auto it = m_forest.begin(); it != m_forest.end(); ++it) { |
159 | m_searchOrder.append(t: it.value()); |
160 | m_moduleNames.append(t: it.key()); |
161 | } |
162 | m_forest.clear(); |
163 | } |
164 | |
165 | /* |
166 | Rebuild the forest after constructing the search order. |
167 | It was destroyed during construction of the search order, |
168 | but it is needed for module-specific searches. |
169 | |
170 | Note that this loop also inserts the primary tree into the |
171 | forrest. That is a requirement. |
172 | */ |
173 | for (int i = 0; i < m_searchOrder.size(); ++i) { |
174 | if (!m_forest.contains(key: m_moduleNames.at(i))) { |
175 | m_forest.insert(key: m_moduleNames.at(i), value: m_searchOrder.at(i)); |
176 | } |
177 | } |
178 | } |
179 | |
180 | /*! |
181 | Returns an ordered array of Tree pointers that represents |
182 | the order in which the trees should be searched. The first |
183 | Tree in the array is the tree for the current module, i.e. |
184 | the module for which qdoc is generating documentation. |
185 | |
186 | The other Tree pointers in the array represent the index |
187 | files that were loaded in preparation for generating this |
188 | module's documentation. Each Tree pointer represents one |
189 | index file. The index file Tree points have been ordered |
190 | heuristically to, hopefully, minimize searching. Thr order |
191 | will probably be changed. |
192 | |
193 | If the search order array is empty, this function calls |
194 | indexSearchOrder(). The search order array is empty while |
195 | the index files are being loaded, but some searches must |
196 | be performed during this time, notably searches for base |
197 | class nodes. These searches require a temporary search |
198 | order. The temporary order changes throughout the loading |
199 | of the index files, but it is always the tree for the |
200 | current index file first, followed by the trees for the |
201 | index files that have already been loaded. The only |
202 | ordering required in this temporary search order is that |
203 | the current tree must be searched first. |
204 | */ |
205 | const QList<Tree *> &QDocForest::searchOrder() |
206 | { |
207 | if (m_searchOrder.isEmpty()) |
208 | return indexSearchOrder(); |
209 | return m_searchOrder; |
210 | } |
211 | |
212 | /*! |
213 | There are two search orders used by qdoc when searching for |
214 | things. The normal search order is returned by searchOrder(), |
215 | but this normal search order is not known until all the index |
216 | files have been read. At that point, setSearchOrder() is |
217 | called. |
218 | |
219 | During the reading of the index files, the vector holding |
220 | the normal search order remains empty. Whenever the search |
221 | order is requested, if that vector is empty, this function |
222 | is called to return a temporary search order, which includes |
223 | all the index files that have been read so far, plus the |
224 | one being read now. That one is prepended to the front of |
225 | the vector. |
226 | */ |
227 | const QList<Tree *> &QDocForest::indexSearchOrder() |
228 | { |
229 | if (m_forest.size() > m_indexSearchOrder.size()) |
230 | m_indexSearchOrder.prepend(t: m_primaryTree); |
231 | return m_indexSearchOrder; |
232 | } |
233 | |
234 | /*! |
235 | Create a new Tree for the index file for the specified |
236 | \a module and add it to the forest. Return the pointer |
237 | to its root. |
238 | */ |
239 | NamespaceNode *QDocForest::newIndexTree(const QString &module) |
240 | { |
241 | m_primaryTree = new Tree(module, m_qdb); |
242 | m_forest.insert(key: module.toLower(), value: m_primaryTree); |
243 | return m_primaryTree->root(); |
244 | } |
245 | |
246 | /*! |
247 | Create a new Tree for use as the primary tree. This tree |
248 | will represent the primary module. \a module is camel case. |
249 | */ |
250 | void QDocForest::newPrimaryTree(const QString &module) |
251 | { |
252 | m_primaryTree = new Tree(module, m_qdb); |
253 | } |
254 | |
255 | /*! |
256 | Searches through the forest for a node named \a targetPath |
257 | and returns a pointer to it if found. The \a relative node |
258 | is the starting point. It only makes sense for the primary |
259 | tree, which is searched first. After the primary tree has |
260 | been searched, \a relative is set to 0 for searching the |
261 | other trees, which are all index trees. With relative set |
262 | to 0, the starting point for each index tree is the root |
263 | of the index tree. |
264 | |
265 | If \a targetPath is resolved successfully but it refers to |
266 | a \\section title, continue the search, keeping the section |
267 | title as a fallback if no higher-priority targets are found. |
268 | */ |
269 | const Node *QDocForest::findNodeForTarget(QStringList &targetPath, const Node *relative, |
270 | Node::Genus genus, QString &ref) |
271 | { |
272 | int flags = SearchBaseClasses | SearchEnumValues; |
273 | |
274 | QString entity = targetPath.takeFirst(); |
275 | QStringList entityPath = entity.split(sep: "::" ); |
276 | |
277 | QString target; |
278 | if (!targetPath.isEmpty()) |
279 | target = targetPath.takeFirst(); |
280 | |
281 | TargetRec::TargetType type = TargetRec::Unknown; |
282 | const Node *tocNode = nullptr; |
283 | for (const auto *tree : searchOrder()) { |
284 | const Node *n = tree->findNodeForTarget(path: entityPath, target, node: relative, flags, genus, ref, targetType: &type); |
285 | if (n) { |
286 | // Targets referring to non-section titles are returned immediately |
287 | if (type != TargetRec::Contents) |
288 | return n; |
289 | if (!tocNode) |
290 | tocNode = n; |
291 | } |
292 | relative = nullptr; |
293 | } |
294 | return tocNode; |
295 | } |
296 | |
297 | /*! |
298 | Finds the FunctionNode for the qualified function name |
299 | in \a path, that also has the specified \a parameters. |
300 | Returns a pointer to the first matching function. |
301 | |
302 | \a relative is a node in the primary tree where the search |
303 | should begin. It is only used when searching the primary |
304 | tree. \a genus can be used to force the search to find a |
305 | C++ function or a QML function. |
306 | */ |
307 | const FunctionNode *QDocForest::findFunctionNode(const QStringList &path, |
308 | const Parameters ¶meters, const Node *relative, |
309 | Node::Genus genus) |
310 | { |
311 | for (const auto *tree : searchOrder()) { |
312 | const FunctionNode *fn = tree->findFunctionNode(path, parameters, relative, genus); |
313 | if (fn) |
314 | return fn; |
315 | relative = nullptr; |
316 | } |
317 | return nullptr; |
318 | } |
319 | |
320 | /*! \class QDocDatabase |
321 | This class provides exclusive access to the qdoc database, |
322 | which consists of a forrest of trees and a lot of maps and |
323 | other useful data structures. |
324 | */ |
325 | |
326 | QDocDatabase *QDocDatabase::s_qdocDB = nullptr; |
327 | NodeMap QDocDatabase::s_typeNodeMap; |
328 | NodeMultiMap QDocDatabase::s_obsoleteClasses; |
329 | NodeMultiMap QDocDatabase::s_classesWithObsoleteMembers; |
330 | NodeMultiMap QDocDatabase::s_obsoleteQmlTypes; |
331 | NodeMultiMap QDocDatabase::s_qmlTypesWithObsoleteMembers; |
332 | NodeMultiMap QDocDatabase::s_cppClasses; |
333 | NodeMultiMap QDocDatabase::s_qmlBasicTypes; |
334 | NodeMultiMap QDocDatabase::s_qmlTypes; |
335 | NodeMultiMap QDocDatabase::s_examples; |
336 | NodeMultiMapMap QDocDatabase::s_newClassMaps; |
337 | NodeMultiMapMap QDocDatabase::s_newQmlTypeMaps; |
338 | NodeMultiMapMap QDocDatabase::s_newEnumValueMaps; |
339 | NodeMultiMapMap QDocDatabase::s_newSinceMaps; |
340 | |
341 | /*! |
342 | Constructs the singleton qdoc database object. The singleton |
343 | constructs the \a forest_ object, which is also a singleton. |
344 | \a m_showInternal is normally false. If it is true, qdoc will |
345 | write documentation for nodes marked \c internal. |
346 | |
347 | \a singleExec_ is false when qdoc is being used in the standard |
348 | way of running qdoc twices for each module, first with -prepare |
349 | and then with -generate. First the -prepare phase is run for |
350 | each module, then the -generate phase is run for each module. |
351 | |
352 | When \a singleExec_ is true, qdoc is run only once. During the |
353 | single execution, qdoc processes the qdocconf files for all the |
354 | modules sequentially in a loop. Each source file for each module |
355 | is read exactly once. |
356 | */ |
357 | QDocDatabase::QDocDatabase() : m_forest(this) |
358 | { |
359 | // nothing |
360 | } |
361 | |
362 | /*! |
363 | Creates the singleton. Allows only one instance of the class |
364 | to be created. Returns a pointer to the singleton. |
365 | */ |
366 | QDocDatabase *QDocDatabase::qdocDB() |
367 | { |
368 | if (s_qdocDB == nullptr) { |
369 | s_qdocDB = new QDocDatabase; |
370 | initializeDB(); |
371 | } |
372 | return s_qdocDB; |
373 | } |
374 | |
375 | /*! |
376 | Destroys the singleton. |
377 | */ |
378 | void QDocDatabase::destroyQdocDB() |
379 | { |
380 | if (s_qdocDB != nullptr) { |
381 | delete s_qdocDB; |
382 | s_qdocDB = nullptr; |
383 | } |
384 | } |
385 | |
386 | /*! |
387 | Initialize data structures in the singleton qdoc database. |
388 | |
389 | In particular, the type node map is initialized with a lot |
390 | type names that don't refer to documented types. For example, |
391 | many C++ standard types are included. These might be documented |
392 | here at some point, but for now they are not. Other examples |
393 | include \c array and \c data, which are just generic names |
394 | used as place holders in function signatures that appear in |
395 | the documentation. |
396 | |
397 | \note Do not add QML basic types into this list as it will |
398 | break linking to those types. |
399 | */ |
400 | void QDocDatabase::initializeDB() |
401 | { |
402 | s_typeNodeMap.insert(key: "accepted" , value: nullptr); |
403 | s_typeNodeMap.insert(key: "actionPerformed" , value: nullptr); |
404 | s_typeNodeMap.insert(key: "activated" , value: nullptr); |
405 | s_typeNodeMap.insert(key: "alias" , value: nullptr); |
406 | s_typeNodeMap.insert(key: "anchors" , value: nullptr); |
407 | s_typeNodeMap.insert(key: "any" , value: nullptr); |
408 | s_typeNodeMap.insert(key: "array" , value: nullptr); |
409 | s_typeNodeMap.insert(key: "autoSearch" , value: nullptr); |
410 | s_typeNodeMap.insert(key: "axis" , value: nullptr); |
411 | s_typeNodeMap.insert(key: "backClicked" , value: nullptr); |
412 | s_typeNodeMap.insert(key: "boomTime" , value: nullptr); |
413 | s_typeNodeMap.insert(key: "border" , value: nullptr); |
414 | s_typeNodeMap.insert(key: "buttonClicked" , value: nullptr); |
415 | s_typeNodeMap.insert(key: "callback" , value: nullptr); |
416 | s_typeNodeMap.insert(key: "char" , value: nullptr); |
417 | s_typeNodeMap.insert(key: "clicked" , value: nullptr); |
418 | s_typeNodeMap.insert(key: "close" , value: nullptr); |
419 | s_typeNodeMap.insert(key: "closed" , value: nullptr); |
420 | s_typeNodeMap.insert(key: "cond" , value: nullptr); |
421 | s_typeNodeMap.insert(key: "data" , value: nullptr); |
422 | s_typeNodeMap.insert(key: "dataReady" , value: nullptr); |
423 | s_typeNodeMap.insert(key: "dateString" , value: nullptr); |
424 | s_typeNodeMap.insert(key: "dateTimeString" , value: nullptr); |
425 | s_typeNodeMap.insert(key: "datetime" , value: nullptr); |
426 | s_typeNodeMap.insert(key: "day" , value: nullptr); |
427 | s_typeNodeMap.insert(key: "deactivated" , value: nullptr); |
428 | s_typeNodeMap.insert(key: "drag" , value: nullptr); |
429 | s_typeNodeMap.insert(key: "easing" , value: nullptr); |
430 | s_typeNodeMap.insert(key: "error" , value: nullptr); |
431 | s_typeNodeMap.insert(key: "exposure" , value: nullptr); |
432 | s_typeNodeMap.insert(key: "fatalError" , value: nullptr); |
433 | s_typeNodeMap.insert(key: "fileSelected" , value: nullptr); |
434 | s_typeNodeMap.insert(key: "flags" , value: nullptr); |
435 | s_typeNodeMap.insert(key: "float" , value: nullptr); |
436 | s_typeNodeMap.insert(key: "focus" , value: nullptr); |
437 | s_typeNodeMap.insert(key: "focusZone" , value: nullptr); |
438 | s_typeNodeMap.insert(key: "format" , value: nullptr); |
439 | s_typeNodeMap.insert(key: "framePainted" , value: nullptr); |
440 | s_typeNodeMap.insert(key: "from" , value: nullptr); |
441 | s_typeNodeMap.insert(key: "frontClicked" , value: nullptr); |
442 | s_typeNodeMap.insert(key: "function" , value: nullptr); |
443 | s_typeNodeMap.insert(key: "hasOpened" , value: nullptr); |
444 | s_typeNodeMap.insert(key: "hovered" , value: nullptr); |
445 | s_typeNodeMap.insert(key: "hoveredTitle" , value: nullptr); |
446 | s_typeNodeMap.insert(key: "hoveredUrl" , value: nullptr); |
447 | s_typeNodeMap.insert(key: "imageCapture" , value: nullptr); |
448 | s_typeNodeMap.insert(key: "imageProcessing" , value: nullptr); |
449 | s_typeNodeMap.insert(key: "index" , value: nullptr); |
450 | s_typeNodeMap.insert(key: "initialized" , value: nullptr); |
451 | s_typeNodeMap.insert(key: "isLoaded" , value: nullptr); |
452 | s_typeNodeMap.insert(key: "item" , value: nullptr); |
453 | s_typeNodeMap.insert(key: "key" , value: nullptr); |
454 | s_typeNodeMap.insert(key: "keysequence" , value: nullptr); |
455 | s_typeNodeMap.insert(key: "listViewClicked" , value: nullptr); |
456 | s_typeNodeMap.insert(key: "loadRequest" , value: nullptr); |
457 | s_typeNodeMap.insert(key: "locale" , value: nullptr); |
458 | s_typeNodeMap.insert(key: "location" , value: nullptr); |
459 | s_typeNodeMap.insert(key: "long" , value: nullptr); |
460 | s_typeNodeMap.insert(key: "message" , value: nullptr); |
461 | s_typeNodeMap.insert(key: "messageReceived" , value: nullptr); |
462 | s_typeNodeMap.insert(key: "mode" , value: nullptr); |
463 | s_typeNodeMap.insert(key: "month" , value: nullptr); |
464 | s_typeNodeMap.insert(key: "name" , value: nullptr); |
465 | s_typeNodeMap.insert(key: "number" , value: nullptr); |
466 | s_typeNodeMap.insert(key: "object" , value: nullptr); |
467 | s_typeNodeMap.insert(key: "offset" , value: nullptr); |
468 | s_typeNodeMap.insert(key: "ok" , value: nullptr); |
469 | s_typeNodeMap.insert(key: "openCamera" , value: nullptr); |
470 | s_typeNodeMap.insert(key: "openImage" , value: nullptr); |
471 | s_typeNodeMap.insert(key: "openVideo" , value: nullptr); |
472 | s_typeNodeMap.insert(key: "padding" , value: nullptr); |
473 | s_typeNodeMap.insert(key: "parent" , value: nullptr); |
474 | s_typeNodeMap.insert(key: "path" , value: nullptr); |
475 | s_typeNodeMap.insert(key: "photoModeSelected" , value: nullptr); |
476 | s_typeNodeMap.insert(key: "position" , value: nullptr); |
477 | s_typeNodeMap.insert(key: "precision" , value: nullptr); |
478 | s_typeNodeMap.insert(key: "presetClicked" , value: nullptr); |
479 | s_typeNodeMap.insert(key: "preview" , value: nullptr); |
480 | s_typeNodeMap.insert(key: "previewSelected" , value: nullptr); |
481 | s_typeNodeMap.insert(key: "progress" , value: nullptr); |
482 | s_typeNodeMap.insert(key: "puzzleLost" , value: nullptr); |
483 | s_typeNodeMap.insert(key: "qmlSignal" , value: nullptr); |
484 | s_typeNodeMap.insert(key: "rectangle" , value: nullptr); |
485 | s_typeNodeMap.insert(key: "request" , value: nullptr); |
486 | s_typeNodeMap.insert(key: "requestId" , value: nullptr); |
487 | s_typeNodeMap.insert(key: "section" , value: nullptr); |
488 | s_typeNodeMap.insert(key: "selected" , value: nullptr); |
489 | s_typeNodeMap.insert(key: "send" , value: nullptr); |
490 | s_typeNodeMap.insert(key: "settingsClicked" , value: nullptr); |
491 | s_typeNodeMap.insert(key: "shoe" , value: nullptr); |
492 | s_typeNodeMap.insert(key: "short" , value: nullptr); |
493 | s_typeNodeMap.insert(key: "signed" , value: nullptr); |
494 | s_typeNodeMap.insert(key: "sizeChanged" , value: nullptr); |
495 | s_typeNodeMap.insert(key: "size_t" , value: nullptr); |
496 | s_typeNodeMap.insert(key: "sockaddr" , value: nullptr); |
497 | s_typeNodeMap.insert(key: "someOtherSignal" , value: nullptr); |
498 | s_typeNodeMap.insert(key: "sourceSize" , value: nullptr); |
499 | s_typeNodeMap.insert(key: "startButtonClicked" , value: nullptr); |
500 | s_typeNodeMap.insert(key: "state" , value: nullptr); |
501 | s_typeNodeMap.insert(key: "std::initializer_list" , value: nullptr); |
502 | s_typeNodeMap.insert(key: "std::list" , value: nullptr); |
503 | s_typeNodeMap.insert(key: "std::map" , value: nullptr); |
504 | s_typeNodeMap.insert(key: "std::pair" , value: nullptr); |
505 | s_typeNodeMap.insert(key: "std::string" , value: nullptr); |
506 | s_typeNodeMap.insert(key: "std::vector" , value: nullptr); |
507 | s_typeNodeMap.insert(key: "stringlist" , value: nullptr); |
508 | s_typeNodeMap.insert(key: "swapPlayers" , value: nullptr); |
509 | s_typeNodeMap.insert(key: "symbol" , value: nullptr); |
510 | s_typeNodeMap.insert(key: "t" , value: nullptr); |
511 | s_typeNodeMap.insert(key: "T" , value: nullptr); |
512 | s_typeNodeMap.insert(key: "tagChanged" , value: nullptr); |
513 | s_typeNodeMap.insert(key: "timeString" , value: nullptr); |
514 | s_typeNodeMap.insert(key: "timeout" , value: nullptr); |
515 | s_typeNodeMap.insert(key: "to" , value: nullptr); |
516 | s_typeNodeMap.insert(key: "toggled" , value: nullptr); |
517 | s_typeNodeMap.insert(key: "type" , value: nullptr); |
518 | s_typeNodeMap.insert(key: "unsigned" , value: nullptr); |
519 | s_typeNodeMap.insert(key: "urllist" , value: nullptr); |
520 | s_typeNodeMap.insert(key: "va_list" , value: nullptr); |
521 | s_typeNodeMap.insert(key: "value" , value: nullptr); |
522 | s_typeNodeMap.insert(key: "valueEmitted" , value: nullptr); |
523 | s_typeNodeMap.insert(key: "videoFramePainted" , value: nullptr); |
524 | s_typeNodeMap.insert(key: "videoModeSelected" , value: nullptr); |
525 | s_typeNodeMap.insert(key: "videoRecorder" , value: nullptr); |
526 | s_typeNodeMap.insert(key: "void" , value: nullptr); |
527 | s_typeNodeMap.insert(key: "volatile" , value: nullptr); |
528 | s_typeNodeMap.insert(key: "wchar_t" , value: nullptr); |
529 | s_typeNodeMap.insert(key: "x" , value: nullptr); |
530 | s_typeNodeMap.insert(key: "y" , value: nullptr); |
531 | s_typeNodeMap.insert(key: "zoom" , value: nullptr); |
532 | s_typeNodeMap.insert(key: "zoomTo" , value: nullptr); |
533 | } |
534 | |
535 | /*! \fn NamespaceNode *QDocDatabase::primaryTreeRoot() |
536 | Returns a pointer to the root node of the primary tree. |
537 | */ |
538 | |
539 | /*! |
540 | \fn const CNMap &QDocDatabase::groups() |
541 | Returns a const reference to the collection of all |
542 | group nodes in the primary tree. |
543 | */ |
544 | |
545 | /*! |
546 | \fn const CNMap &QDocDatabase::modules() |
547 | Returns a const reference to the collection of all |
548 | module nodes in the primary tree. |
549 | */ |
550 | |
551 | /*! |
552 | \fn const CNMap &QDocDatabase::qmlModules() |
553 | Returns a const reference to the collection of all |
554 | QML module nodes in the primary tree. |
555 | */ |
556 | |
557 | /*! \fn CollectionNode *QDocDatabase::findGroup(const QString &name) |
558 | Find the group node named \a name and return a pointer |
559 | to it. If a matching node is not found, add a new group |
560 | node named \a name and return a pointer to that one. |
561 | |
562 | If a new group node is added, its parent is the tree root, |
563 | and the new group node is marked \e{not seen}. |
564 | */ |
565 | |
566 | /*! \fn CollectionNode *QDocDatabase::findModule(const QString &name) |
567 | Find the module node named \a name and return a pointer |
568 | to it. If a matching node is not found, add a new module |
569 | node named \a name and return a pointer to that one. |
570 | |
571 | If a new module node is added, its parent is the tree root, |
572 | and the new module node is marked \e{not seen}. |
573 | */ |
574 | |
575 | /*! \fn CollectionNode *QDocDatabase::addGroup(const QString &name) |
576 | Looks up the group named \a name in the primary tree. If |
577 | a match is found, a pointer to the node is returned. |
578 | Otherwise, a new group node named \a name is created and |
579 | inserted into the collection, and the pointer to that node |
580 | is returned. |
581 | */ |
582 | |
583 | /*! \fn CollectionNode *QDocDatabase::addModule(const QString &name) |
584 | Looks up the module named \a name in the primary tree. If |
585 | a match is found, a pointer to the node is returned. |
586 | Otherwise, a new module node named \a name is created and |
587 | inserted into the collection, and the pointer to that node |
588 | is returned. |
589 | */ |
590 | |
591 | /*! \fn CollectionNode *QDocDatabase::addQmlModule(const QString &name) |
592 | Looks up the QML module named \a name in the primary tree. |
593 | If a match is found, a pointer to the node is returned. |
594 | Otherwise, a new QML module node named \a name is created |
595 | and inserted into the collection, and the pointer to that |
596 | node is returned. |
597 | */ |
598 | |
599 | /*! \fn CollectionNode *QDocDatabase::addToGroup(const QString &name, Node *node) |
600 | Looks up the group node named \a name in the collection |
601 | of all group nodes. If a match is not found, a new group |
602 | node named \a name is created and inserted into the collection. |
603 | Then append \a node to the group's members list, and append the |
604 | group node to the member list of the \a node. The parent of the |
605 | \a node is not changed by this function. Returns a pointer to |
606 | the group node. |
607 | */ |
608 | |
609 | /*! \fn CollectionNode *QDocDatabase::addToModule(const QString &name, Node *node) |
610 | Looks up the module node named \a name in the collection |
611 | of all module nodes. If a match is not found, a new module |
612 | node named \a name is created and inserted into the collection. |
613 | Then append \a node to the module's members list. The parent of |
614 | \a node is not changed by this function. Returns the module node. |
615 | */ |
616 | |
617 | /*! \fn Collection *QDocDatabase::addToQmlModule(const QString &name, Node *node) |
618 | Looks up the QML module named \a name. If it isn't there, |
619 | create it. Then append \a node to the QML module's member |
620 | list. The parent of \a node is not changed by this function. |
621 | */ |
622 | |
623 | /*! |
624 | Looks up the QML type node identified by the qualified Qml |
625 | type \a name and returns a pointer to the QML type node. |
626 | */ |
627 | QmlTypeNode *QDocDatabase::findQmlType(const QString &name) |
628 | { |
629 | QmlTypeNode *qcn = m_forest.lookupQmlType(name); |
630 | if (qcn) |
631 | return qcn; |
632 | return nullptr; |
633 | } |
634 | |
635 | /*! |
636 | Looks up the QML type node identified by the Qml module id |
637 | \a qmid and QML type \a name and returns a pointer to the |
638 | QML type node. The key is \a qmid + "::" + \a name. |
639 | |
640 | If the QML module id is empty, it looks up the QML type by |
641 | \a name only. |
642 | */ |
643 | QmlTypeNode *QDocDatabase::findQmlType(const QString &qmid, const QString &name) |
644 | { |
645 | if (!qmid.isEmpty()) { |
646 | QString t = qmid + "::" + name; |
647 | QmlTypeNode *qcn = m_forest.lookupQmlType(name: t); |
648 | if (qcn) |
649 | return qcn; |
650 | } |
651 | |
652 | QStringList path(name); |
653 | Node *n = m_forest.findNodeByNameAndType(path, isMatch: &Node::isQmlType); |
654 | if (n && n->isQmlType()) |
655 | return static_cast<QmlTypeNode *>(n); |
656 | return nullptr; |
657 | } |
658 | |
659 | /*! |
660 | Looks up the QML type node identified by the Qml module id |
661 | constructed from the strings in the \a import record and the |
662 | QML type \a name and returns a pointer to the QML type node. |
663 | If a QML type node is not found, 0 is returned. |
664 | */ |
665 | QmlTypeNode *QDocDatabase::findQmlType(const ImportRec &import, const QString &name) |
666 | { |
667 | if (!import.isEmpty()) { |
668 | QStringList dotSplit; |
669 | dotSplit = name.split(sep: QLatin1Char('.')); |
670 | QString qmName; |
671 | if (import.m_importUri.isEmpty()) |
672 | qmName = import.m_moduleName; |
673 | else |
674 | qmName = import.m_importUri; |
675 | for (const auto &namePart : dotSplit) { |
676 | QString qualifiedName = qmName + "::" + namePart; |
677 | QmlTypeNode *qcn = m_forest.lookupQmlType(name: qualifiedName); |
678 | if (qcn) |
679 | return qcn; |
680 | } |
681 | } |
682 | return nullptr; |
683 | } |
684 | |
685 | /*! |
686 | This function calls a set of functions for each tree in the |
687 | forest that has not already been analyzed. In this way, when |
688 | running qdoc in \e singleExec mode, each tree is analyzed in |
689 | turn, and its classes and types are added to the appropriate |
690 | node maps. |
691 | */ |
692 | void QDocDatabase::processForest() |
693 | { |
694 | processForest(func: &QDocDatabase::findAllClasses); |
695 | processForest(func: &QDocDatabase::findAllFunctions); |
696 | processForest(func: &QDocDatabase::findAllObsoleteThings); |
697 | processForest(func: &QDocDatabase::findAllLegaleseTexts); |
698 | processForest(func: &QDocDatabase::findAllSince); |
699 | processForest(func: &QDocDatabase::findAllAttributions); |
700 | resolveNamespaces(); |
701 | } |
702 | |
703 | /*! |
704 | This function calls \a func for each tree in the forest, |
705 | ensuring that \a func is called only once per tree. |
706 | |
707 | \sa processForest() |
708 | */ |
709 | void QDocDatabase::processForest(FindFunctionPtr func) |
710 | { |
711 | Tree *t = m_forest.firstTree(); |
712 | while (t) { |
713 | if (!m_completedFindFunctions.values(key: t).contains(t: func)) { |
714 | (this->*(func))(t->root()); |
715 | m_completedFindFunctions.insert(key: t, value: func); |
716 | } |
717 | t = m_forest.nextTree(); |
718 | } |
719 | } |
720 | |
721 | /*! |
722 | Returns a reference to the collection of legalese texts. |
723 | */ |
724 | TextToNodeMap &QDocDatabase::getLegaleseTexts() |
725 | { |
726 | processForest(func: &QDocDatabase::findAllLegaleseTexts); |
727 | return m_legaleseTexts; |
728 | } |
729 | |
730 | /*! |
731 | Returns a reference to the map of C++ classes with obsolete members. |
732 | */ |
733 | NodeMultiMap &QDocDatabase::getClassesWithObsoleteMembers() |
734 | { |
735 | processForest(func: &QDocDatabase::findAllObsoleteThings); |
736 | return s_classesWithObsoleteMembers; |
737 | } |
738 | |
739 | /*! |
740 | Returns a reference to the map of obsolete QML types. |
741 | */ |
742 | NodeMultiMap &QDocDatabase::getObsoleteQmlTypes() |
743 | { |
744 | processForest(func: &QDocDatabase::findAllObsoleteThings); |
745 | return s_obsoleteQmlTypes; |
746 | } |
747 | |
748 | /*! |
749 | Returns a reference to the map of QML types with obsolete members. |
750 | */ |
751 | NodeMultiMap &QDocDatabase::getQmlTypesWithObsoleteMembers() |
752 | { |
753 | processForest(func: &QDocDatabase::findAllObsoleteThings); |
754 | return s_qmlTypesWithObsoleteMembers; |
755 | } |
756 | |
757 | /*! |
758 | Returns a reference to the map of QML basic types. |
759 | */ |
760 | NodeMultiMap &QDocDatabase::getQmlValueTypes() |
761 | { |
762 | processForest(func: &QDocDatabase::findAllClasses); |
763 | return s_qmlBasicTypes; |
764 | } |
765 | |
766 | /*! |
767 | Returns a reference to the multimap of QML types. |
768 | */ |
769 | NodeMultiMap &QDocDatabase::getQmlTypes() |
770 | { |
771 | processForest(func: &QDocDatabase::findAllClasses); |
772 | return s_qmlTypes; |
773 | } |
774 | |
775 | /*! |
776 | Returns a reference to the multimap of example nodes. |
777 | */ |
778 | NodeMultiMap &QDocDatabase::getExamples() |
779 | { |
780 | processForest(func: &QDocDatabase::findAllClasses); |
781 | return s_examples; |
782 | } |
783 | |
784 | /*! |
785 | Returns a reference to the multimap of attribution nodes. |
786 | */ |
787 | NodeMultiMap &QDocDatabase::getAttributions() |
788 | { |
789 | processForest(func: &QDocDatabase::findAllAttributions); |
790 | return m_attributions; |
791 | } |
792 | |
793 | /*! |
794 | Returns a reference to the map of obsolete C++ clases. |
795 | */ |
796 | NodeMultiMap &QDocDatabase::getObsoleteClasses() |
797 | { |
798 | processForest(func: &QDocDatabase::findAllObsoleteThings); |
799 | return s_obsoleteClasses; |
800 | } |
801 | |
802 | /*! |
803 | Returns a reference to the map of all C++ classes. |
804 | */ |
805 | NodeMultiMap &QDocDatabase::getCppClasses() |
806 | { |
807 | processForest(func: &QDocDatabase::findAllClasses); |
808 | return s_cppClasses; |
809 | } |
810 | |
811 | /*! |
812 | Returns the function index. This data structure is used to |
813 | output the function index page. |
814 | */ |
815 | NodeMapMap &QDocDatabase::getFunctionIndex() |
816 | { |
817 | processForest(func: &QDocDatabase::findAllFunctions); |
818 | return m_functionIndex; |
819 | } |
820 | |
821 | /*! |
822 | Finds all the nodes containing legalese text and puts them |
823 | in a map. |
824 | */ |
825 | void QDocDatabase::findAllLegaleseTexts(Aggregate *node) |
826 | { |
827 | for (const auto &childNode : node->childNodes()) { |
828 | if (childNode->isPrivate()) |
829 | continue; |
830 | if (!childNode->doc().legaleseText().isEmpty()) |
831 | m_legaleseTexts.insert(key: childNode->doc().legaleseText(), value: childNode); |
832 | if (childNode->isAggregate()) |
833 | findAllLegaleseTexts(node: static_cast<Aggregate *>(childNode)); |
834 | } |
835 | } |
836 | |
837 | /*! |
838 | \fn void QDocDatabase::findAllObsoleteThings(Aggregate *node) |
839 | |
840 | Finds all nodes with status = Deprecated and sorts them into |
841 | maps. They can be C++ classes, QML types, or they can be |
842 | functions, enum types, typedefs, methods, etc. |
843 | */ |
844 | |
845 | /*! |
846 | \fn void QDocDatabase::findAllSince(Aggregate *node) |
847 | |
848 | Finds all the nodes in \a node where a \e{since} command appeared |
849 | in the qdoc comment and sorts them into maps according to the kind |
850 | of node. |
851 | |
852 | This function is used for generating the "New Classes... in x.y" |
853 | section on the \e{What's New in Qt x.y} page. |
854 | */ |
855 | |
856 | /*! |
857 | Find the \a key in the map of new class maps, and return a |
858 | reference to the value, which is a NodeMap. If \a key is not |
859 | found, return a reference to an empty NodeMap. |
860 | */ |
861 | const NodeMultiMap &QDocDatabase::getClassMap(const QString &key) |
862 | { |
863 | processForest(func: &QDocDatabase::findAllSince); |
864 | auto it = s_newClassMaps.constFind(key); |
865 | return (it != s_newClassMaps.constEnd()) ? it.value() : emptyNodeMultiMap_; |
866 | } |
867 | |
868 | /*! |
869 | Find the \a key in the map of new QML type maps, and return a |
870 | reference to the value, which is a NodeMap. If the \a key is not |
871 | found, return a reference to an empty NodeMap. |
872 | */ |
873 | const NodeMultiMap &QDocDatabase::getQmlTypeMap(const QString &key) |
874 | { |
875 | processForest(func: &QDocDatabase::findAllSince); |
876 | auto it = s_newQmlTypeMaps.constFind(key); |
877 | return (it != s_newQmlTypeMaps.constEnd()) ? it.value() : emptyNodeMultiMap_; |
878 | } |
879 | |
880 | /*! |
881 | Find the \a key in the map of new \e {since} maps, and return |
882 | a reference to the value, which is a NodeMultiMap. If \a key |
883 | is not found, return a reference to an empty NodeMultiMap. |
884 | */ |
885 | const NodeMultiMap &QDocDatabase::getSinceMap(const QString &key) |
886 | { |
887 | processForest(func: &QDocDatabase::findAllSince); |
888 | auto it = s_newSinceMaps.constFind(key); |
889 | return (it != s_newSinceMaps.constEnd()) ? it.value() : emptyNodeMultiMap_; |
890 | } |
891 | |
892 | /*! |
893 | Performs several housekeeping tasks prior to generating the |
894 | documentation. These tasks create required data structures |
895 | and resolve links. |
896 | */ |
897 | void QDocDatabase::resolveStuff() |
898 | { |
899 | const auto &config = Config::instance(); |
900 | if (config.dualExec() || config.preparing()) { |
901 | // order matters |
902 | primaryTree()->resolveBaseClasses(n: primaryTreeRoot()); |
903 | primaryTree()->resolvePropertyOverriddenFromPtrs(n: primaryTreeRoot()); |
904 | primaryTreeRoot()->normalizeOverloads(); |
905 | primaryTree()->markDontDocumentNodes(); |
906 | primaryTree()->removePrivateAndInternalBases(rootNode: primaryTreeRoot()); |
907 | primaryTree()->resolveProperties(); |
908 | primaryTreeRoot()->markUndocumentedChildrenInternal(); |
909 | primaryTreeRoot()->resolveQmlInheritance(); |
910 | primaryTree()->resolveTargets(root: primaryTreeRoot()); |
911 | primaryTree()->resolveCppToQmlLinks(); |
912 | primaryTree()->resolveSince(aggregate&: *primaryTreeRoot()); |
913 | } |
914 | if (config.singleExec() && config.generating()) { |
915 | primaryTree()->resolveBaseClasses(n: primaryTreeRoot()); |
916 | primaryTree()->resolvePropertyOverriddenFromPtrs(n: primaryTreeRoot()); |
917 | primaryTreeRoot()->resolveQmlInheritance(); |
918 | primaryTree()->resolveCppToQmlLinks(); |
919 | primaryTree()->resolveSince(aggregate&: *primaryTreeRoot()); |
920 | } |
921 | if (!config.preparing()) { |
922 | resolveNamespaces(); |
923 | resolveProxies(); |
924 | resolveBaseClasses(); |
925 | updateNavigation(); |
926 | } |
927 | if (config.dualExec()) |
928 | QDocIndexFiles::destroyQDocIndexFiles(); |
929 | } |
930 | |
931 | void QDocDatabase::resolveBaseClasses() |
932 | { |
933 | Tree *t = m_forest.firstTree(); |
934 | while (t) { |
935 | t->resolveBaseClasses(n: t->root()); |
936 | t = m_forest.nextTree(); |
937 | } |
938 | } |
939 | |
940 | /*! |
941 | Returns a reference to the namespace map. Constructs the |
942 | namespace map if it hasn't been constructed yet. |
943 | |
944 | \note This function must not be called in the prepare phase. |
945 | */ |
946 | NodeMultiMap &QDocDatabase::getNamespaces() |
947 | { |
948 | resolveNamespaces(); |
949 | return m_namespaceIndex; |
950 | } |
951 | |
952 | /*! |
953 | Multiple namespace nodes for namespace X can exist in the |
954 | qdoc database in different trees. This function first finds |
955 | all namespace nodes in all the trees and inserts them into |
956 | a multimap. Then it combines all the namespace nodes that |
957 | have the same name into a single namespace node of that |
958 | name and inserts that combined namespace node into an index. |
959 | */ |
960 | void QDocDatabase::resolveNamespaces() |
961 | { |
962 | if (!m_namespaceIndex.isEmpty()) |
963 | return; |
964 | |
965 | bool linkErrors = !Config::instance().get(CONFIG_NOLINKERRORS).asBool(); |
966 | NodeMultiMap namespaceMultimap; |
967 | Tree *t = m_forest.firstTree(); |
968 | while (t) { |
969 | t->root()->findAllNamespaces(namespaces&: namespaceMultimap); |
970 | t = m_forest.nextTree(); |
971 | } |
972 | const QList<QString> keys = namespaceMultimap.uniqueKeys(); |
973 | for (const QString &key : keys) { |
974 | NamespaceNode *ns = nullptr; |
975 | NamespaceNode *indexNamespace = nullptr; |
976 | const NodeList namespaces = namespaceMultimap.values(key); |
977 | qsizetype count = namespaceMultimap.remove(key); |
978 | if (count > 0) { |
979 | for (auto *node : namespaces) { |
980 | ns = static_cast<NamespaceNode *>(node); |
981 | if (ns->isDocumentedHere()) |
982 | break; |
983 | else if (ns->hadDoc()) |
984 | indexNamespace = ns; // namespace was documented but in another tree |
985 | ns = nullptr; |
986 | } |
987 | if (ns) { |
988 | for (auto *node : namespaces) { |
989 | auto *nsNode = static_cast<NamespaceNode *>(node); |
990 | if (nsNode->hadDoc() && nsNode != ns) { |
991 | ns->doc().location().warning( |
992 | QStringLiteral("Namespace %1 documented more than once" ) |
993 | .arg(a: nsNode->name()), QStringLiteral("also seen here: %1" ) |
994 | .arg(a: nsNode->doc().location().toString())); |
995 | } |
996 | } |
997 | } else if (!indexNamespace) { |
998 | // Warn about documented children in undocumented namespaces. |
999 | // As the namespace can be documented outside this project, |
1000 | // skip the warning if -no-link-errors is set |
1001 | if (linkErrors) { |
1002 | for (auto *node : namespaces) { |
1003 | if (!node->isIndexNode()) |
1004 | static_cast<NamespaceNode *>(node)->reportDocumentedChildrenInUndocumentedNamespace(); |
1005 | } |
1006 | } |
1007 | } else { |
1008 | for (auto *node : namespaces) { |
1009 | auto *nsNode = static_cast<NamespaceNode *>(node); |
1010 | if (nsNode != indexNamespace) |
1011 | nsNode->setDocNode(indexNamespace); |
1012 | } |
1013 | } |
1014 | } |
1015 | /* |
1016 | If there are multiple namespace nodes with the same |
1017 | name where one of them will be the main reference page |
1018 | for the namespace, include all nodes in the public |
1019 | API of the namespace. |
1020 | */ |
1021 | if (ns && count > 1) { |
1022 | for (auto *node : namespaces) { |
1023 | auto *nameSpaceNode = static_cast<NamespaceNode *>(node); |
1024 | if (nameSpaceNode != ns) { |
1025 | for (auto it = nameSpaceNode->constBegin(); it != nameSpaceNode->constEnd(); |
1026 | ++it) { |
1027 | Node *anotherNs = *it; |
1028 | if (anotherNs && anotherNs->isPublic() && !anotherNs->isInternal()) |
1029 | ns->includeChild(child: anotherNs); |
1030 | } |
1031 | } |
1032 | } |
1033 | } |
1034 | /* |
1035 | Add the main namespace reference node to index, or the last seen |
1036 | namespace if the main one was not found. |
1037 | */ |
1038 | if (!ns) |
1039 | ns = indexNamespace ? indexNamespace : static_cast<NamespaceNode *>(namespaces.last()); |
1040 | m_namespaceIndex.insert(key: ns->name(), value: ns); |
1041 | } |
1042 | } |
1043 | |
1044 | /*! |
1045 | Each instance of class Tree that represents an index file |
1046 | must be traversed to find all instances of class ProxyNode. |
1047 | For each ProxyNode found, look up the ProxyNode's name in |
1048 | the primary Tree. If it is found, it means that the proxy |
1049 | node contains elements (normally just functions) that are |
1050 | documented in the module represented by the Tree containing |
1051 | the proxy node but that are related to the node we found in |
1052 | the primary tree. |
1053 | */ |
1054 | void QDocDatabase::resolveProxies() |
1055 | { |
1056 | // The first tree is the primary tree. |
1057 | // Skip the primary tree. |
1058 | Tree *t = m_forest.firstTree(); |
1059 | t = m_forest.nextTree(); |
1060 | while (t) { |
1061 | const NodeList &proxies = t->proxies(); |
1062 | if (!proxies.isEmpty()) { |
1063 | for (auto *node : proxies) { |
1064 | const auto *pn = static_cast<ProxyNode *>(node); |
1065 | if (pn->count() > 0) { |
1066 | Aggregate *aggregate = primaryTree()->findAggregate(name: pn->name()); |
1067 | if (aggregate != nullptr) |
1068 | aggregate->appendToRelatedByProxy(t: pn->childNodes()); |
1069 | } |
1070 | } |
1071 | } |
1072 | t = m_forest.nextTree(); |
1073 | } |
1074 | } |
1075 | |
1076 | /*! |
1077 | Finds the function node for the qualified function path in |
1078 | \a target and returns a pointer to it. The \a target is a |
1079 | function signature with or without parameters but without |
1080 | the return type. |
1081 | |
1082 | \a relative is the node in the primary tree where the search |
1083 | begins. It is not used in the other trees, if the node is not |
1084 | found in the primary tree. \a genus can be used to force the |
1085 | search to find a C++ function or a QML function. |
1086 | |
1087 | The entire forest is searched, but the first match is accepted. |
1088 | */ |
1089 | const FunctionNode *QDocDatabase::findFunctionNode(const QString &target, const Node *relative, |
1090 | Node::Genus genus) |
1091 | { |
1092 | QString signature; |
1093 | QString function = target; |
1094 | qsizetype length = target.size(); |
1095 | if (function.endsWith(s: "()" )) |
1096 | function.chop(n: 2); |
1097 | if (function.endsWith(c: QChar(')'))) { |
1098 | qsizetype position = function.lastIndexOf(c: QChar('(')); |
1099 | signature = function.mid(position: position + 1, n: length - position - 2); |
1100 | function = function.left(n: position); |
1101 | } |
1102 | QStringList path = function.split(sep: "::" ); |
1103 | return m_forest.findFunctionNode(path, parameters: Parameters(signature), relative, genus); |
1104 | } |
1105 | |
1106 | /*! |
1107 | This function is called for autolinking to a \a type, |
1108 | which could be a function return type or a parameter |
1109 | type. The tree node that represents the \a type is |
1110 | returned. All the trees are searched until a match is |
1111 | found. When searching the primary tree, the search |
1112 | begins at \a relative and proceeds up the parent chain. |
1113 | When searching the index trees, the search begins at the |
1114 | root. |
1115 | */ |
1116 | const Node *QDocDatabase::findTypeNode(const QString &type, const Node *relative, Node::Genus genus) |
1117 | { |
1118 | QStringList path = type.split(sep: "::" ); |
1119 | if ((path.size() == 1) && (path.at(i: 0)[0].isLower() || path.at(i: 0) == QString("T" ))) { |
1120 | auto it = s_typeNodeMap.find(key: path.at(i: 0)); |
1121 | if (it != s_typeNodeMap.end()) |
1122 | return it.value(); |
1123 | } |
1124 | return m_forest.findTypeNode(path, relative, genus); |
1125 | } |
1126 | |
1127 | /*! |
1128 | Finds the node that will generate the documentation that |
1129 | contains the \a target and returns a pointer to it. |
1130 | |
1131 | Can this be improved by using the target map in Tree? |
1132 | */ |
1133 | const Node *QDocDatabase::findNodeForTarget(const QString &target, const Node *relative) |
1134 | { |
1135 | const Node *node = nullptr; |
1136 | if (target.isEmpty()) |
1137 | node = relative; |
1138 | else if (target.endsWith(s: ".html" )) |
1139 | node = findNodeByNameAndType(path: QStringList(target), isMatch: &Node::isPageNode); |
1140 | else { |
1141 | QStringList path = target.split(sep: "::" ); |
1142 | int flags = SearchBaseClasses | SearchEnumValues; |
1143 | for (const auto *tree : searchOrder()) { |
1144 | const Node *n = tree->findNode(path, relative, flags, genus: Node::DontCare); |
1145 | if (n) |
1146 | return n; |
1147 | relative = nullptr; |
1148 | } |
1149 | node = findPageNodeByTitle(title: target); |
1150 | } |
1151 | return node; |
1152 | } |
1153 | |
1154 | QStringList QDocDatabase::groupNamesForNode(Node *node) |
1155 | { |
1156 | QStringList result; |
1157 | CNMap *m = primaryTree()->getCollectionMap(type: Node::Group); |
1158 | |
1159 | if (!m) |
1160 | return result; |
1161 | |
1162 | for (auto it = m->cbegin(); it != m->cend(); ++it) |
1163 | if (it.value()->members().contains(t: node)) |
1164 | result << it.key(); |
1165 | |
1166 | return result; |
1167 | } |
1168 | |
1169 | /*! |
1170 | Reads and parses the qdoc index files listed in \a indexFiles. |
1171 | */ |
1172 | void QDocDatabase::readIndexes(const QStringList &indexFiles) |
1173 | { |
1174 | QStringList filesToRead; |
1175 | for (const QString &file : indexFiles) { |
1176 | QString fn = file.mid(position: file.lastIndexOf(c: QChar('/')) + 1); |
1177 | if (!isLoaded(t: fn)) |
1178 | filesToRead << file; |
1179 | else |
1180 | qCCritical(lcQdoc) << "Index file" << file << "is already in memory." ; |
1181 | } |
1182 | QDocIndexFiles::qdocIndexFiles()->readIndexes(indexFiles: filesToRead); |
1183 | } |
1184 | |
1185 | /*! |
1186 | Generates a qdoc index file and write it to \a fileName. The |
1187 | index file is generated with the parameters \a url and \a title, |
1188 | using the generator \a g. |
1189 | */ |
1190 | void QDocDatabase::generateIndex(const QString &fileName, const QString &url, const QString &title, |
1191 | Generator *g) |
1192 | { |
1193 | QString t = fileName.mid(position: fileName.lastIndexOf(c: QChar('/')) + 1); |
1194 | primaryTree()->setIndexFileName(t); |
1195 | QDocIndexFiles::qdocIndexFiles()->generateIndex(fileName, url, title, g); |
1196 | QDocIndexFiles::destroyQDocIndexFiles(); |
1197 | } |
1198 | |
1199 | /*! |
1200 | Find a node of the specified \a type that is reached with |
1201 | the specified \a path qualified with the name of one of the |
1202 | open namespaces (might not be any open ones). If the node |
1203 | is found in an open namespace, prefix \a path with the name |
1204 | of the open namespace and "::" and return a pointer to the |
1205 | node. Otherwise return \c nullptr. |
1206 | |
1207 | This function only searches in the current primary tree. |
1208 | */ |
1209 | Node *QDocDatabase::findNodeInOpenNamespace(QStringList &path, bool (Node::*isMatch)() const) |
1210 | { |
1211 | if (path.isEmpty()) |
1212 | return nullptr; |
1213 | Node *n = nullptr; |
1214 | if (!m_openNamespaces.isEmpty()) { |
1215 | const auto &openNamespaces = m_openNamespaces; |
1216 | for (const QString &t : openNamespaces) { |
1217 | QStringList p; |
1218 | if (t != path[0]) |
1219 | p = t.split(sep: "::" ) + path; |
1220 | else |
1221 | p = path; |
1222 | n = primaryTree()->findNodeByNameAndType(path: p, isMatch); |
1223 | if (n) { |
1224 | path = p; |
1225 | break; |
1226 | } |
1227 | } |
1228 | } |
1229 | return n; |
1230 | } |
1231 | |
1232 | /*! |
1233 | Returns the collection node representing the module that \a relative |
1234 | node belongs to, or \c nullptr if there is no such module in the |
1235 | primary tree. |
1236 | */ |
1237 | const CollectionNode *QDocDatabase::getModuleNode(const Node *relative) |
1238 | { |
1239 | Node::NodeType moduleType{Node::Module}; |
1240 | QString moduleName; |
1241 | switch (relative->genus()) |
1242 | { |
1243 | case Node::CPP: |
1244 | moduleType = Node::Module; |
1245 | moduleName = relative->physicalModuleName(); |
1246 | break; |
1247 | case Node::QML: |
1248 | moduleType = Node::QmlModule; |
1249 | moduleName = relative->logicalModuleName(); |
1250 | break; |
1251 | default: |
1252 | return nullptr; |
1253 | } |
1254 | if (moduleName.isEmpty()) |
1255 | return nullptr; |
1256 | |
1257 | return primaryTree()->getCollection(name: moduleName, type: moduleType); |
1258 | } |
1259 | |
1260 | /*! |
1261 | Finds all the collection nodes of the specified \a type |
1262 | and merges them into the collection node map \a cnm. Nodes |
1263 | that match the \a relative node are not included. |
1264 | */ |
1265 | void QDocDatabase::mergeCollections(Node::NodeType type, CNMap &cnm, const Node *relative) |
1266 | { |
1267 | cnm.clear(); |
1268 | CNMultiMap cnmm; |
1269 | for (auto *tree : searchOrder()) { |
1270 | CNMap *m = tree->getCollectionMap(type); |
1271 | if (m && !m->isEmpty()) { |
1272 | for (auto it = m->cbegin(); it != m->cend(); ++it) { |
1273 | if (!it.value()->isInternal()) |
1274 | cnmm.insert(key: it.key(), value: it.value()); |
1275 | } |
1276 | } |
1277 | } |
1278 | if (cnmm.isEmpty()) |
1279 | return; |
1280 | static const QRegularExpression singleDigit("\\b([0-9])\\b" ); |
1281 | const QStringList keys = cnmm.uniqueKeys(); |
1282 | for (const auto &key : keys) { |
1283 | const QList<CollectionNode *> values = cnmm.values(key); |
1284 | CollectionNode *n = nullptr; |
1285 | for (auto *value : values) { |
1286 | if (value && value->wasSeen() && value != relative) { |
1287 | n = value; |
1288 | break; |
1289 | } |
1290 | } |
1291 | if (n) { |
1292 | if (values.size() > 1) { |
1293 | for (CollectionNode *value : values) { |
1294 | if (value != n) { |
1295 | // Allow multiple (major) versions of QML modules |
1296 | if ((n->isQmlModule()) |
1297 | && n->logicalModuleIdentifier() != value->logicalModuleIdentifier()) { |
1298 | if (value->wasSeen() && value != relative |
1299 | && !value->members().isEmpty()) |
1300 | cnm.insert(key: value->fullTitle().toLower(), value); |
1301 | continue; |
1302 | } |
1303 | for (Node *t : value->members()) |
1304 | n->addMember(node: t); |
1305 | } |
1306 | } |
1307 | } |
1308 | QString sortKey = n->fullTitle().toLower(); |
1309 | if (sortKey.startsWith(s: "the " )) |
1310 | sortKey.remove(i: 0, len: 4); |
1311 | sortKey.replace(re: singleDigit, after: "0\\1" ); |
1312 | cnm.insert(key: sortKey, value: n); |
1313 | } |
1314 | } |
1315 | } |
1316 | |
1317 | /*! |
1318 | Finds all the collection nodes with the same name |
1319 | and type as \a c and merges their members into the |
1320 | members list of \a c. |
1321 | |
1322 | For QML modules, only nodes with matching |
1323 | module identifiers are merged to avoid merging |
1324 | modules with different (major) versions. |
1325 | */ |
1326 | void QDocDatabase::mergeCollections(CollectionNode *c) |
1327 | { |
1328 | if (c == nullptr) |
1329 | return; |
1330 | |
1331 | // REMARK: This form of merging is usually called during the |
1332 | // generation phase om-the-fly when a source-of-truth collection |
1333 | // is required. |
1334 | // In practice, this means a collection could be merged many, many |
1335 | // times during the lifetime of a generation. |
1336 | // To avoid repeating the merging process each time, which could |
1337 | // be time consuming, we use a small flag that is set directly on |
1338 | // the collection to bail-out early. |
1339 | // |
1340 | // The merging process is only meaningful for collections when the |
1341 | // collection references are spread troughout multiple projects. |
1342 | // The part of information that exists in other project is read |
1343 | // before the generation phase, such that when the generation |
1344 | // phase comes, we already have all the information we need for |
1345 | // merging such that we can consider all version of a certain |
1346 | // collection node immutable, making the caching inherently |
1347 | // correct at any point of the generation. |
1348 | // |
1349 | // This implies that this operation is unsafe if it is performed |
1350 | // before all the index files are loaded. |
1351 | // Indeed, this is a prerequisite, with the current structure, to |
1352 | // perform this optmization. |
1353 | // |
1354 | // At the current time, this is true and is expected not to |
1355 | // change. |
1356 | // |
1357 | // Do note that this is not applied to the other overload of |
1358 | // mergeCollections as we cannot as safely ensure its consistency |
1359 | // and, as the result of the merging depends on multiple |
1360 | // parameters, it would require an actual memoization of the call. |
1361 | // |
1362 | // Note that this is a defensive optimization and we are assuming |
1363 | // that it is effective based on heuristical data. As this is |
1364 | // expected to disappear, at least in its current form, in the |
1365 | // future, a more thorough analysis was not performed. |
1366 | if (c->isMerged()) { |
1367 | return; |
1368 | } |
1369 | |
1370 | for (auto *tree : searchOrder()) { |
1371 | CollectionNode *cn = tree->getCollection(name: c->name(), type: c->nodeType()); |
1372 | if (cn && cn != c) { |
1373 | if ((cn->isQmlModule()) |
1374 | && cn->logicalModuleIdentifier() != c->logicalModuleIdentifier()) |
1375 | continue; |
1376 | |
1377 | for (auto *node : cn->members()) |
1378 | c->addMember(node); |
1379 | |
1380 | // REMARK: The merging process is performed to ensure that |
1381 | // references to the collection in external projects are |
1382 | // taken into account before consuming the collection. |
1383 | // |
1384 | // This works by having QDoc construct empty collections |
1385 | // as soon as a reference to a collection is encountered |
1386 | // and filling details later on when its definition is |
1387 | // found. |
1388 | // |
1389 | // This initially-empty collection is always saved to the |
1390 | // primaryTree and it is the collection that is directly |
1391 | // accessible to consumers during the generation process. |
1392 | // |
1393 | // Nonetheless, when the definition for the collection is |
1394 | // not in the same project as the one that is being |
1395 | // compiled, its details will never be filled in. |
1396 | // |
1397 | // Indeed, the details will live in the index file for the |
1398 | // project where the collection is defined, if any, and |
1399 | // the node for it, which has complete information, will |
1400 | // live in some non-primaryTree. |
1401 | // |
1402 | // The merging process itself is used by consumers during |
1403 | // the generation process because they access the |
1404 | // primaryTree version of the collection expecting a |
1405 | // source-of-truth. |
1406 | // To ensure that this is the case for usages that |
1407 | // requires linking, we need to merge not only the members |
1408 | // of the collection that reside in external versions of |
1409 | // the collection; but some of the data that reside in the |
1410 | // definition of the collection intself, namely the title |
1411 | // and the url. |
1412 | // |
1413 | // A collection that contains the data of a definition is |
1414 | // always marked as seen, hence we use that to discern |
1415 | // whether we are working with a placeholder node or not, |
1416 | // and fill in the data if we encounter a node that |
1417 | // represents a definition. |
1418 | // |
1419 | // The way in which QDoc works implies that collection are |
1420 | // globally scoped between projects. |
1421 | // The repetition of the definition for the same |
1422 | // collection is warned for as a duplicate documentation, |
1423 | // such that we can expect a single valid source of truth |
1424 | // for a given collection in each project. |
1425 | // It is currently unknown if this warning is applicable |
1426 | // when the repeated collection is defined in two |
1427 | // different projects. |
1428 | // |
1429 | // As QDoc implicitly would not correctly support this |
1430 | // case, we assume that only one declaration exists for |
1431 | // each collection, such that the first encoutered one |
1432 | // must be the source of truth and that there is no need |
1433 | // to copy any data after the first copy is performed. |
1434 | // KLUDGE: Note that this process is done as a hackish |
1435 | // solution to QTBUG-104237 and should not be considered |
1436 | // final or dependable. |
1437 | if (!c->wasSeen() && cn->wasSeen()) { |
1438 | c->markSeen(); |
1439 | c->setTitle(cn->title()); |
1440 | c->setUrl(cn->url()); |
1441 | } |
1442 | } |
1443 | } |
1444 | |
1445 | c->markMerged(); |
1446 | } |
1447 | |
1448 | /*! |
1449 | Searches for the node that matches the path in \a atom and the |
1450 | specified \a genus. The \a relative node is used if the first |
1451 | leg of the path is empty, i.e. if the path begins with '#'. |
1452 | The function also sets \a ref if there remains an unused leg |
1453 | in the path after the node is found. The node is returned as |
1454 | well as the \a ref. If the returned node pointer is null, |
1455 | \a ref is also not valid. |
1456 | */ |
1457 | const Node *QDocDatabase::findNodeForAtom(const Atom *a, const Node *relative, QString &ref, |
1458 | Node::Genus genus) |
1459 | { |
1460 | const Node *node = nullptr; |
1461 | |
1462 | Atom *atom = const_cast<Atom *>(a); |
1463 | QStringList targetPath = atom->string().split(sep: QLatin1Char('#')); |
1464 | QString first = targetPath.first().trimmed(); |
1465 | |
1466 | Tree *domain = nullptr; |
1467 | |
1468 | if (atom->isLinkAtom()) { |
1469 | domain = atom->domain(); |
1470 | genus = atom->genus(); |
1471 | } |
1472 | |
1473 | if (first.isEmpty()) |
1474 | node = relative; // search for a target on the current page. |
1475 | else if (domain) { |
1476 | if (first.endsWith(s: ".html" )) |
1477 | node = domain->findNodeByNameAndType(path: QStringList(first), isMatch: &Node::isPageNode); |
1478 | else if (first.endsWith(c: QChar(')'))) { |
1479 | QString signature; |
1480 | QString function = first; |
1481 | qsizetype length = first.size(); |
1482 | if (function.endsWith(s: "()" )) |
1483 | function.chop(n: 2); |
1484 | if (function.endsWith(c: QChar(')'))) { |
1485 | qsizetype position = function.lastIndexOf(c: QChar('(')); |
1486 | signature = function.mid(position: position + 1, n: length - position - 2); |
1487 | function = function.left(n: position); |
1488 | } |
1489 | QStringList path = function.split(sep: "::" ); |
1490 | node = domain->findFunctionNode(path, parameters: Parameters(signature), relative: nullptr, genus); |
1491 | } |
1492 | if (node == nullptr) { |
1493 | int flags = SearchBaseClasses | SearchEnumValues; |
1494 | QStringList nodePath = first.split(sep: "::" ); |
1495 | QString target; |
1496 | targetPath.removeFirst(); |
1497 | if (!targetPath.isEmpty()) |
1498 | target = targetPath.takeFirst(); |
1499 | if (relative && relative->tree()->physicalModuleName() != domain->physicalModuleName()) |
1500 | relative = nullptr; |
1501 | return domain->findNodeForTarget(path: nodePath, target, node: relative, flags, genus, ref); |
1502 | } |
1503 | } else { |
1504 | if (first.endsWith(s: ".html" )) |
1505 | node = findNodeByNameAndType(path: QStringList(first), isMatch: &Node::isPageNode); |
1506 | else if (first.endsWith(c: QChar(')'))) |
1507 | node = findFunctionNode(target: first, relative, genus); |
1508 | if (node == nullptr) |
1509 | return findNodeForTarget(targetPath, relative, genus, ref); |
1510 | } |
1511 | |
1512 | if (node != nullptr && ref.isEmpty()) { |
1513 | if (!node->url().isEmpty()) |
1514 | return node; |
1515 | targetPath.removeFirst(); |
1516 | if (!targetPath.isEmpty()) { |
1517 | ref = node->root()->tree()->getRef(target: targetPath.first(), node); |
1518 | if (ref.isEmpty()) |
1519 | node = nullptr; |
1520 | } |
1521 | } |
1522 | return node; |
1523 | } |
1524 | |
1525 | /*! |
1526 | Updates navigation (previous/next page links and the navigation parent) |
1527 | for pages listed in the TOC, specified by the \c navigation.toctitles |
1528 | configuration variable. |
1529 | |
1530 | if \c navigation.toctitles.inclusive is \c true, include also the TOC |
1531 | page(s) themselves as a 'root' item in the navigation bar (breadcrumbs) |
1532 | that are generated for HTML output. |
1533 | */ |
1534 | void QDocDatabase::updateNavigation() |
1535 | { |
1536 | // Restrict searching only to the local (primary) tree |
1537 | QList<Tree *> searchOrder = this->searchOrder(); |
1538 | setLocalSearch(); |
1539 | |
1540 | const QString configVar = CONFIG_NAVIGATION + |
1541 | Config::dot + |
1542 | CONFIG_TOCTITLES; |
1543 | |
1544 | // TODO: [direct-configuration-access] |
1545 | // The configuration is currently a singleton with some generally |
1546 | // global mutable state. |
1547 | // |
1548 | // Accessing the data in this form complicates testing and |
1549 | // requires tests that inhibit any test parallelization, as the |
1550 | // tests are not self contained. |
1551 | // |
1552 | // This should be generally avoived. Possibly, we should strive |
1553 | // for Config to be a POD type that generally is scoped to |
1554 | // main and whose data is destructured into dependencies when |
1555 | // the dependencies are constructed. |
1556 | bool inclusive{Config::instance().get( |
1557 | var: configVar + Config::dot + CONFIG_INCLUSIVE).asBool()}; |
1558 | |
1559 | // TODO: [direct-configuration-access] |
1560 | const auto tocTitles{Config::instance().get(var: configVar).asStringList()}; |
1561 | |
1562 | for (const auto &tocTitle : tocTitles) { |
1563 | if (const auto candidateTarget = findNodeForTarget(target: tocTitle, relative: nullptr); candidateTarget && candidateTarget->isPageNode()) { |
1564 | auto tocPage{static_cast<const PageNode*>(candidateTarget)}; |
1565 | |
1566 | Text body = tocPage->doc().body(); |
1567 | |
1568 | auto *atom = body.firstAtom(); |
1569 | |
1570 | std::pair<PageNode *, Atom *> prev { nullptr, nullptr }; |
1571 | |
1572 | std::stack<const PageNode *> tocStack; |
1573 | tocStack.push(x: inclusive ? tocPage : nullptr); |
1574 | |
1575 | bool inItem = false; |
1576 | |
1577 | // TODO: Understand how much we use this form of looping over atoms. |
1578 | // If it is used a few times we might consider providing |
1579 | // an iterator for Text to make use of a simpler |
1580 | // range-for loop. |
1581 | while (atom) { |
1582 | switch (atom->type()) { |
1583 | case Atom::ListItemLeft: |
1584 | // Not known if we're going to have a link, push a temporary |
1585 | tocStack.push(x: nullptr); |
1586 | inItem = true; |
1587 | break; |
1588 | case Atom::ListItemRight: |
1589 | tocStack.pop(); |
1590 | inItem = false; |
1591 | break; |
1592 | case Atom::Link: { |
1593 | if (!inItem) |
1594 | break; |
1595 | |
1596 | // TODO: [unnecessary-output-parameter] |
1597 | // We currently need an lvalue string to |
1598 | // pass to findNodeForAtom, as the |
1599 | // outparameter ref. |
1600 | // |
1601 | // Apart from the general problems with output |
1602 | // parameters, we shouldn't be forced to |
1603 | // instanciate an unnecessary object at call |
1604 | // site. |
1605 | // |
1606 | // Understand what the correct way to avoid this is. |
1607 | // This requires changes to findNodeForAtom |
1608 | // and should be addressed in the context of |
1609 | // revising that method. |
1610 | QString unused{}; |
1611 | // TODO: Having to const cast is really a code |
1612 | // smell and could result in undefined |
1613 | // behavior in some specific cases (e.g point |
1614 | // to something that is actually const). |
1615 | // |
1616 | // We should understand how to sequence the |
1617 | // code so that we have access to mutable data |
1618 | // when we need it and "freeze" the data |
1619 | // afterwards. |
1620 | // |
1621 | // If it we expect this form of mutability at |
1622 | // this point we should expose a non-const API |
1623 | // for the database, possibly limited to a |
1624 | // very specific scope of execution. |
1625 | // |
1626 | // Understand what the correct sequencing for |
1627 | // this processing is and revise this part. |
1628 | auto candidatePage = const_cast<Node *>(findNodeForAtom(a: atom, relative: nullptr, ref&: unused)); |
1629 | if (!candidatePage || !candidatePage->isPageNode()) break; |
1630 | |
1631 | auto page{static_cast<PageNode*>(candidatePage)}; |
1632 | |
1633 | // ignore self-references |
1634 | if (page == prev.first) break; |
1635 | |
1636 | if (prev.first) { |
1637 | prev.first->setLink( |
1638 | linkType: Node::NextLink, |
1639 | link: page->title(), |
1640 | // TODO: [possible-assertion-failure][imprecise-types][atoms-link] |
1641 | // As with other structures in QDoc we |
1642 | // are able to call methods that are |
1643 | // valid only on very specific states. |
1644 | // |
1645 | // For some of those calls we have |
1646 | // some defensive programming measures |
1647 | // that allow us to at least identify |
1648 | // the error during debugging, while |
1649 | // for others this may currently hide |
1650 | // some logic error. |
1651 | // |
1652 | // To avoid those cases, we should |
1653 | // strive to move those cases to a |
1654 | // compilation error, requiring a |
1655 | // statically analyzable state that |
1656 | // represents the current model. |
1657 | // |
1658 | // This would ensure that those |
1659 | // lingering class of bugs are |
1660 | // eliminated completely, forces a |
1661 | // more explicit codebase where the |
1662 | // current capabilities do not depend |
1663 | // on runtime values might not be |
1664 | // generally visible, and does not |
1665 | // require us to incur into the |
1666 | // required state, which may be rare, |
1667 | // simplifying our abilities to |
1668 | // evaluate all possible states. |
1669 | // |
1670 | // For linking atoms, LinkAtom is |
1671 | // available and might be a good |
1672 | // enough solution to move linkText |
1673 | // to. |
1674 | desc: atom->linkText() |
1675 | ); |
1676 | page->setLink( |
1677 | linkType: Node::PreviousLink, |
1678 | link: prev.first->title(), |
1679 | // TODO: [possible-assertion-failure][imprecise-types][atoms-link] |
1680 | desc: prev.second->linkText() |
1681 | ); |
1682 | } |
1683 | |
1684 | if (page == tocPage) |
1685 | break; |
1686 | |
1687 | // Find the navigation parent from the stack; we may have null pointers |
1688 | // for non-link list items, so skip those. |
1689 | qsizetype popped = 0; |
1690 | while (tocStack.size() > 1 && !tocStack.top()) { |
1691 | tocStack.pop(); |
1692 | ++popped; |
1693 | } |
1694 | |
1695 | page->setNavigationParent(tocStack.empty() ? nullptr : tocStack.top()); |
1696 | |
1697 | while (--popped > 0) |
1698 | tocStack.push(x: nullptr); |
1699 | |
1700 | tocStack.push(x: page); |
1701 | prev = { page, atom }; |
1702 | } |
1703 | break; |
1704 | default: |
1705 | break; |
1706 | } |
1707 | |
1708 | atom = atom->next(); |
1709 | } |
1710 | } else { |
1711 | Config::instance().get(var: configVar).location() |
1712 | .warning(QStringLiteral("Failed to find table of contents with title '%1'" ) |
1713 | .arg(a: tocTitle)); |
1714 | } |
1715 | } |
1716 | |
1717 | // Restore search order |
1718 | setSearchOrder(searchOrder); |
1719 | } |
1720 | |
1721 | QT_END_NAMESPACE |
1722 | |