1 /****************************************************************************
2 **
3 ** Copyright (C) 2019 Thibaut Cuvelier
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the tools applications of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ** $QT_END_LICENSE$
26 **
27 ****************************************************************************/
28
29 /*
30 xmlgenerator.cpp
31 */
32
33 #include "xmlgenerator.h"
34 #include "qdocdatabase.h"
35
36 QT_BEGIN_NAMESPACE
37
38 /*!
39 Do not display \brief for QML/JS types, document and collection nodes
40 */
hasBrief(const Node * node)41 bool XmlGenerator::hasBrief(const Node *node)
42 {
43 return !(node->isQmlType() || node->isPageNode() || node->isCollectionNode()
44 || node->isJsType());
45 }
46
47 /*!
48 Determines whether the list atom should be shown with three columns
49 (constant-value-description).
50 */
isThreeColumnEnumValueTable(const Atom * atom)51 bool XmlGenerator::isThreeColumnEnumValueTable(const Atom *atom)
52 {
53 while (atom && !(atom->type() == Atom::ListRight && atom->string() == ATOM_LIST_VALUE)) {
54 if (atom->type() == Atom::ListItemLeft && !matchAhead(atom, Atom::ListItemRight))
55 return true;
56 atom = atom->next();
57 }
58 return false;
59 }
60
61 /*!
62 Header offset depending on the type of the node
63 */
hOffset(const Node * node)64 int XmlGenerator::hOffset(const Node *node)
65 {
66 switch (node->nodeType()) {
67 case Node::Namespace:
68 case Node::Class:
69 case Node::Struct:
70 case Node::Union:
71 case Node::Module:
72 return 2;
73 case Node::QmlModule:
74 case Node::QmlBasicType:
75 case Node::QmlType:
76 case Node::Page:
77 return 1;
78 case Node::Enum:
79 case Node::TypeAlias:
80 case Node::Typedef:
81 case Node::Function:
82 case Node::Property:
83 default:
84 return 3;
85 }
86 }
87
88 /*!
89 Rewrites the brief of this node depending on its first word.
90 Only for properties and variables (does nothing otherwise).
91 */
rewritePropertyBrief(const Atom * atom,const Node * relative)92 void XmlGenerator::rewritePropertyBrief(const Atom *atom, const Node *relative)
93 {
94 if (relative->nodeType() == Node::Property || relative->nodeType() == Node::Variable) {
95 atom = atom->next();
96 if (atom && atom->type() == Atom::String) {
97 QString firstWord =
98 atom->string().toLower().section(' ', 0, 0, QString::SectionSkipEmpty);
99 if (firstWord == QLatin1String("the") || firstWord == QLatin1String("a")
100 || firstWord == QLatin1String("an") || firstWord == QLatin1String("whether")
101 || firstWord == QLatin1String("which")) {
102 QString str = QLatin1String("This ")
103 + QLatin1String(relative->nodeType() == Node::Property ? "property"
104 : "variable")
105 + QLatin1String(" holds ") + atom->string().left(1).toLower()
106 + atom->string().mid(1);
107 const_cast<Atom *>(atom)->setString(str);
108 }
109 }
110 }
111 }
112
113 /*!
114 Returns the type of this atom as an enumeration.
115 */
typeFromString(const Atom * atom)116 Node::NodeType XmlGenerator::typeFromString(const Atom *atom)
117 {
118 const auto &name = atom->string();
119 if (name.startsWith(QLatin1String("qml")))
120 return Node::QmlModule;
121 else if (name.startsWith(QLatin1String("js")))
122 return Node::JsModule;
123 else if (name.startsWith(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 */
setImageFileName(const Node * relative,const QString & fileName)133 void 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 */
getAtomListValue(const Atom * atom)149 QPair<QString, int> XmlGenerator::getAtomListValue(const Atom *atom)
150 {
151 const Atom *lookAhead = atom->next();
152 if (!lookAhead)
153 return QPair<QString, int>(QString(), 1);
154
155 QString t = lookAhead->string();
156 lookAhead = lookAhead->next();
157 if (!lookAhead || lookAhead->type() != Atom::ListTagRight)
158 return QPair<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(0).isDigit())
167 t += QLatin1String("Qt ");
168 t += lookAhead->string() + QLatin1String(")");
169 skipAhead = 4;
170 } else {
171 skipAhead = 1;
172 }
173 return QPair<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 */
getTableWidthAttr(const Atom * atom)182 QPair<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(0);
189 if (atom->count() > 1)
190 p1 = atom->string(1);
191 }
192 if (!p0.isEmpty()) {
193 if (p0 == QLatin1String("borderless"))
194 attr = p0;
195 else if (p0.contains(QLatin1Char('%')))
196 width = p0;
197 }
198 if (!p1.isEmpty()) {
199 if (p1 == QLatin1String("borderless"))
200 attr = p1;
201 else if (p1.contains(QLatin1Char('%')))
202 width = p1;
203 }
204 return QPair<QString, QString>(width, attr);
205 }
206
207 /*!
208 Registers an anchor reference and returns a unique
209 and cleaned copy of the reference (the one that should be
210 used in the output).
211 To ensure unicity throughout the document, this method
212 uses the \a refMap cache.
213 */
registerRef(const QString & ref)214 QString XmlGenerator::registerRef(const QString &ref)
215 {
216 QString clean = Generator::cleanRef(ref);
217
218 for (;;) {
219 QString &prevRef = refMap[clean.toLower()];
220 if (prevRef.isEmpty()) {
221 prevRef = ref;
222 break;
223 } else if (prevRef == ref) {
224 break;
225 }
226 clean += QLatin1Char('x');
227 }
228 return clean;
229 }
230
231 /*!
232 Generates a clean and unique reference for the given \a node.
233 This reference may depend on the type of the node (typedef,
234 QML signal, etc.)
235 */
refForNode(const Node * node)236 QString XmlGenerator::refForNode(const Node *node)
237 {
238 QString ref;
239 switch (node->nodeType()) {
240 case Node::Enum:
241 ref = node->name() + "-enum";
242 break;
243 case Node::TypeAlias:
244 ref = node->name() + "-alias";
245 break;
246 case Node::Typedef: {
247 const auto tdn = static_cast<const TypedefNode *>(node);
248 if (tdn->associatedEnum())
249 return refForNode(tdn->associatedEnum());
250 ref = node->name() + "-typedef";
251 } break;
252 case Node::Function: {
253 const auto fn = static_cast<const FunctionNode *>(node);
254 switch (fn->metaness()) {
255 case FunctionNode::JsSignal:
256 case FunctionNode::QmlSignal:
257 ref = fn->name() + "-signal";
258 break;
259 case FunctionNode::JsSignalHandler:
260 case FunctionNode::QmlSignalHandler:
261 ref = fn->name() + "-signal-handler";
262 break;
263 case FunctionNode::JsMethod:
264 case FunctionNode::QmlMethod:
265 ref = fn->name() + "-method";
266 if (fn->overloadNumber() != 0)
267 ref += QLatin1Char('-') + QString::number(fn->overloadNumber());
268 break;
269 default:
270 if (fn->hasOneAssociatedProperty() && fn->doc().isEmpty()) {
271 return refForNode(fn->firstAssociatedProperty());
272 } else {
273 ref = fn->name();
274 if (fn->overloadNumber() != 0)
275 ref += QLatin1Char('-') + QString::number(fn->overloadNumber());
276 }
277 break;
278 }
279 } break;
280 case Node::JsProperty:
281 case Node::QmlProperty:
282 if (node->isAttached())
283 ref = node->name() + "-attached-prop";
284 else
285 ref = node->name() + "-prop";
286 break;
287 case Node::Property:
288 ref = node->name() + "-prop";
289 break;
290 case Node::Variable:
291 ref = node->name() + "-var";
292 break;
293 case Node::SharedComment:
294 if (node->isPropertyGroup())
295 ref = node->name() + "-prop";
296 break;
297 default:
298 break;
299 }
300 return registerRef(ref);
301 }
302
303 /*!
304 Construct the link string for the \a node and return it.
305 The \a relative node is used to decide whether the link
306 we are generating is in the same file as the target.
307 Note the relative node can be 0, which pretty much
308 guarantees that the link and the target aren't in the
309 same file.
310 */
linkForNode(const Node * node,const Node * relative)311 QString XmlGenerator::linkForNode(const Node *node, const Node *relative)
312 {
313 if (node == nullptr)
314 return QString();
315 if (!node->url().isEmpty())
316 return node->url();
317 if (fileBase(node).isEmpty())
318 return QString();
319 if (node->isPrivate())
320 return QString();
321
322 QString fn = fileName(node);
323 if (node && node->parent() && (node->parent()->isQmlType() || node->parent()->isJsType())
324 && node->parent()->isAbstract()) {
325 if (Generator::qmlTypeContext()) {
326 if (Generator::qmlTypeContext()->inherits(node->parent())) {
327 fn = fileName(Generator::qmlTypeContext());
328 } else if (node->parent()->isInternal()) {
329 node->doc().location().warning(tr("Cannot link to property in internal type '%1'")
330 .arg(node->parent()->name()));
331 return QString();
332 }
333 }
334 }
335
336 QString link = fn;
337
338 if (!node->isPageNode() || node->isPropertyGroup()) {
339 QString ref = refForNode(node);
340 if (relative && fn == fileName(relative) && ref == refForNode(relative))
341 return QString();
342
343 link += QLatin1Char('#');
344 link += ref;
345 }
346
347 /*
348 If the output is going to subdirectories, then if the
349 two nodes will be output to different directories, then
350 the link must go up to the parent directory and then
351 back down into the other subdirectory.
352 */
353 if (node && relative && (node != relative)) {
354 if (useOutputSubdirs() && !node->isExternalPage()
355 && node->outputSubdirectory() != relative->outputSubdirectory()) {
356 if (link.startsWith(QString(node->outputSubdirectory() + QLatin1Char('/')))) {
357 link.prepend(QString("../"));
358 } else {
359 link.prepend(QString("../" + node->outputSubdirectory() + QLatin1Char('/')));
360 }
361 }
362 }
363 return link;
364 }
365
366 /*!
367 This function is called for links, i.e. for words that
368 are marked with the qdoc link command. For autolinks
369 that are not marked with the qdoc link command, the
370 getAutoLink() function is called
371
372 It returns the string for a link found by using the data
373 in the \a atom to search the database. It also sets \a node
374 to point to the target node for that link. \a relative points
375 to the node holding the qdoc comment where the link command
376 was found.
377 */
getLink(const Atom * atom,const Node * relative,const Node ** node)378 QString XmlGenerator::getLink(const Atom *atom, const Node *relative, const Node **node)
379 {
380 const QString &t = atom->string();
381 if (t.at(0) == QChar('h')) {
382 if (t.startsWith("http:") || t.startsWith("https:"))
383 return t;
384 } else if (t.at(0) == QChar('f')) {
385 if (t.startsWith("file:") || t.startsWith("ftp:"))
386 return t;
387 } else if (t.at(0) == QChar('m')) {
388 if (t.startsWith("mailto:"))
389 return t;
390 }
391 return getAutoLink(atom, relative, node);
392 }
393
394 /*!
395 This function is called for autolinks, i.e. for words that
396 are not marked with the qdoc link command that qdoc has
397 reason to believe should be links. For links marked with
398 the qdoc link command, the getLink() function is called.
399
400 It returns the string for a link found by using the data
401 in the \a atom to search the database. It also sets \a node
402 to point to the target node for that link. \a relative points
403 to the node holding the qdoc comment where the link command
404 was found.
405 */
getAutoLink(const Atom * atom,const Node * relative,const Node ** node)406 QString XmlGenerator::getAutoLink(const Atom *atom, const Node *relative, const Node **node)
407 {
408 QString ref;
409
410 *node = qdb_->findNodeForAtom(atom, relative, ref);
411 if (!(*node))
412 return QString();
413
414 QString link = (*node)->url();
415 if (link.isEmpty())
416 link = linkForNode(*node, relative);
417 if (!ref.isEmpty()) {
418 int hashtag = link.lastIndexOf(QChar('#'));
419 if (hashtag != -1)
420 link.truncate(hashtag);
421 link += QLatin1Char('#') + ref;
422 }
423 return link;
424 }
425
anchorForNode(const Node * node)426 const QPair<QString, QString> XmlGenerator::anchorForNode(const Node *node)
427 {
428 QPair<QString, QString> anchorPair;
429
430 anchorPair.first = Generator::fileName(node);
431 if (node->isTextPageNode())
432 anchorPair.second = node->title();
433
434 return anchorPair;
435 }
436
437 /*!
438 Returns a string describing the \a node type.
439 */
targetType(const Node * node)440 QString XmlGenerator::targetType(const Node *node)
441 {
442 if (!node)
443 return QStringLiteral("external");
444
445 switch (node->nodeType()) {
446 case Node::Namespace:
447 return QStringLiteral("namespace");
448 case Node::Class:
449 case Node::Struct:
450 case Node::Union:
451 return QStringLiteral("class");
452 case Node::Page:
453 case Node::Example:
454 return QStringLiteral("page");
455 case Node::Enum:
456 return QStringLiteral("enum");
457 case Node::TypeAlias:
458 return QStringLiteral("alias");
459 case Node::Typedef:
460 return QStringLiteral("typedef");
461 case Node::Property:
462 return QStringLiteral("property");
463 case Node::Function:
464 return QStringLiteral("function");
465 case Node::Variable:
466 return QStringLiteral("variable");
467 case Node::Module:
468 return QStringLiteral("module");
469 default:
470 break;
471 }
472 return QString();
473 }
474
475 QT_END_NAMESPACE
476