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