Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
xmlgenerator.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 Thibaut Cuvelier
2// Copyright (C) 2021 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
4
5#include "xmlgenerator.h"
6
7#include "config.h"
8#include "enumnode.h"
9#include "examplenode.h"
10#include "functionnode.h"
11#include "anchorid.h"
14#include "node.h"
15#include "qdocdatabase.h"
16#include "typedefnode.h"
17
18#include <type_traits>
19
20using namespace Qt::Literals::StringLiterals;
21
22QT_BEGIN_NAMESPACE
23
24const QRegularExpression XmlGenerator::m_funcLeftParen(QStringLiteral("^\\S+(\\‍(.*\\‍))"));
25
26XmlGenerator::XmlGenerator(FileResolver& file_resolver) : Generator(file_resolver) {}
27
28/*!
29 Do not display \brief for QML types, document and collection nodes
30 */
31bool XmlGenerator::hasBrief(const Node *node)
32{
33 return !(node->isQmlType() || node->isPageNode() || node->isCollectionNode());
34}
35
36/*!
37 Determines whether the list atom should be shown with three columns
38 (constant-value-description).
39 */
40bool XmlGenerator::isThreeColumnEnumValueTable(const Atom *atom)
41{
42 while (atom && !(atom->type() == Atom::ListRight && atom->string() == ATOM_LIST_VALUE)) {
43 if (atom->type() == Atom::ListItemLeft && !matchAhead(atom, Atom::ListItemRight))
44 return true;
45 atom = atom->next();
46 }
47 return false;
48}
49
50/*!
51 Determines whether the list atom should be shown with just one column (value).
52 */
53bool XmlGenerator::isOneColumnValueTable(const Atom *atom)
54{
55 if (atom->type() != Atom::ListLeft || atom->string() != ATOM_LIST_VALUE)
56 return false;
57
58 while (atom && atom->type() != Atom::ListTagRight)
59 atom = atom->next();
60
61 if (atom) {
62 if (!matchAhead(atom, Atom::ListItemLeft))
63 return false;
64 if (!atom->next())
65 return false;
66 return matchAhead(atom->next(), Atom::ListItemRight);
67 }
68 return false;
69}
70
71/*!
72 Header offset depending on the type of the node
73 */
74int XmlGenerator::hOffset(const Node *node)
75{
76 switch (node->nodeType()) {
77 case NodeType::Namespace:
78 case NodeType::Class:
79 case NodeType::Struct:
80 case NodeType::Union:
81 case NodeType::Module:
82 return 2;
83 case NodeType::QmlModule:
84 case NodeType::QmlValueType:
85 case NodeType::QmlType:
86 case NodeType::Page:
87 case NodeType::Group:
88 return 1;
89 case NodeType::Enum:
90 case NodeType::TypeAlias:
91 case NodeType::Typedef:
92 case NodeType::Function:
93 case NodeType::Property:
94 default:
95 return 3;
96 }
97}
98
99/*!
100 Rewrites the brief of this node depending on its first word.
101 Only for properties and variables (does nothing otherwise).
102 */
103void XmlGenerator::rewritePropertyBrief(const Atom *atom, const Node *relative)
104{
105 if (relative->nodeType() != NodeType::Property && relative->nodeType() != NodeType::Variable)
106 return;
107 atom = atom->next();
108 if (!atom || atom->type() != Atom::String)
109 return;
110
111 const QString firstWord =
112 atom->string().toLower().section(' ', 0, 0, QString::SectionSkipEmpty);
113 const QStringList words{ "the", "a", "an", "whether", "which" };
114 if (words.contains(firstWord)) {
115 QString str = QLatin1String("This ")
116 + QLatin1String(relative->nodeType() == NodeType::Property ? "property" : "variable")
117 + QLatin1String(" holds ") + atom->string().left(1).toLower()
118 + atom->string().mid(1);
119 const_cast<Atom *>(atom)->setString(str);
120 }
121}
122
123/*!
124 Returns the type of this atom as an enumeration.
125 */
126NodeType XmlGenerator::typeFromString(const Atom *atom)
127{
128 const auto &name = atom->string();
129 if (name.startsWith(QLatin1String("qml")))
130 return NodeType::QmlModule;
131 else if (name.startsWith(QLatin1String("groups")))
132 return NodeType::Group;
133 else
134 return NodeType::Module;
135}
136
137/*!
138 For images shown in examples, set the image file to the one it
139 will have once the documentation is generated.
140 */
141void XmlGenerator::setImageFileName(const Node *relative, const QString &fileName)
142{
143 if (relative->isExample()) {
144 const auto cen = static_cast<const ExampleNode *>(relative);
145 if (cen->imageFileName().isEmpty()) {
146 auto *en = const_cast<ExampleNode *>(cen);
147 en->setImageFileName(fileName);
148 }
149 }
150}
151
152/*!
153 Handles the differences in lists between list tags and since tags, and
154 returns the content of the list entry \a atom (first member of the pair).
155 It also returns the number of items to skip ahead (second member of the pair).
156 */
157std::pair<QString, int> XmlGenerator::getAtomListValue(const Atom *atom)
158{
159 const Atom *lookAhead = atom->next();
160 if (!lookAhead)
161 return std::pair<QString, int>(QString(), 1);
162
163 QString t = lookAhead->string();
164 lookAhead = lookAhead->next();
165 if (!lookAhead || lookAhead->type() != Atom::ListTagRight)
166 return std::pair<QString, int>(QString(), 1);
167
168 lookAhead = lookAhead->next();
169 int skipAhead;
170 if (lookAhead && lookAhead->type() == Atom::SinceTagLeft) {
171 lookAhead = lookAhead->next();
172 Q_ASSERT(lookAhead && lookAhead->type() == Atom::String);
173 t += QLatin1String(" (since ");
174 const QString sinceString = lookAhead->string();
175 if (sinceString.at(0).isDigit()) {
176 const QString productName = Config::instance().get(CONFIG_PRODUCTNAME).asString();
177 t += productName.isEmpty() ? sinceString : productName + " " + sinceString;
178 } else {
179 t += sinceString;
180 }
181 t += QLatin1String(")");
182 skipAhead = 4;
183 } else {
184 skipAhead = 1;
185 }
186 return std::pair<QString, int>(t, skipAhead);
187}
188
189/*!
190 Parses the table attributes from the given \a atom.
191 This method returns a pair containing the width (%) and
192 the attribute for this table (either "generic" or
193 "borderless").
194 */
195std::pair<QString, QString> XmlGenerator::getTableWidthAttr(const Atom *atom)
196{
197 QString p0, p1;
198 QString attr = "generic";
199 QString width;
200 if (atom->count() > 0) {
201 p0 = atom->string(0);
202 if (atom->count() > 1)
203 p1 = atom->string(1);
204 }
205 if (!p0.isEmpty()) {
206 if (p0 == QLatin1String("borderless"))
207 attr = p0;
208 else if (p0.contains(QLatin1Char('%')))
209 width = p0;
210 }
211 if (!p1.isEmpty()) {
212 if (p1 == QLatin1String("borderless"))
213 attr = std::move(p1);
214 else if (p1.contains(QLatin1Char('%')))
215 width = std::move(p1);
216 }
217
218 // Many times, in the documentation, there is a space before the % sign:
219 // this breaks the parsing logic above.
220 if (width == QLatin1String("%")) {
221 // The percentage is typically stored in p0, parse it as an int.
222 bool ok = false;
223 int widthPercentage = p0.toInt(&ok);
224 if (ok) {
225 width = QString::number(widthPercentage) + "%";
226 } else {
227 width = {};
228 }
229 }
230
231 return {width, attr};
232}
233
234/*!
235 Registers an anchor reference and returns a unique
236 and cleaned copy of the reference (the one that should be
237 used in the output).
238 To ensure unicity throughout the document, this method
239 uses the \a refMap cache.
240 */
241QString XmlGenerator::registerRef(const QString &ref, bool xmlCompliant)
242{
243 QString cleanRef = Generator::cleanRef(ref, xmlCompliant);
244
245 for (;;) {
246 QString &prevRef = refMap[cleanRef.toLower()];
247 if (prevRef.isEmpty()) {
248 // This reference has never been met before for this document: register it.
249 prevRef = ref;
250 break;
251 } else if (prevRef == ref) {
252 // This exact same reference was already found. This case typically occurs within refForNode.
253 break;
254 }
255 cleanRef += QLatin1Char('x');
256 }
257 return cleanRef;
258}
259
260/*!
261 Generates a clean and unique reference for the given \a node.
262 This reference may depend on the type of the node (typedef,
263 QML signal, etc.)
264
265 Delegates base anchor computation to the shared computeAnchorId()
266 utility, then runs the result through registerRef() for
267 per-document collision handling.
268 */
269QString XmlGenerator::refForNode(const Node *node)
270{
271 QString ref = computeAnchorId(node);
272 return registerRef(ref);
273}
274
275
276/*!
277 Construct the link string for the \a node and return it.
278 The \a relative node is used to decide whether the link
279 we are generating is in the same file as the target.
280 Note the relative node can be 0, which pretty much
281 guarantees that the link and the target aren't in the
282 same file.
283 */
284QString XmlGenerator::linkForNode(const Node *node, const Node *relative)
285{
286 if (node == nullptr)
287 return QString();
288 if (!node->url().isNull())
289 return node->url();
290 if (fileBase(node).isEmpty())
291 return QString();
292 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
293 const NodeContext context = node->createContext();
294 if (!InclusionFilter::isIncluded(policy, context))
295 return QString();
296
297 QString fn = fileName(node);
298 if (node->parent() && node->parent()->isQmlType() && node->parent()->isAbstract()) {
299 if (Generator::qmlTypeContext()) {
300 if (Generator::qmlTypeContext()->inherits(node->parent())) {
301 fn = fileName(Generator::qmlTypeContext());
302 } else if (node->parent()->isInternal() && !noLinkErrors()) {
303 node->doc().location().warning(
304 QStringLiteral("Cannot link to property in internal type '%1'")
305 .arg(node->parent()->name()));
306 return QString();
307 }
308 }
309 }
310
311 QString link = fn;
312
313 if (!node->isPageNode() || node->isPropertyGroup()) {
314 QString ref = refForNode(node);
315 if (relative && fn == fileName(relative) && ref == refForNode(relative))
316 return QString();
317
318 link += QLatin1Char('#');
319 link += ref;
320 }
321
322 /*
323 If the output is going to subdirectories, the two nodes have
324 different output directories if `node` was read from index or
325 is located in a different tree than `relative`. These two
326 conditions may differ only when running in single-exec mode
327 where QDoc does not load index files (or mark nodes as being
328 index nodes).
329 */
330 if (relative && (node != relative)) {
331 if (useOutputSubdirs() && !node->isExternalPage() &&
332 (node->isIndexNode() || node->tree() != relative->tree()))
333 link.prepend("../%1/"_L1.arg(node->tree()->physicalModuleName()));
334 }
335 return link;
336}
337
338/*!
339 This function is called for links, i.e. for words that
340 are marked with the qdoc link command. For autolinks
341 that are not marked with the qdoc link command, the
342 getAutoLink() function is called
343
344 It returns the string for a link found by using the data
345 in the \a atom to search the database. It also sets \a node
346 to point to the target node for that link. \a relative points
347 to the node holding the qdoc comment where the link command
348 was found.
349 */
350QString XmlGenerator::getLink(const Atom *atom, const Node *relative, const Node **node)
351{
352 const QString &t = atom->string();
353
354 if (t.isEmpty())
355 return t;
356
357 if (t.at(0) == QChar('h')) {
358 if (t.startsWith("http:") || t.startsWith("https:"))
359 return t;
360 } else if (t.at(0) == QChar('f')) {
361 if (t.startsWith("file:") || t.startsWith("ftp:"))
362 return t;
363 } else if (t.at(0) == QChar('m')) {
364 if (t.startsWith("mailto:"))
365 return t;
366 }
367 return getAutoLink(atom, relative, node);
368}
369
370/*!
371 This function is called for autolinks, i.e. for words that
372 are not marked with the qdoc link command that qdoc has
373 reason to believe should be links.
374
375 Returns the string for a link found by using the data in the \a atom to
376 search the database. \a relative points to the node holding the qdoc comment
377 where the link command was found. Sets \a node to point to the target node
378 for that link if a target was found. \a genus specifies the kind of target to
379 look for.
380
381 If no target was found, returns an empty string which may also be null.
382 */
383QString XmlGenerator::getAutoLink(const Atom *atom, const Node *relative, const Node **node,
384 Genus genus)
385{
386 QString ref;
387 *node = nullptr;
388
389 // If there is an overlap between the requested genus and the parent node's genus,
390 // search for nodes with the common genus first. This helps to find more relevant
391 // targets in situations where identically-titled nodes are available.
392 if (genus != Genus::DontCare && relative && relative->genus() != Genus::DontCare) {
393 using GenusValue = std::underlying_type_t<Genus>;
394 const Genus common = static_cast<Genus>(
395 static_cast<GenusValue>(genus) &
396 static_cast<GenusValue>(relative->genus())
397 );
398 if (common != Genus::DontCare)
399 *node = m_qdb->findNodeForAtom(atom, relative, ref, common);
400 }
401
402 if (!(*node))
403 *node = m_qdb->findNodeForAtom(atom, relative, ref, genus);
404 if (!(*node))
405 return QString();
406
407 QString link = (*node)->url();
408 if (link.isNull()) {
409 link = linkForNode(*node, relative);
410 } else if (link.isEmpty()) {
411 return link; // Explicit empty url (node is ignored as a link target)
412 }
413 if (!ref.isEmpty()) {
414 qsizetype hashtag = link.lastIndexOf(QChar('#'));
415 if (hashtag != -1)
416 link.truncate(hashtag);
417 link += QLatin1Char('#') + ref;
418 }
419 return link;
420}
421
422std::pair<QString, QString> XmlGenerator::anchorForNode(const Node *node)
423{
424 std::pair<QString, QString> anchorPair;
425
426 anchorPair.first = Generator::fileName(node);
427 if (node->isTextPageNode())
428 anchorPair.second = node->title();
429
430 return anchorPair;
431}
432
433/*!
434 Returns a string describing the \a node type.
435 */
436QString XmlGenerator::targetType(const Node *node)
437{
438 if (!node)
439 return QStringLiteral("external");
440
441 switch (node->nodeType()) {
442 case NodeType::Namespace:
443 return QStringLiteral("namespace");
444 case NodeType::Class:
445 case NodeType::Struct:
446 case NodeType::Union:
447 return QStringLiteral("class");
448 case NodeType::Page:
449 case NodeType::Example:
450 return QStringLiteral("page");
451 case NodeType::Enum:
452 return QStringLiteral("enum");
453 case NodeType::TypeAlias:
454 return QStringLiteral("alias");
455 case NodeType::Typedef:
456 return QStringLiteral("typedef");
457 case NodeType::Property:
458 return QStringLiteral("property");
459 case NodeType::Function:
460 return QStringLiteral("function");
461 case NodeType::Variable:
462 return QStringLiteral("variable");
463 case NodeType::Module:
464 return QStringLiteral("module");
465 default:
466 break;
467 }
468 return QString();
469}
470
471QT_END_NAMESPACE
#define ATOM_LIST_VALUE
Definition atom.h:222
#define CONFIG_PRODUCTNAME
Definition config.h:437