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 keep references unique throughout the document, this method
239 uses the \a refMap cache. Anchor IDs are case-sensitive, so refs
240 that differ only in case remain distinct.
241 */
242QString XmlGenerator::registerRef(const QString &ref, bool xmlCompliant)
243{
244 QString cleanRef = Generator::cleanRef(ref, xmlCompliant);
245
246 for (;;) {
247 QString &prevRef = refMap[cleanRef];
248 if (prevRef.isEmpty()) {
249 // This reference has never been met before for this document: register it.
250 prevRef = ref;
251 break;
252 } else if (prevRef == ref) {
253 // This exact same reference was already found. This case typically occurs within refForNode.
254 break;
255 }
256 cleanRef += QLatin1Char('x');
257 }
258 return cleanRef;
259}
260
261/*!
262 Generates a clean and unique reference for the given \a node.
263 This reference may depend on the type of the node (typedef,
264 QML signal, etc.)
265
266 Delegates base anchor computation to the shared computeAnchorId()
267 utility, then runs the result through registerRef() for
268 per-document collision handling.
269 */
270QString XmlGenerator::refForNode(const Node *node)
271{
272 QString ref = computeAnchorId(node);
273 return registerRef(ref);
274}
275
276
277/*!
278 Construct the link string for the \a node and return it.
279 The \a relative node is used to decide whether the link
280 we are generating is in the same file as the target.
281 Note the relative node can be 0, which pretty much
282 guarantees that the link and the target aren't in the
283 same file.
284 */
285QString XmlGenerator::linkForNode(const Node *node, const Node *relative)
286{
287 if (node == nullptr)
288 return QString();
289 if (!node->url().isNull())
290 return node->url();
291 if (fileBase(node).isEmpty())
292 return QString();
293 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
294 const NodeContext context = node->createContext();
295 if (!InclusionFilter::isIncluded(policy, context))
296 return QString();
297
298 QString fn = fileName(node);
299 if (node->parent() && node->parent()->isQmlType() && node->parent()->isAbstract()) {
300 if (Generator::qmlTypeContext()) {
301 if (Generator::qmlTypeContext()->inherits(node->parent())) {
302 fn = fileName(Generator::qmlTypeContext());
303 } else if (node->parent()->isInternal() && !noLinkErrors()) {
304 node->doc().location().warning(
305 QStringLiteral("Cannot link to property in internal type '%1'")
306 .arg(node->parent()->name()));
307 return QString();
308 }
309 }
310 }
311
312 QString link = fn;
313
314 if (!node->isPageNode() || node->isPropertyGroup()) {
315 QString ref = refForNode(node);
316 if (relative && fn == fileName(relative) && ref == refForNode(relative))
317 return QString();
318
319 link += QLatin1Char('#');
320 link += ref;
321 }
322
323 /*
324 If the output is going to subdirectories, the two nodes have
325 different output directories if `node` was read from index or
326 is located in a different tree than `relative`. These two
327 conditions may differ only when running in single-exec mode
328 where QDoc does not load index files (or mark nodes as being
329 index nodes).
330 */
331 if (relative && (node != relative)) {
332 if (useOutputSubdirs() && !node->isExternalPage() &&
333 (node->isIndexNode() || node->tree() != relative->tree()))
334 link.prepend("../%1/"_L1.arg(node->tree()->physicalModuleName()));
335 }
336 return link;
337}
338
339/*!
340 This function is called for links, i.e. for words that
341 are marked with the qdoc link command. For autolinks
342 that are not marked with the qdoc link command, the
343 getAutoLink() function is called
344
345 It returns the string for a link found by using the data
346 in the \a atom to search the database. It also sets \a node
347 to point to the target node for that link. \a relative points
348 to the node holding the qdoc comment where the link command
349 was found.
350 */
351QString XmlGenerator::getLink(const Atom *atom, const Node *relative, const Node **node)
352{
353 const QString &t = atom->string();
354
355 if (t.isEmpty())
356 return t;
357
358 if (t.at(0) == QChar('h')) {
359 if (t.startsWith("http:") || t.startsWith("https:"))
360 return t;
361 } else if (t.at(0) == QChar('f')) {
362 if (t.startsWith("file:") || t.startsWith("ftp:"))
363 return t;
364 } else if (t.at(0) == QChar('m')) {
365 if (t.startsWith("mailto:"))
366 return t;
367 }
368 return getAutoLink(atom, relative, node);
369}
370
371/*!
372 This function is called for autolinks, i.e. for words that
373 are not marked with the qdoc link command that qdoc has
374 reason to believe should be links.
375
376 Returns the string for a link found by using the data in the \a atom to
377 search the database. \a relative points to the node holding the qdoc comment
378 where the link command was found. Sets \a node to point to the target node
379 for that link if a target was found. \a genus specifies the kind of target to
380 look for.
381
382 If no target was found, returns an empty string which may also be null.
383 */
384QString XmlGenerator::getAutoLink(const Atom *atom, const Node *relative, const Node **node,
385 Genus genus)
386{
387 QString ref;
388 *node = nullptr;
389
390 // If there is an overlap between the requested genus and the parent node's genus,
391 // search for nodes with the common genus first. This helps to find more relevant
392 // targets in situations where identically-titled nodes are available.
393 if (genus != Genus::DontCare && relative && relative->genus() != Genus::DontCare) {
394 const Genus common = commonGenusType(genus, relative->genus());
395 if (common != Genus::DontCare)
396 *node = m_qdb->findNodeForAtom(atom, relative, ref, common);
397 }
398
399 if (!(*node))
400 *node = m_qdb->findNodeForAtom(atom, relative, ref, genus);
401 if (!(*node))
402 return QString();
403
404 QString link = (*node)->url();
405 if (link.isNull()) {
406 link = linkForNode(*node, relative);
407 } else if (link.isEmpty()) {
408 return link; // Explicit empty url (node is ignored as a link target)
409 }
410 if (!ref.isEmpty()) {
411 qsizetype hashtag = link.lastIndexOf(QChar('#'));
412 if (hashtag != -1)
413 link.truncate(hashtag);
414 link += QLatin1Char('#') + ref;
415 }
416 return link;
417}
418
419std::pair<QString, QString> XmlGenerator::anchorForNode(const Node *node)
420{
421 std::pair<QString, QString> anchorPair;
422
423 anchorPair.first = Generator::fileName(node);
424 if (node->isTextPageNode())
425 anchorPair.second = node->title();
426
427 return anchorPair;
428}
429
430/*!
431 Returns a string describing the \a node type.
432 */
433QString XmlGenerator::targetType(const Node *node)
434{
435 if (!node)
436 return QStringLiteral("external");
437
438 switch (node->nodeType()) {
439 case NodeType::Namespace:
440 return QStringLiteral("namespace");
441 case NodeType::Class:
442 case NodeType::Struct:
443 case NodeType::Union:
444 return QStringLiteral("class");
445 case NodeType::Page:
446 case NodeType::Example:
447 return QStringLiteral("page");
448 case NodeType::Enum:
449 return QStringLiteral("enum");
450 case NodeType::TypeAlias:
451 return QStringLiteral("alias");
452 case NodeType::Typedef:
453 return QStringLiteral("typedef");
454 case NodeType::Property:
455 return QStringLiteral("property");
456 case NodeType::Function:
457 return QStringLiteral("function");
458 case NodeType::Variable:
459 return QStringLiteral("variable");
460 case NodeType::Module:
461 return QStringLiteral("module");
462 default:
463 break;
464 }
465 return QString();
466}
467
468QT_END_NAMESPACE
#define ATOM_LIST_VALUE
Definition atom.h:211
#define CONFIG_PRODUCTNAME
Definition config.h:437