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