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