Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Toggle main menu visibility
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"
12
#
include
"inclusionfilter.h"
13
#
include
"inclusionpolicy.h"
14
#
include
"node.h"
15
#
include
"qdocdatabase.h"
16
#
include
"typedefnode.h"
17
18
#
include
<
type_traits
>
19
20
using
namespace
Qt::Literals::StringLiterals;
21
22
QT_BEGIN_NAMESPACE
23
24
const
QRegularExpression XmlGenerator::m_funcLeftParen(QStringLiteral(
"^\\S+(\\(.*\\))"
));
25
26
XmlGenerator::XmlGenerator(FileResolver& file_resolver) : Generator(file_resolver) {}
27
28
/*!
29
Do not display \brief for QML types, document and collection nodes
30
*/
31
bool
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
*/
40
bool
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
*/
53
bool
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
*/
74
int
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
*/
103
void
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
*/
126
NodeType 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
*/
141
void
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
*/
157
std::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
*/
195
std::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
*/
242
QString 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
*/
270
QString 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
*/
285
QString 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
*/
351
QString 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
*/
384
QString 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
419
std::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
*/
433
QString 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
468
QT_END_NAMESPACE
ATOM_LIST_VALUE
#define ATOM_LIST_VALUE
Definition
atom.h:211
CONFIG_PRODUCTNAME
#define CONFIG_PRODUCTNAME
Definition
config.h:443
qttools
src
qdoc
qdoc
src
qdoc
xmlgenerator.cpp
Generated on
for Qt by
1.18.0