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

source code of qttools/src/qdoc/qdoc/xmlgenerator.cpp