1 /****************************************************************************
2 **
3 ** Copyright (C) 2019 The Qt Company Ltd.
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   htmlgenerator.cpp
31 */
32 
33 #include "htmlgenerator.h"
34 
35 #include "config.h"
36 #include "codemarker.h"
37 #include "codeparser.h"
38 #include "helpprojectwriter.h"
39 #include "node.h"
40 #include "qdocdatabase.h"
41 #include "separator.h"
42 #include "tree.h"
43 #include "quoter.h"
44 
45 #include <QtCore/qdebug.h>
46 #include <QtCore/qiterator.h>
47 #include <QtCore/qlist.h>
48 #include <QtCore/qmap.h>
49 #include <QtCore/qtextcodec.h>
50 #include <QtCore/quuid.h>
51 #include <QtCore/qversionnumber.h>
52 
53 #include <ctype.h>
54 
55 QT_BEGIN_NAMESPACE
56 
57 int HtmlGenerator::id = 0;
58 bool HtmlGenerator::debugging_on = false;
59 
60 QString HtmlGenerator::divNavTop;
61 
62 static bool showBrokenLinks = false;
63 
64 static QRegExp linkTag("(<@link node=\"([^\"]+)\">).*(</@link>)");
65 static QRegExp funcTag("(<@func target=\"([^\"]*)\">)(.*)(</@func>)");
66 static QRegExp typeTag("(<@(type|headerfile|func)(?: +[^>]*)?>)(.*)(</@\\2>)");
67 static QRegExp spanTag("</@(?:comment|preprocessor|string|char|number|op|type|name|keyword)>");
68 static QRegExp unknownTag("</?@[^>]*>");
69 
addLink(const QString & linkTarget,const QStringRef & nestedStuff,QString * res)70 static void addLink(const QString &linkTarget, const QStringRef &nestedStuff, QString *res)
71 {
72     if (!linkTarget.isEmpty()) {
73         *res += QLatin1String("<a href=\"");
74         *res += linkTarget;
75         *res += QLatin1String("\">");
76         *res += nestedStuff;
77         *res += QLatin1String("</a>");
78     } else {
79         *res += nestedStuff;
80     }
81 }
82 
83 /*!
84   Constructs the HTML output generator.
85  */
HtmlGenerator()86 HtmlGenerator::HtmlGenerator()
87     : codeIndent(0),
88       helpProjectWriter(nullptr),
89       inObsoleteLink(false),
90       funcLeftParen("\\S(\\()"),
91       obsoleteLinks(false)
92 {
93 }
94 
95 /*!
96   Destroys the HTML output generator. Deletes the singleton
97   instance of HelpProjectWriter.
98  */
~HtmlGenerator()99 HtmlGenerator::~HtmlGenerator()
100 {
101     if (helpProjectWriter) {
102         delete helpProjectWriter;
103         helpProjectWriter = nullptr;
104     }
105 }
106 
107 /*!
108   Initializes the HTML output generator's data structures
109   from the configuration (Config) singleton.
110  */
initializeGenerator()111 void HtmlGenerator::initializeGenerator()
112 {
113     static const struct
114     {
115         const char *key;
116         const char *left;
117         const char *right;
118     } defaults[] = { { ATOM_FORMATTING_BOLD, "<b>", "</b>" },
119                      { ATOM_FORMATTING_INDEX, "<!--", "-->" },
120                      { ATOM_FORMATTING_ITALIC, "<i>", "</i>" },
121                      { ATOM_FORMATTING_PARAMETER, "<i>", "</i>" },
122                      { ATOM_FORMATTING_SUBSCRIPT, "<sub>", "</sub>" },
123                      { ATOM_FORMATTING_SUPERSCRIPT, "<sup>", "</sup>" },
124                      { ATOM_FORMATTING_TELETYPE, "<code>",
125                        "</code>" }, // <tt> tag is not supported in HTML5
126                      { ATOM_FORMATTING_UICONTROL, "<b>", "</b>" },
127                      { ATOM_FORMATTING_UNDERLINE, "<u>", "</u>" },
128                      { nullptr, nullptr, nullptr } };
129 
130     Generator::initializeGenerator();
131     config = &Config::instance();
132     obsoleteLinks = config->getBool(CONFIG_OBSOLETELINKS);
133     setImageFileExtensions(QStringList() << "png"
134                                          << "jpg"
135                                          << "jpeg"
136                                          << "gif");
137 
138     /*
139       The formatting maps are owned by Generator. They are cleared in
140       Generator::terminate().
141      */
142     int i = 0;
143     while (defaults[i].key) {
144         formattingLeftMap().insert(QLatin1String(defaults[i].key), QLatin1String(defaults[i].left));
145         formattingRightMap().insert(QLatin1String(defaults[i].key),
146                                     QLatin1String(defaults[i].right));
147         i++;
148     }
149 
150     style = config->getString(HtmlGenerator::format() + Config::dot + CONFIG_STYLE);
151     endHeader = config->getString(HtmlGenerator::format() + Config::dot + CONFIG_ENDHEADER);
152     postHeader =
153             config->getString(HtmlGenerator::format() + Config::dot + HTMLGENERATOR_POSTHEADER);
154     postPostHeader =
155             config->getString(HtmlGenerator::format() + Config::dot + HTMLGENERATOR_POSTPOSTHEADER);
156     prologue = config->getString(HtmlGenerator::format() + Config::dot + HTMLGENERATOR_PROLOGUE);
157 
158     footer = config->getString(HtmlGenerator::format() + Config::dot + HTMLGENERATOR_FOOTER);
159     address = config->getString(HtmlGenerator::format() + Config::dot + HTMLGENERATOR_ADDRESS);
160     pleaseGenerateMacRef =
161             config->getBool(HtmlGenerator::format() + Config::dot + HTMLGENERATOR_GENERATEMACREFS);
162     noNavigationBar =
163             config->getBool(HtmlGenerator::format() + Config::dot + HTMLGENERATOR_NONAVIGATIONBAR);
164     navigationSeparator = config->getString(HtmlGenerator::format() + Config::dot
165                                             + HTMLGENERATOR_NAVIGATIONSEPARATOR);
166     tocDepth = config->getInt(HtmlGenerator::format() + Config::dot + HTMLGENERATOR_TOCDEPTH);
167 
168     project = config->getString(CONFIG_PROJECT);
169 
170     projectDescription = config->getString(CONFIG_DESCRIPTION);
171     if (projectDescription.isEmpty() && !project.isEmpty())
172         projectDescription = project + QLatin1String(" Reference Documentation");
173 
174     projectUrl = config->getString(CONFIG_URL);
175     tagFile_ = config->getString(CONFIG_TAGFILE);
176 
177 #ifndef QT_NO_TEXTCODEC
178     outputEncoding = config->getString(CONFIG_OUTPUTENCODING);
179     if (outputEncoding.isEmpty())
180         outputEncoding = QLatin1String("UTF-8");
181     outputCodec = QTextCodec::codecForName(outputEncoding.toLocal8Bit());
182 #endif
183 
184     naturalLanguage = config->getString(CONFIG_NATURALLANGUAGE);
185     if (naturalLanguage.isEmpty())
186         naturalLanguage = QLatin1String("en");
187 
188     codeIndent = config->getInt(CONFIG_CODEINDENT); // QTBUG-27798
189     codePrefix = config->getString(CONFIG_CODEPREFIX);
190     codeSuffix = config->getString(CONFIG_CODESUFFIX);
191 
192     /*
193       The help file write should be allocated once and only once
194       per qdoc execution.
195      */
196     if (helpProjectWriter)
197         helpProjectWriter->reset(project.toLower() + ".qhp", this);
198     else
199         helpProjectWriter = new HelpProjectWriter(project.toLower() + ".qhp", this);
200 
201     // Documentation template handling
202     headerScripts = config->getString(HtmlGenerator::format() + Config::dot + CONFIG_HEADERSCRIPTS);
203     headerStyles = config->getString(HtmlGenerator::format() + Config::dot + CONFIG_HEADERSTYLES);
204 
205     QString prefix = CONFIG_QHP + Config::dot + project + Config::dot;
206     manifestDir =
207             QLatin1String("qthelp://") + config->getString(prefix + QLatin1String("namespace"));
208     manifestDir += QLatin1Char('/') + config->getString(prefix + QLatin1String("virtualFolder"))
209             + QLatin1Char('/');
210     readManifestMetaContent();
211     examplesPath = config->getString(CONFIG_EXAMPLESINSTALLPATH);
212     if (!examplesPath.isEmpty())
213         examplesPath += QLatin1Char('/');
214 
215     // Retrieve the config for the navigation bar
216     homepage = config->getString(CONFIG_NAVIGATION + Config::dot + CONFIG_HOMEPAGE);
217 
218     hometitle = config->getString(CONFIG_NAVIGATION + Config::dot + CONFIG_HOMETITLE, homepage);
219 
220     landingpage = config->getString(CONFIG_NAVIGATION + Config::dot + CONFIG_LANDINGPAGE);
221 
222     landingtitle =
223             config->getString(CONFIG_NAVIGATION + Config::dot + CONFIG_LANDINGTITLE, landingpage);
224 
225     cppclassespage = config->getString(CONFIG_NAVIGATION + Config::dot + CONFIG_CPPCLASSESPAGE);
226 
227     cppclassestitle = config->getString(CONFIG_NAVIGATION + Config::dot + CONFIG_CPPCLASSESTITLE,
228                                         QLatin1String("C++ Classes"));
229 
230     qmltypespage = config->getString(CONFIG_NAVIGATION + Config::dot + CONFIG_QMLTYPESPAGE);
231 
232     qmltypestitle = config->getString(CONFIG_NAVIGATION + Config::dot + CONFIG_QMLTYPESTITLE,
233                                       QLatin1String("QML Types"));
234 
235     buildversion = config->getString(CONFIG_BUILDVERSION);
236 }
237 
238 /*!
239   Gracefully terminates the HTML output generator.
240  */
terminateGenerator()241 void HtmlGenerator::terminateGenerator()
242 {
243     Generator::terminateGenerator();
244 }
245 
format()246 QString HtmlGenerator::format()
247 {
248     return "HTML";
249 }
250 
251 /*!
252   Generate targets for any \keyword commands that were seen
253   in the qdoc comment for the \a node.
254  */
generateKeywordAnchors(const Node * node)255 void HtmlGenerator::generateKeywordAnchors(const Node *node)
256 {
257     Q_UNUSED(node);
258     // Disabled: keywords always link to the top of the QDoc
259     // comment they appear in, and do not use a dedicated anchor.
260 }
261 
262 /*!
263   If qdoc is in the \c {-prepare} phase, traverse the primary
264   tree to generate the index file for the current module.
265 
266   If qdoc is in the \c {-generate} phase, traverse the primary
267   tree to generate all the HTML documentation for the current
268   module. Then generate the help file and the tag file.
269  */
generateDocs()270 void HtmlGenerator::generateDocs()
271 {
272     Node *qflags = qdb_->findClassNode(QStringList("QFlags"));
273     if (qflags)
274         qflagsHref_ = linkForNode(qflags, nullptr);
275     if (!config->preparing())
276         Generator::generateDocs();
277     if (config->generating() && config->getBool(CONFIG_WRITEQAPAGES))
278         generateQAPage();
279 
280     if (!config->generating()) {
281         QString fileBase =
282                 project.toLower().simplified().replace(QLatin1Char(' '), QLatin1Char('-'));
283         qdb_->generateIndex(outputDir() + QLatin1Char('/') + fileBase + ".index", projectUrl,
284                             projectDescription, this);
285     }
286 
287     if (!config->preparing()) {
288         helpProjectWriter->generate();
289         generateManifestFiles();
290         /*
291           Generate the XML tag file, if it was requested.
292         */
293         qdb_->generateTagFile(tagFile_, this);
294     }
295 }
296 
297 /*!
298   Output the module's Quality Assurance page.
299  */
generateQAPage()300 void HtmlGenerator::generateQAPage()
301 {
302     NamespaceNode *node = qdb_->primaryTreeRoot();
303     beginSubPage(node, "aaa-" + defaultModuleName().toLower() + "-qa-page.html");
304     CodeMarker *marker = CodeMarker::markerForFileName(node->location().filePath());
305     QString title = "Quality Assurance Page for " + defaultModuleName();
306     QString t = "Quality assurance information for checking the " + defaultModuleName()
307             + " documentation.";
308     generateHeader(title, node, marker);
309     generateTitle(title, Text() << t, LargeSubTitle, node, marker);
310 
311     QStringList strings;
312     QVector<int> counts;
313     QString depends = qdb_->getLinkCounts(strings, counts);
314     if (!strings.isEmpty()) {
315         t = "Intermodule Link Counts";
316         QString ref = registerRef(t);
317         out() << "<a name=\"" << ref << "\"></a>" << divNavTop << '\n';
318         out() << "<h2 id=\"" << ref << "\">" << protectEnc(t) << "</h2>\n";
319         out() << "<table class=\"valuelist\"><tr valign=\"top\" "
320               << "class=\"even\"><th class=\"tblConst\">Destination Module</th>"
321               << "<th class=\"tblval\">Link Count</th></tr>\n";
322         QString fileName;
323         for (int i = 0; i < strings.size(); ++i) {
324             fileName = generateLinksToLinksPage(strings.at(i), marker);
325             out() << "<tr><td class=\"topAlign\"><tt>"
326                   << "<a href=\"" << fileName << "\">" << strings.at(i) << "</a>"
327                   << "</tt></td><td class=\"topAlign\"><tt>" << counts.at(i) << "</tt></td></tr>\n";
328         }
329         int count = 0;
330         fileName = generateLinksToBrokenLinksPage(marker, count);
331         if (count != 0) {
332             out() << "<tr><td class=\"topAlign\"><tt>"
333                   << "<a href=\"" << fileName << "\">"
334                   << "Broken Links"
335                   << "</a>"
336                   << "</tt></td><td class=\"topAlign\"><tt>" << count << "</tt></td></tr>\n";
337         }
338 
339         out() << "</table>\n";
340         t = "The Optimal \"depends\" Variable";
341         out() << "<h2>" << protectEnc(t) << "</h2>\n";
342         t = "Consider replacing the depends variable in " + defaultModuleName().toLower()
343                 + ".qdocconf with this one, if the two are not identical:";
344         out() << "<p>" << protectEnc(t) << "</p>\n";
345         out() << "<p>" << protectEnc(depends) << "</p>\n";
346     }
347     generateFooter();
348     endSubPage();
349 }
350 
351 /*!
352   Generate an html file with the contents of a C++ or QML source file.
353  */
generateExampleFilePage(const Node * en,const QString & file,CodeMarker * marker)354 void HtmlGenerator::generateExampleFilePage(const Node *en, const QString &file, CodeMarker *marker)
355 {
356     SubTitleSize subTitleSize = LargeSubTitle;
357     QString fullTitle = en->fullTitle();
358 
359     beginFilePage(en, linkForExampleFile(file, en));
360     generateHeader(fullTitle, en, marker);
361     generateTitle(fullTitle, Text() << en->subtitle(), subTitleSize, en, marker);
362 
363     Text text;
364     Quoter quoter;
365     Doc::quoteFromFile(en->doc().location(), quoter, file);
366     QString code = quoter.quoteTo(en->location(), QString(), QString());
367     CodeMarker *codeMarker = CodeMarker::markerForFileName(file);
368     text << Atom(codeMarker->atomType(), code);
369     Atom a(codeMarker->atomType(), code);
370 
371     generateText(text, en, codeMarker);
372     endFilePage();
373 }
374 
375 /*!
376   This function writes an html file containing a list of
377   links to links that originate in the current module and
378   go to targets in the specified \a module. The \a marker
379   is used for the same thing the marker is always used for.
380  */
generateLinksToLinksPage(const QString & module,CodeMarker * marker)381 QString HtmlGenerator::generateLinksToLinksPage(const QString &module, CodeMarker *marker)
382 {
383     NamespaceNode *node = qdb_->primaryTreeRoot();
384     QString fileName = "aaa-links-to-" + module + ".html";
385     beginSubPage(node, fileName);
386     QString title = "Links from " + defaultModuleName() + " to " + module;
387     generateHeader(title, node, marker);
388     generateTitle(title, Text(), SmallSubTitle, node, marker);
389     out() << "<p>This is a list of links from " << defaultModuleName() << " to " << module << ".  ";
390     out() << "Click on a link to go to the location of the link. The link is marked ";
391     out() << "with red asterisks. ";
392     out() << "Click on the marked link to see if it goes to the right place.</p>\n";
393     const TargetList *tlist = qdb_->getTargetList(module);
394     if (tlist) {
395         out() << "<table class=\"valuelist\"><tr valign=\"top\" class=\"odd\"><th "
396                  "class=\"tblConst\">Link to  link...</th><th class=\"tblval\">In file...</th><th "
397                  "class=\"tbldscr\">Somewhere after line number...</th></tr>\n";
398         for (const TargetLoc *t : *tlist) {
399             // e.g.: <a name="link-8421"></a><a href="layout.html">Layout Management</a>
400             out() << "<tr><td class=\"topAlign\">";
401             out() << "<a href=\"" << t->fileName_ << "#" << t->target_ << "\">";
402             out() << t->text_ << "</a></td>";
403             out() << "<td class=\"topAlign\">";
404             QString f = t->loc_->doc().location().filePath();
405             out() << f << "</td>";
406             out() << "<td class=\"topAlign\">";
407             out() << t->loc_->doc().location().lineNo() << "</td></tr>\n";
408         }
409         out() << "</table>\n";
410     }
411     generateFooter();
412     endSubPage();
413     return fileName;
414 }
415 
416 /*!
417   This function writes an html file containing a list of
418   links to broken links that originate in the current
419   module and go nowwhere. It returns the name of the file
420   it generates, and it sets \a count to the number of
421   broken links that were found. The \a marker is used for
422   the same thing the marker is always used for.
423  */
generateLinksToBrokenLinksPage(CodeMarker * marker,int & count)424 QString HtmlGenerator::generateLinksToBrokenLinksPage(CodeMarker *marker, int &count)
425 {
426     QString fileName;
427     NamespaceNode *node = qdb_->primaryTreeRoot();
428     const TargetList *tlist = qdb_->getTargetList("broken");
429     if (tlist && !tlist->isEmpty()) {
430         count = tlist->size();
431         fileName = "aaa-links-to-broken-links.html";
432         beginSubPage(node, fileName);
433         QString title = "Broken links in " + defaultModuleName();
434         generateHeader(title, node, marker);
435         generateTitle(title, Text(), SmallSubTitle, node, marker);
436         out() << "<p>This is a list of broken links in " << defaultModuleName() << ".  ";
437         out() << "Click on a link to go to the broken link.  ";
438         out() << "The link's target could not be found.</p>\n";
439         out() << "<table class=\"valuelist\"><tr valign=\"top\" class=\"odd\"><th "
440                  "class=\"tblConst\">Link to broken link...</th><th class=\"tblval\">In "
441                  "file...</th><th class=\"tbldscr\">Somewhere after line number...</th></tr>\n";
442         for (const TargetLoc *t : *tlist) {
443             // e.g.: <a name="link-8421"></a><a href="layout.html">Layout Management</a>
444             out() << "<tr><td class=\"topAlign\">";
445             out() << "<a href=\"" << t->fileName_ << "#" << t->target_ << "\">";
446             out() << t->text_ << "</a></td>";
447             out() << "<td class=\"topAlign\">";
448             QString f = t->loc_->doc().location().filePath();
449             out() << f << "</td>";
450             out() << "<td class=\"topAlign\">";
451             out() << t->loc_->doc().location().lineNo() << "</td></tr>\n";
452         }
453         out() << "</table>\n";
454         generateFooter();
455         endSubPage();
456     }
457     return fileName;
458 }
459 
460 /*!
461   Generate html from an instance of Atom.
462  */
generateAtom(const Atom * atom,const Node * relative,CodeMarker * marker)463 int HtmlGenerator::generateAtom(const Atom *atom, const Node *relative, CodeMarker *marker)
464 {
465     int idx, skipAhead = 0;
466     static bool in_para = false;
467 
468     switch (atom->type()) {
469     case Atom::AutoLink: {
470         QString name = atom->string();
471         if (relative && relative->name() == name.replace(QLatin1String("()"), QLatin1String())) {
472             out() << protectEnc(atom->string());
473             break;
474         }
475     }
476         Q_FALLTHROUGH();
477     case Atom::NavAutoLink:
478         if (!inLink_ && !inContents_ && !inSectionHeading_) {
479             const Node *node = nullptr;
480             QString link = getAutoLink(atom, relative, &node);
481             if (link.isEmpty()) {
482                 if (autolinkErrors())
483                     relative->doc().location().warning(
484                             tr("Can't autolink to '%1'").arg(atom->string()));
485             } else if (node && node->isObsolete()) {
486                 if ((relative->parent() != node) && !relative->isObsolete())
487                     link.clear();
488             }
489             if (link.isEmpty()) {
490                 out() << protectEnc(atom->string());
491             } else {
492                 if (config->getBool(CONFIG_WRITEQAPAGES)
493                         && node && (atom->type() != Atom::NavAutoLink)) {
494                     QString text = atom->string();
495                     QString target = qdb_->getNewLinkTarget(relative, node, outFileName(), text);
496                     out() << "<a id=\"" << Doc::canonicalTitle(target)
497                           << "\" class=\"qa-mark\"></a>";
498                 }
499                 beginLink(link, node, relative);
500                 generateLink(atom, marker);
501                 endLink();
502             }
503         } else {
504             out() << protectEnc(atom->string());
505         }
506         break;
507     case Atom::BaseName:
508         break;
509     case Atom::BriefLeft:
510         if (!hasBrief(relative)) {
511             skipAhead = skipAtoms(atom, Atom::BriefRight);
512             break;
513         }
514         out() << "<p>";
515         rewritePropertyBrief(atom, relative);
516         break;
517     case Atom::BriefRight:
518         if (hasBrief(relative))
519             out() << "</p>\n";
520         break;
521     case Atom::C:
522         // This may at one time have been used to mark up C++ code but it is
523         // now widely used to write teletype text. As a result, text marked
524         // with the \c command is not passed to a code marker.
525         out() << formattingLeftMap()[ATOM_FORMATTING_TELETYPE];
526         out() << protectEnc(plainCode(atom->string()));
527         out() << formattingRightMap()[ATOM_FORMATTING_TELETYPE];
528         break;
529     case Atom::CaptionLeft:
530         out() << "<p class=\"figCaption\">";
531         in_para = true;
532         break;
533     case Atom::CaptionRight:
534         endLink();
535         if (in_para) {
536             out() << "</p>\n";
537             in_para = false;
538         }
539         break;
540     case Atom::Qml:
541         out() << "<pre class=\"qml\">"
542               << trimmedTrailing(highlightedCode(indent(codeIndent, atom->string()), relative,
543                                                  false, Node::QML),
544                                  codePrefix, codeSuffix)
545               << "</pre>\n";
546         break;
547     case Atom::JavaScript:
548         out() << "<pre class=\"js\">"
549               << trimmedTrailing(highlightedCode(indent(codeIndent, atom->string()), relative,
550                                                  false, Node::JS),
551                                  codePrefix, codeSuffix)
552               << "</pre>\n";
553         break;
554     case Atom::CodeNew:
555         out() << "<p>you can rewrite it as</p>\n";
556         Q_FALLTHROUGH();
557     case Atom::Code:
558         out() << "<pre class=\"cpp\">"
559               << trimmedTrailing(highlightedCode(indent(codeIndent, atom->string()), relative),
560                                  codePrefix, codeSuffix)
561               << "</pre>\n";
562         break;
563     case Atom::CodeOld:
564         out() << "<p>For example, if you have code like</p>\n";
565         Q_FALLTHROUGH();
566     case Atom::CodeBad:
567         out() << "<pre class=\"cpp plain\">"
568               << trimmedTrailing(protectEnc(plainCode(indent(codeIndent, atom->string()))),
569                                  codePrefix, codeSuffix)
570               << "</pre>\n";
571         break;
572     case Atom::DivLeft:
573         out() << "<div";
574         if (!atom->string().isEmpty())
575             out() << ' ' << atom->string();
576         out() << '>';
577         break;
578     case Atom::DivRight:
579         out() << "</div>";
580         break;
581     case Atom::FootnoteLeft:
582         // ### For now
583         if (in_para) {
584             out() << "</p>\n";
585             in_para = false;
586         }
587         out() << "<!-- ";
588         break;
589     case Atom::FootnoteRight:
590         // ### For now
591         out() << "-->";
592         break;
593     case Atom::FormatElse:
594     case Atom::FormatEndif:
595     case Atom::FormatIf:
596         break;
597     case Atom::FormattingLeft:
598         if (atom->string().startsWith("span ")) {
599             out() << '<' + atom->string() << '>';
600         } else
601             out() << formattingLeftMap()[atom->string()];
602         if (atom->string() == ATOM_FORMATTING_PARAMETER) {
603             if (atom->next() != nullptr && atom->next()->type() == Atom::String) {
604                 QRegExp subscriptRegExp("([a-z]+)_([0-9n])");
605                 if (subscriptRegExp.exactMatch(atom->next()->string())) {
606                     out() << subscriptRegExp.cap(1) << "<sub>" << subscriptRegExp.cap(2)
607                           << "</sub>";
608                     skipAhead = 1;
609                 }
610             }
611         }
612         break;
613     case Atom::FormattingRight:
614         if (atom->string() == ATOM_FORMATTING_LINK) {
615             endLink();
616         } else if (atom->string().startsWith("span ")) {
617             out() << "</span>";
618         } else {
619             out() << formattingRightMap()[atom->string()];
620         }
621         break;
622     case Atom::AnnotatedList: {
623         const CollectionNode *cn = qdb_->getCollectionNode(atom->string(), Node::Group);
624         if (cn)
625             generateList(cn, marker, atom->string());
626     } break;
627     case Atom::GeneratedList:
628         if (atom->string() == QLatin1String("annotatedclasses")) {
629             generateAnnotatedList(relative, marker, qdb_->getCppClasses());
630         } else if (atom->string() == QLatin1String("annotatedexamples")) {
631             generateAnnotatedLists(relative, marker, qdb_->getExamples());
632         } else if (atom->string() == QLatin1String("annotatedattributions")) {
633             generateAnnotatedLists(relative, marker, qdb_->getAttributions());
634         } else if (atom->string() == QLatin1String("classes")) {
635             generateCompactList(Generic, relative, qdb_->getCppClasses(), true, QStringLiteral(""));
636         } else if (atom->string().contains("classes ")) {
637             QString rootName = atom->string().mid(atom->string().indexOf("classes") + 7).trimmed();
638             generateCompactList(Generic, relative, qdb_->getCppClasses(), true, rootName);
639         } else if (atom->string() == QLatin1String("qmlbasictypes")) {
640             generateCompactList(Generic, relative, qdb_->getQmlBasicTypes(), true,
641                                 QStringLiteral(""));
642         } else if (atom->string() == QLatin1String("qmltypes")) {
643             generateCompactList(Generic, relative, qdb_->getQmlTypes(), true, QStringLiteral(""));
644         } else if ((idx = atom->string().indexOf(QStringLiteral("bymodule"))) != -1) {
645             QString moduleName = atom->string().mid(idx + 8).trimmed();
646             Node::NodeType type = typeFromString(atom);
647             QDocDatabase *qdb = QDocDatabase::qdocDB();
648             const CollectionNode *cn = qdb->getCollectionNode(moduleName, type);
649             if (cn) {
650                 if (type == Node::Module) {
651                     NodeMap m;
652                     cn->getMemberClasses(m);
653                     if (!m.isEmpty()) {
654                         generateAnnotatedList(relative, marker, m);
655                     }
656                 } else
657                     generateAnnotatedList(relative, marker, cn->members());
658             }
659         } else if (atom->string().startsWith("examplefiles")
660                    || atom->string().startsWith("exampleimages")) {
661             if (relative->isExample()) {
662                 qDebug() << "GENERATE FILE LIST CALLED" << relative->name() << atom->string();
663             } else
664                 relative->location().warning(QString("'\\generatelist \1' can only be used with "
665                                                      "'\\example' topic command")
666                                                      .arg(atom->string()));
667         } else if (atom->string() == QLatin1String("classhierarchy")) {
668             generateClassHierarchy(relative, qdb_->getCppClasses());
669         } else if (atom->string() == QLatin1String("obsoleteclasses")) {
670             generateCompactList(Generic, relative, qdb_->getObsoleteClasses(), false,
671                                 QStringLiteral("Q"));
672         } else if (atom->string() == QLatin1String("obsoleteqmltypes")) {
673             generateCompactList(Generic, relative, qdb_->getObsoleteQmlTypes(), false,
674                                 QStringLiteral(""));
675         } else if (atom->string() == QLatin1String("obsoletecppmembers")) {
676             generateCompactList(Obsolete, relative, qdb_->getClassesWithObsoleteMembers(), false,
677                                 QStringLiteral("Q"));
678         } else if (atom->string() == QLatin1String("obsoleteqmlmembers")) {
679             generateCompactList(Obsolete, relative, qdb_->getQmlTypesWithObsoleteMembers(), false,
680                                 QStringLiteral(""));
681         } else if (atom->string() == QLatin1String("functionindex")) {
682             generateFunctionIndex(relative);
683         } else if (atom->string() == QLatin1String("attributions")) {
684             generateAnnotatedList(relative, marker, qdb_->getAttributions());
685         } else if (atom->string() == QLatin1String("legalese")) {
686             generateLegaleseList(relative, marker);
687         } else if (atom->string() == QLatin1String("overviews")) {
688             generateList(relative, marker, "overviews");
689         } else if (atom->string() == QLatin1String("cpp-modules")) {
690             generateList(relative, marker, "cpp-modules");
691         } else if (atom->string() == QLatin1String("qml-modules")) {
692             generateList(relative, marker, "qml-modules");
693         } else if (atom->string() == QLatin1String("namespaces")) {
694             generateAnnotatedList(relative, marker, qdb_->getNamespaces());
695         } else if (atom->string() == QLatin1String("related")) {
696             generateList(relative, marker, "related");
697         } else {
698             const CollectionNode *cn = qdb_->getCollectionNode(atom->string(), Node::Group);
699             if (cn) {
700                 if (!generateGroupList(const_cast<CollectionNode *>(cn)))
701                     relative->location().warning(
702                             QString("'\\generatelist \1' group is empty").arg(atom->string()));
703             } else {
704                 relative->location().warning(
705                         QString("'\\generatelist \1' no such group").arg(atom->string()));
706             }
707         }
708         break;
709     case Atom::SinceList: {
710         const NodeMultiMap &nsmap = qdb_->getSinceMap(atom->string());
711         if (nsmap.isEmpty())
712             break;
713 
714         const NodeMap &ncmap = qdb_->getClassMap(atom->string());
715         const NodeMap &nqcmap = qdb_->getQmlTypeMap(atom->string());
716 
717         Sections sections(nsmap);
718         out() << "<ul>\n";
719         const QVector<Section> sinceSections = sections.sinceSections();
720         for (const auto &section : sinceSections) {
721             if (!section.members().isEmpty()) {
722                 out() << "<li>"
723                       << "<a href=\"#" << Doc::canonicalTitle(section.title()) << "\">"
724                       << section.title() << "</a></li>\n";
725             }
726         }
727         out() << "</ul>\n";
728 
729         int idx = 0;
730         for (const auto &section : sinceSections) {
731             if (!section.members().isEmpty()) {
732                 out() << "<a name=\"" << Doc::canonicalTitle(section.title()) << "\"></a>\n";
733                 out() << "<h3>" << protectEnc(section.title()) << "</h3>\n";
734                 if (idx == Sections::SinceClasses)
735                     generateCompactList(Generic, relative, ncmap, false, QStringLiteral("Q"));
736                 else if (idx == Sections::SinceQmlTypes)
737                     generateCompactList(Generic, relative, nqcmap, false, QStringLiteral(""));
738                 else if (idx == Sections::SinceMemberFunctions) {
739                     ParentMaps parentmaps;
740                     ParentMaps::iterator pmap;
741                     const QVector<Node *> members = section.members();
742                     for (const auto &member : members) {
743                         Node *parent = (*member).parent();
744                         pmap = parentmaps.find(parent);
745                         if (pmap == parentmaps.end())
746                             pmap = parentmaps.insert(parent, NodeMultiMap());
747                         pmap->insert(member->name(), member);
748                     }
749                     for (auto map = parentmaps.begin(); map != parentmaps.end(); ++map) {
750                         NodeVector nv = map->values().toVector();
751                         out() << "<p>Class ";
752 
753                         out() << "<a href=\"" << linkForNode(map.key(), relative) << "\">";
754                         QStringList pieces = map.key()->fullName().split("::");
755                         out() << protectEnc(pieces.last());
756                         out() << "</a>"
757                               << ":</p>\n";
758 
759                         generateSection(nv, relative, marker);
760                         out() << "<br/>";
761                     }
762                 } else {
763                     generateSection(section.members(), relative, marker);
764                 }
765             }
766             ++idx;
767         }
768     } break;
769     case Atom::BR:
770         out() << "<br />\n";
771         break;
772     case Atom::HR:
773         out() << "<hr />\n";
774         break;
775     case Atom::Image:
776     case Atom::InlineImage: {
777         QString fileName = imageFileName(relative, atom->string());
778         QString text;
779         if (atom->next() != nullptr)
780             text = atom->next()->string();
781         if (atom->type() == Atom::Image)
782             out() << "<p class=\"centerAlign\">";
783         if (fileName.isEmpty()) {
784             relative->location().warning(tr("Missing image: %1").arg(protectEnc(atom->string())));
785             out() << "<font color=\"red\">[Missing image " << protectEnc(atom->string())
786                   << "]</font>";
787         } else {
788             QString prefix;
789             out() << "<img src=\"" << protectEnc(prefix + fileName) << '"';
790             if (!text.isEmpty())
791                 out() << " alt=\"" << protectEnc(text) << '"';
792             else
793                 out() << " alt=\"\"";
794             out() << " />";
795             helpProjectWriter->addExtraFile(fileName);
796             setImageFileName(relative, fileName);
797         }
798         if (atom->type() == Atom::Image)
799             out() << "</p>";
800     } break;
801     case Atom::ImageText:
802         break;
803     case Atom::ImportantLeft:
804         out() << "<p>";
805         out() << formattingLeftMap()[ATOM_FORMATTING_BOLD];
806         out() << "Important: ";
807         out() << formattingRightMap()[ATOM_FORMATTING_BOLD];
808         break;
809     case Atom::ImportantRight:
810         out() << "</p>";
811         break;
812     case Atom::NoteLeft:
813         out() << "<p>";
814         out() << formattingLeftMap()[ATOM_FORMATTING_BOLD];
815         out() << "Note: ";
816         out() << formattingRightMap()[ATOM_FORMATTING_BOLD];
817         break;
818     case Atom::NoteRight:
819         out() << "</p>\n";
820         break;
821     case Atom::LegaleseLeft:
822         out() << "<div class=\"LegaleseLeft\">";
823         break;
824     case Atom::LegaleseRight:
825         out() << "</div>";
826         break;
827     case Atom::LineBreak:
828         out() << "<br/>";
829         break;
830     case Atom::Link:
831     case Atom::NavLink: {
832         inObsoleteLink = false;
833         const Node *node = nullptr;
834         QString link = getLink(atom, relative, &node);
835         if (link.isEmpty() && (node != relative) && !noLinkErrors()) {
836             relative->doc().location().warning(tr("Can't link to '%1'").arg(atom->string()));
837             if (config->getBool(CONFIG_WRITEQAPAGES) && (atom->type() != Atom::NavAutoLink)) {
838                 QString text = atom->next()->next()->string();
839                 QString target = qdb_->getNewLinkTarget(relative, node, outFileName(), text, true);
840                 out() << "<a id=\"" << Doc::canonicalTitle(target) << "\" class=\"qa-mark\"></a>";
841             }
842         } else {
843             if (config->getBool(CONFIG_WRITEQAPAGES) && node && (atom->type() != Atom::NavLink)) {
844                 QString text = atom->next()->next()->string();
845                 QString target = qdb_->getNewLinkTarget(relative, node, outFileName(), text);
846                 out() << "<a id=\"" << Doc::canonicalTitle(target) << "\" class=\"qa-mark\"></a>";
847             }
848             node = nullptr;
849         }
850         beginLink(link, node, relative);
851         skipAhead = 1;
852     } break;
853     case Atom::ExampleFileLink: {
854         QString link = linkForExampleFile(atom->string(), relative);
855         if (link.isEmpty() && !noLinkErrors())
856             relative->doc().location().warning(tr("Can't link to '%1'").arg(atom->string()));
857         beginLink(link);
858         skipAhead = 1;
859     } break;
860     case Atom::ExampleImageLink: {
861         QString link = atom->string();
862         if (link.isEmpty() && !noLinkErrors())
863             relative->doc().location().warning(tr("Can't link to '%1'").arg(atom->string()));
864         link = "images/used-in-examples/" + link;
865         beginLink(link);
866         skipAhead = 1;
867     } break;
868     case Atom::LinkNode: {
869         const Node *node = CodeMarker::nodeForString(atom->string());
870         beginLink(linkForNode(node, relative), node, relative);
871         skipAhead = 1;
872     } break;
873     case Atom::ListLeft:
874         if (in_para) {
875             out() << "</p>\n";
876             in_para = false;
877         }
878         if (atom->string() == ATOM_LIST_BULLET) {
879             out() << "<ul>\n";
880         } else if (atom->string() == ATOM_LIST_TAG) {
881             out() << "<dl>\n";
882         } else if (atom->string() == ATOM_LIST_VALUE) {
883             out() << "<div class=\"table\"><table class=\"valuelist\">";
884             threeColumnEnumValueTable_ = isThreeColumnEnumValueTable(atom);
885             if (threeColumnEnumValueTable_) {
886                 if (++numTableRows_ % 2 == 1)
887                     out() << "<tr valign=\"top\" class=\"odd\">";
888                 else
889                     out() << "<tr valign=\"top\" class=\"even\">";
890 
891                 out() << "<th class=\"tblConst\">Constant</th>";
892 
893                 // If not in \enum topic, skip the value column
894                 if (relative->isEnumType())
895                     out() << "<th class=\"tblval\">Value</th>";
896 
897                 out() << "<th class=\"tbldscr\">Description</th></tr>\n";
898             } else {
899                 out() << "<tr><th class=\"tblConst\">Constant</th><th "
900                          "class=\"tblVal\">Value</th></tr>\n";
901             }
902         } else {
903             QString olType;
904             if (atom->string() == ATOM_LIST_UPPERALPHA) {
905                 olType = "A";
906             } else if (atom->string() == ATOM_LIST_LOWERALPHA) {
907                 olType = "a";
908             } else if (atom->string() == ATOM_LIST_UPPERROMAN) {
909                 olType = "I";
910             } else if (atom->string() == ATOM_LIST_LOWERROMAN) {
911                 olType = "i";
912             } else { // (atom->string() == ATOM_LIST_NUMERIC)
913                 olType = "1";
914             }
915 
916             if (atom->next() != nullptr && atom->next()->string().toInt() > 1) {
917                 out() << QString("<ol class=\"%1\" type=\"%1\" start=\"%2\">")
918                                  .arg(olType)
919                                  .arg(atom->next()->string());
920             } else
921                 out() << QString("<ol class=\"%1\" type=\"%1\">").arg(olType);
922         }
923         break;
924     case Atom::ListItemNumber:
925         break;
926     case Atom::ListTagLeft:
927         if (atom->string() == ATOM_LIST_TAG) {
928             out() << "<dt>";
929         } else { // (atom->string() == ATOM_LIST_VALUE)
930             QPair<QString, int> pair = getAtomListValue(atom);
931             skipAhead = pair.second;
932             QString t = protectEnc(plainCode(marker->markedUpEnumValue(pair.first, relative)));
933             out() << "<tr><td class=\"topAlign\"><code>" << t << "</code>";
934 
935             if (relative->isEnumType()) {
936                 out() << "</td><td class=\"topAlign tblval\">";
937                 const EnumNode *enume = static_cast<const EnumNode *>(relative);
938                 QString itemValue = enume->itemValue(atom->next()->string());
939                 if (itemValue.isEmpty())
940                     out() << '?';
941                 else
942                     out() << "<code>" << protectEnc(itemValue) << "</code>";
943             }
944         }
945         break;
946     case Atom::SinceTagRight:
947     case Atom::ListTagRight:
948         if (atom->string() == ATOM_LIST_TAG)
949             out() << "</dt>\n";
950         break;
951     case Atom::ListItemLeft:
952         if (atom->string() == ATOM_LIST_TAG) {
953             out() << "<dd>";
954         } else if (atom->string() == ATOM_LIST_VALUE) {
955             if (threeColumnEnumValueTable_) {
956                 out() << "</td><td class=\"topAlign\">";
957                 if (matchAhead(atom, Atom::ListItemRight))
958                     out() << "&nbsp;";
959             }
960         } else {
961             out() << "<li>";
962         }
963         if (matchAhead(atom, Atom::ParaLeft))
964             skipAhead = 1;
965         break;
966     case Atom::ListItemRight:
967         if (atom->string() == ATOM_LIST_TAG) {
968             out() << "</dd>\n";
969         } else if (atom->string() == ATOM_LIST_VALUE) {
970             out() << "</td></tr>\n";
971         } else {
972             out() << "</li>\n";
973         }
974         break;
975     case Atom::ListRight:
976         if (atom->string() == ATOM_LIST_BULLET) {
977             out() << "</ul>\n";
978         } else if (atom->string() == ATOM_LIST_TAG) {
979             out() << "</dl>\n";
980         } else if (atom->string() == ATOM_LIST_VALUE) {
981             out() << "</table></div>\n";
982         } else {
983             out() << "</ol>\n";
984         }
985         break;
986     case Atom::Nop:
987         break;
988     case Atom::ParaLeft:
989         out() << "<p>";
990         in_para = true;
991         break;
992     case Atom::ParaRight:
993         endLink();
994         if (in_para) {
995             out() << "</p>\n";
996             in_para = false;
997         }
998         // if (!matchAhead(atom, Atom::ListItemRight) && !matchAhead(atom, Atom::TableItemRight))
999         //    out() << "</p>\n";
1000         break;
1001     case Atom::QuotationLeft:
1002         out() << "<blockquote>";
1003         break;
1004     case Atom::QuotationRight:
1005         out() << "</blockquote>\n";
1006         break;
1007     case Atom::RawString:
1008         out() << atom->string();
1009         break;
1010     case Atom::SectionLeft:
1011         out() << "<a name=\"" << Doc::canonicalTitle(Text::sectionHeading(atom).toString())
1012               << "\"></a>" << divNavTop << '\n';
1013         break;
1014     case Atom::SectionRight:
1015         break;
1016     case Atom::SectionHeadingLeft: {
1017         int unit = atom->string().toInt() + hOffset(relative);
1018         out() << "<h" + QString::number(unit) + QLatin1Char(' ') << "id=\""
1019               << Doc::canonicalTitle(Text::sectionHeading(atom).toString()) << "\">";
1020         inSectionHeading_ = true;
1021         break;
1022     }
1023     case Atom::SectionHeadingRight:
1024         out() << "</h" + QString::number(atom->string().toInt() + hOffset(relative)) + ">\n";
1025         inSectionHeading_ = false;
1026         break;
1027     case Atom::SidebarLeft:
1028         break;
1029     case Atom::SidebarRight:
1030         break;
1031     case Atom::String:
1032         if (inLink_ && !inContents_ && !inSectionHeading_) {
1033             generateLink(atom, marker);
1034         } else {
1035             out() << protectEnc(atom->string());
1036         }
1037         break;
1038     case Atom::TableLeft: {
1039         QPair<QString, QString> pair = getTableWidthAttr(atom);
1040         QString attr = pair.second;
1041         QString width = pair.first;
1042 
1043         if (in_para) {
1044             out() << "</p>\n";
1045             in_para = false;
1046         }
1047 
1048         out() << "<div class=\"table\"><table class=\"" << attr << '"';
1049         if (!width.isEmpty())
1050             out() << " width=\"" << width << '"';
1051         out() << ">\n ";
1052         numTableRows_ = 0;
1053     } break;
1054     case Atom::TableRight:
1055         out() << "</table></div>\n";
1056         break;
1057     case Atom::TableHeaderLeft:
1058         out() << "<thead><tr class=\"qt-style\">";
1059         inTableHeader_ = true;
1060         break;
1061     case Atom::TableHeaderRight:
1062         out() << "</tr>";
1063         if (matchAhead(atom, Atom::TableHeaderLeft)) {
1064             skipAhead = 1;
1065             out() << "\n<tr class=\"qt-style\">";
1066         } else {
1067             out() << "</thead>\n";
1068             inTableHeader_ = false;
1069         }
1070         break;
1071     case Atom::TableRowLeft:
1072         if (!atom->string().isEmpty())
1073             out() << "<tr " << atom->string() << '>';
1074         else if (++numTableRows_ % 2 == 1)
1075             out() << "<tr valign=\"top\" class=\"odd\">";
1076         else
1077             out() << "<tr valign=\"top\" class=\"even\">";
1078         break;
1079     case Atom::TableRowRight:
1080         out() << "</tr>\n";
1081         break;
1082     case Atom::TableItemLeft: {
1083         if (inTableHeader_)
1084             out() << "<th ";
1085         else
1086             out() << "<td ";
1087 
1088         for (int i = 0; i < atom->count(); ++i) {
1089             if (i > 0)
1090                 out() << ' ';
1091             QString p = atom->string(i);
1092             if (p.contains('=')) {
1093                 out() << p;
1094             } else {
1095                 QStringList spans = p.split(QLatin1Char(','));
1096                 if (spans.size() == 2) {
1097                     if (spans.at(0) != "1")
1098                         out() << " colspan=\"" << spans.at(0) << '"';
1099                     if (spans.at(1) != "1")
1100                         out() << " rowspan=\"" << spans.at(1) << '"';
1101                 }
1102             }
1103         }
1104         out() << '>';
1105         if (matchAhead(atom, Atom::ParaLeft))
1106             skipAhead = 1;
1107     } break;
1108     case Atom::TableItemRight:
1109         if (inTableHeader_)
1110             out() << "</th>";
1111         else {
1112             out() << "</td>";
1113         }
1114         if (matchAhead(atom, Atom::ParaLeft))
1115             skipAhead = 1;
1116         break;
1117     case Atom::TableOfContents:
1118         break;
1119     case Atom::Keyword:
1120         break;
1121     case Atom::Target:
1122         out() << "<a name=\"" << Doc::canonicalTitle(atom->string()) << "\"></a>";
1123         break;
1124     case Atom::UnhandledFormat:
1125         out() << "<b class=\"redFont\">&lt;Missing HTML&gt;</b>";
1126         break;
1127     case Atom::UnknownCommand:
1128         out() << "<b class=\"redFont\"><code>\\" << protectEnc(atom->string()) << "</code></b>";
1129         break;
1130     case Atom::QmlText:
1131     case Atom::EndQmlText:
1132         // don't do anything with these. They are just tags.
1133         break;
1134     case Atom::CodeQuoteArgument:
1135     case Atom::CodeQuoteCommand:
1136     case Atom::SnippetCommand:
1137     case Atom::SnippetIdentifier:
1138     case Atom::SnippetLocation:
1139         // no HTML output (ignore)
1140         break;
1141     default:
1142         unknownAtom(atom);
1143     }
1144     return skipAhead;
1145 }
1146 
1147 /*!
1148   Generate a reference page for the C++ class, namespace, or
1149   header file documented in \a node using the code \a marker
1150   provided.
1151  */
generateCppReferencePage(Aggregate * aggregate,CodeMarker * marker)1152 void HtmlGenerator::generateCppReferencePage(Aggregate *aggregate, CodeMarker *marker)
1153 {
1154     QString title;
1155     QString rawTitle;
1156     QString fullTitle;
1157     NamespaceNode *ns = nullptr;
1158     SectionVector *summarySections = nullptr;
1159     SectionVector *detailsSections = nullptr;
1160 
1161     Sections sections(aggregate);
1162     QString word = aggregate->typeWord(true);
1163     QString templateDecl = aggregate->templateDecl();
1164     if (aggregate->isNamespace()) {
1165         rawTitle = aggregate->plainName();
1166         fullTitle = aggregate->plainFullName();
1167         title = rawTitle + " Namespace";
1168         ns = static_cast<NamespaceNode *>(aggregate);
1169         summarySections = &sections.stdSummarySections();
1170         detailsSections = &sections.stdDetailsSections();
1171     } else if (aggregate->isClassNode()) {
1172         rawTitle = aggregate->plainName();
1173         fullTitle = aggregate->plainFullName();
1174         title = rawTitle + QLatin1Char(' ') + word;
1175         summarySections = &sections.stdCppClassSummarySections();
1176         detailsSections = &sections.stdCppClassDetailsSections();
1177     } else if (aggregate->isHeader()) {
1178         title = fullTitle = rawTitle = aggregate->fullTitle();
1179         summarySections = &sections.stdSummarySections();
1180         detailsSections = &sections.stdDetailsSections();
1181     }
1182 
1183     Text subtitleText;
1184     if (rawTitle != fullTitle || !templateDecl.isEmpty()) {
1185         if (aggregate->isClassNode()) {
1186             if (!templateDecl.isEmpty())
1187                 subtitleText << templateDecl + QLatin1Char(' ');
1188             subtitleText << aggregate->typeWord(false) + QLatin1Char(' ');
1189             const QStringList ancestors = fullTitle.split(QLatin1String("::"));
1190             for (const auto &a : ancestors) {
1191                 if (a == rawTitle) {
1192                     subtitleText << a;
1193                     break;
1194                 } else {
1195                     subtitleText << Atom(Atom::AutoLink, a) << "::";
1196                 }
1197             }
1198         } else {
1199             subtitleText << fullTitle;
1200         }
1201     }
1202 
1203     generateHeader(title, aggregate, marker);
1204     generateTableOfContents(aggregate, marker, summarySections);
1205     generateKeywordAnchors(aggregate);
1206     generateTitle(title, subtitleText, SmallSubTitle, aggregate, marker);
1207     if (ns && !ns->hasDoc() && ns->docNode()) {
1208         NamespaceNode *NS = ns->docNode();
1209         Text brief;
1210         brief << "The " << ns->name() << " namespace includes the following elements from module "
1211               << ns->tree()->camelCaseModuleName() << ". The full namespace is "
1212               << "documented in module " << NS->tree()->camelCaseModuleName()
1213               << Atom(Atom::LinkNode, CodeMarker::stringForNode(NS))
1214               << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << Atom(Atom::String, " here.")
1215               << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1216         out() << "<p>";
1217         generateText(brief, ns, marker);
1218         out() << "</p>\n";
1219     } else
1220         generateBrief(aggregate, marker);
1221     if (!aggregate->parent()->isClassNode())
1222         generateRequisites(aggregate, marker);
1223     generateStatus(aggregate, marker);
1224     generateSince(aggregate, marker);
1225 
1226     out() << "<ul>\n";
1227 
1228     QString membersLink = generateAllMembersFile(Sections::allMembersSection(), marker);
1229     if (!membersLink.isEmpty())
1230         out() << "<li><a href=\"" << membersLink << "\">"
1231               << "List of all members, including inherited members</a></li>\n";
1232 
1233     QString obsoleteLink = generateObsoleteMembersFile(sections, marker);
1234     if (!obsoleteLink.isEmpty()) {
1235         out() << "<li><a href=\"" << obsoleteLink << "\">"
1236               << "Obsolete members</a></li>\n";
1237     }
1238 
1239     out() << "</ul>\n";
1240     generateThreadSafeness(aggregate, marker);
1241 
1242     bool needOtherSection = false;
1243 
1244     for (const auto &section : qAsConst(*summarySections)) {
1245         if (section.members().isEmpty() && section.reimplementedMembers().isEmpty()) {
1246             if (!section.inheritedMembers().isEmpty())
1247                 needOtherSection = true;
1248         } else {
1249             if (!section.members().isEmpty()) {
1250                 QString ref = registerRef(section.title().toLower());
1251                 out() << "<a name=\"" << ref << "\"></a>" << divNavTop << "\n";
1252                 out() << "<h2 id=\"" << ref << "\">" << protectEnc(section.title()) << "</h2>\n";
1253                 generateSection(section.members(), aggregate, marker);
1254             }
1255             if (!section.reimplementedMembers().isEmpty()) {
1256                 QString name = QString("Reimplemented ") + section.title();
1257                 QString ref = registerRef(name.toLower());
1258                 out() << "<a name=\"" << ref << "\"></a>" << divNavTop << "\n";
1259                 out() << "<h2 id=\"" << ref << "\">" << protectEnc(name) << "</h2>\n";
1260                 generateSection(section.reimplementedMembers(), aggregate, marker);
1261             }
1262 
1263             if (!section.inheritedMembers().isEmpty()) {
1264                 out() << "<ul>\n";
1265                 generateSectionInheritedList(section, aggregate);
1266                 out() << "</ul>\n";
1267             }
1268         }
1269     }
1270 
1271     if (needOtherSection) {
1272         out() << "<h3>Additional Inherited Members</h3>\n"
1273                  "<ul>\n";
1274 
1275         for (const auto &section : qAsConst(*summarySections)) {
1276             if (section.members().isEmpty() && !section.inheritedMembers().isEmpty())
1277                 generateSectionInheritedList(section, aggregate);
1278         }
1279         out() << "</ul>\n";
1280     }
1281 
1282     QString detailsRef = registerRef("details");
1283     out() << "<a name=\"" << detailsRef << "\"></a>" << divNavTop << '\n';
1284 
1285     if (aggregate->doc().isEmpty()) {
1286         QString command = "documentation";
1287         if (aggregate->isClassNode())
1288             command = "\'\\class\' comment";
1289         if (!ns || ns->isDocumentedHere()) {
1290             aggregate->location().warning(
1291                     tr("No %1 for '%2'")
1292                         .arg(command)
1293                         .arg(aggregate->plainSignature()));
1294         }
1295     } else {
1296         generateExtractionMark(aggregate, DetailedDescriptionMark);
1297         out() << "<div class=\"descr\">\n" // QTBUG-9504
1298               << "<h2 id=\"" << detailsRef << "\">"
1299               << "Detailed Description"
1300               << "</h2>\n";
1301         generateBody(aggregate, marker);
1302         out() << "</div>\n"; // QTBUG-9504
1303         generateAlsoList(aggregate, marker);
1304         generateMaintainerList(aggregate, marker);
1305         generateExtractionMark(aggregate, EndMark);
1306     }
1307 
1308     for (const auto &section : qAsConst(*detailsSections)) {
1309         bool headerGenerated = false;
1310         if (section.isEmpty())
1311             continue;
1312 
1313         const QVector<Node *> members = section.members();
1314         for (const auto &member : members) {
1315             if (member->access() == Node::Private) // ### check necessary?
1316                 continue;
1317             if (!headerGenerated) {
1318                 if (!section.divClass().isEmpty())
1319                     out() << "<div class=\"" << section.divClass() << "\">\n"; // QTBUG-9504
1320                 out() << "<h2>" << protectEnc(section.title()) << "</h2>\n";
1321                 headerGenerated = true;
1322             }
1323             if (!member->isClassNode())
1324                 generateDetailedMember(member, aggregate, marker);
1325             else {
1326                 out() << "<h3> class ";
1327                 generateFullName(member, aggregate);
1328                 out() << "</h3>";
1329                 generateBrief(member, marker, aggregate);
1330             }
1331 
1332             QStringList names;
1333             names << member->name();
1334             if (member->isFunction()) {
1335                 const FunctionNode *func = reinterpret_cast<const FunctionNode *>(member);
1336                 if (func->isSomeCtor() || func->isDtor() || func->overloadNumber() != 0)
1337                     names.clear();
1338             } else if (member->isProperty()) {
1339                 const PropertyNode *prop = reinterpret_cast<const PropertyNode *>(member);
1340                 if (!prop->getters().isEmpty() && !names.contains(prop->getters().first()->name()))
1341                     names << prop->getters().first()->name();
1342                 if (!prop->setters().isEmpty())
1343                     names << prop->setters().first()->name();
1344                 if (!prop->resetters().isEmpty())
1345                     names << prop->resetters().first()->name();
1346                 if (!prop->notifiers().isEmpty())
1347                     names << prop->notifiers().first()->name();
1348             } else if (member->isEnumType()) {
1349                 const EnumNode *enume = reinterpret_cast<const EnumNode *>(member);
1350                 if (enume->flagsType())
1351                     names << enume->flagsType()->name();
1352                 const auto &enumItemNameList = enume->doc().enumItemNames();
1353                 const auto &omitEnumItemNameList = enume->doc().omitEnumItemNames();
1354                 const auto items = QSet<QString>(enumItemNameList.cbegin(), enumItemNameList.cend())
1355                         - QSet<QString>(omitEnumItemNameList.cbegin(), omitEnumItemNameList.cend());
1356                 for (const QString &enumName : items) {
1357                     names << plainCode(marker->markedUpEnumValue(enumName, enume));
1358                 }
1359             }
1360         }
1361         if (headerGenerated && !section.divClass().isEmpty())
1362             out() << "</div>\n"; // QTBUG-9504
1363     }
1364     generateFooter(aggregate);
1365 }
1366 
generateProxyPage(Aggregate * aggregate,CodeMarker * marker)1367 void HtmlGenerator::generateProxyPage(Aggregate *aggregate, CodeMarker *marker)
1368 {
1369     Q_ASSERT(aggregate->isProxyNode());
1370 
1371     QString title;
1372     QString rawTitle;
1373     QString fullTitle;
1374     Text subtitleText;
1375     SectionVector *summarySections = nullptr;
1376     SectionVector *detailsSections = nullptr;
1377 
1378     Sections sections(aggregate);
1379     rawTitle = aggregate->plainName();
1380     fullTitle = aggregate->plainFullName();
1381     title = rawTitle + " Proxy Page";
1382     summarySections = &sections.stdSummarySections();
1383     detailsSections = &sections.stdDetailsSections();
1384     generateHeader(title, aggregate, marker);
1385     generateTitle(title, subtitleText, SmallSubTitle, aggregate, marker);
1386     generateBrief(aggregate, marker);
1387     for (auto it = summarySections->constBegin(); it != summarySections->constEnd(); ++it) {
1388         if (!it->members().isEmpty()) {
1389             QString ref = registerRef(it->title().toLower());
1390             out() << "<a name=\"" << ref << "\"></a>" << divNavTop << "\n";
1391             out() << "<h2 id=\"" << ref << "\">" << protectEnc(it->title()) << "</h2>\n";
1392             generateSection(it->members(), aggregate, marker);
1393         }
1394     }
1395 
1396     QString detailsRef = registerRef("details");
1397     out() << "<a name=\"" << detailsRef << "\"></a>" << divNavTop << '\n';
1398 
1399     if (!aggregate->doc().isEmpty()) {
1400         generateExtractionMark(aggregate, DetailedDescriptionMark);
1401         out() << "<div class=\"descr\">\n" // QTBUG-9504
1402               << "<h2 id=\"" << detailsRef << "\">"
1403               << "Detailed Description"
1404               << "</h2>\n";
1405         generateBody(aggregate, marker);
1406         out() << "</div>\n"; // QTBUG-9504
1407         generateAlsoList(aggregate, marker);
1408         generateMaintainerList(aggregate, marker);
1409         generateExtractionMark(aggregate, EndMark);
1410     }
1411 
1412     for (const auto &section : qAsConst(*detailsSections)) {
1413         if (section.isEmpty())
1414             continue;
1415 
1416         if (!section.divClass().isEmpty())
1417             out() << "<div class=\"" << section.divClass() << "\">\n"; // QTBUG-9504
1418         out() << "<h2>" << protectEnc(section.title()) << "</h2>\n";
1419 
1420         const QVector<Node *> &members = section.members();
1421         for (const auto &member : members) {
1422             if (!member->isPrivate()) { // ### check necessary?
1423                 if (!member->isClassNode())
1424                     generateDetailedMember(member, aggregate, marker);
1425                 else {
1426                     out() << "<h3> class ";
1427                     generateFullName(member, aggregate);
1428                     out() << "</h3>";
1429                     generateBrief(member, marker, aggregate);
1430                 }
1431 
1432                 QStringList names;
1433                 names << member->name();
1434                 if (member->isFunction()) {
1435                     const FunctionNode *func = reinterpret_cast<const FunctionNode *>(member);
1436                     if (func->isSomeCtor() || func->isDtor() || func->overloadNumber() != 0)
1437                         names.clear();
1438                 } else if (member->isEnumType()) {
1439                     const EnumNode *enume = reinterpret_cast<const EnumNode *>(member);
1440                     if (enume->flagsType())
1441                         names << enume->flagsType()->name();
1442                     const auto &enumItemNameList = enume->doc().enumItemNames();
1443                     const auto &omitEnumItemNameList = enume->doc().omitEnumItemNames();
1444                     const auto items =
1445                             QSet<QString>(enumItemNameList.cbegin(), enumItemNameList.cend())
1446                             - QSet<QString>(omitEnumItemNameList.cbegin(),
1447                                             omitEnumItemNameList.cend());
1448                     for (const QString &enumName : items)
1449                         names << plainCode(marker->markedUpEnumValue(enumName, enume));
1450                 }
1451             }
1452         }
1453         if (!section.divClass().isEmpty())
1454             out() << "</div>\n"; // QTBUG-9504
1455     }
1456     generateFooter(aggregate);
1457 }
1458 
1459 /*!
1460   Generate the HTML page for a QML type. \qcn is the QML type.
1461   \marker is the code markeup object.
1462  */
generateQmlTypePage(QmlTypeNode * qcn,CodeMarker * marker)1463 void HtmlGenerator::generateQmlTypePage(QmlTypeNode *qcn, CodeMarker *marker)
1464 {
1465     Generator::setQmlTypeContext(qcn);
1466     SubTitleSize subTitleSize = LargeSubTitle;
1467     QString htmlTitle = qcn->fullTitle();
1468     if (qcn->isJsType())
1469         htmlTitle += " JavaScript Type";
1470     else
1471         htmlTitle += " QML Type";
1472 
1473     generateHeader(htmlTitle, qcn, marker);
1474     Sections sections(qcn);
1475     generateTableOfContents(qcn, marker, &sections.stdQmlTypeSummarySections());
1476     marker = CodeMarker::markerForLanguage(QLatin1String("QML"));
1477     generateKeywordAnchors(qcn);
1478     generateTitle(htmlTitle, Text() << qcn->subtitle(), subTitleSize, qcn, marker);
1479     generateBrief(qcn, marker);
1480     generateQmlRequisites(qcn, marker);
1481 
1482     QString allQmlMembersLink = generateAllQmlMembersFile(sections, marker);
1483     QString obsoleteLink = generateObsoleteQmlMembersFile(sections, marker);
1484     if (!allQmlMembersLink.isEmpty() || !obsoleteLink.isEmpty()) {
1485         out() << "<ul>\n";
1486         if (!allQmlMembersLink.isEmpty()) {
1487             out() << "<li><a href=\"" << allQmlMembersLink << "\">"
1488                   << "List of all members, including inherited members</a></li>\n";
1489         }
1490         if (!obsoleteLink.isEmpty()) {
1491             out() << "<li><a href=\"" << obsoleteLink << "\">"
1492                   << "Obsolete members</a></li>\n";
1493         }
1494         out() << "</ul>\n";
1495     }
1496 
1497     const QVector<Section> &stdQmlTypeSummarySections = sections.stdQmlTypeSummarySections();
1498     for (const auto &section : stdQmlTypeSummarySections) {
1499         if (!section.isEmpty()) {
1500             QString ref = registerRef(section.title().toLower());
1501             out() << "<a name=\"" << ref << "\"></a>" << divNavTop << '\n';
1502             out() << "<h2 id=\"" << ref << "\">" << protectEnc(section.title()) << "</h2>\n";
1503             generateQmlSummary(section.members(), qcn, marker);
1504         }
1505     }
1506 
1507     generateExtractionMark(qcn, DetailedDescriptionMark);
1508     QString detailsRef = registerRef("details");
1509     out() << "<a name=\"" << detailsRef << "\"></a>" << divNavTop << '\n';
1510     out() << "<h2 id=\"" << detailsRef << "\">"
1511           << "Detailed Description"
1512           << "</h2>\n";
1513     generateBody(qcn, marker);
1514     ClassNode *cn = qcn->classNode();
1515     if (cn)
1516         generateQmlText(cn->doc().body(), cn, marker, qcn->name());
1517     generateAlsoList(qcn, marker);
1518     generateExtractionMark(qcn, EndMark);
1519 
1520     const QVector<Section> &stdQmlTypeDetailsSections = sections.stdQmlTypeDetailsSections();
1521     for (const auto &section : stdQmlTypeDetailsSections) {
1522         if (!section.isEmpty()) {
1523             out() << "<h2>" << protectEnc(section.title()) << "</h2>\n";
1524             const QVector<Node *> members = section.members();
1525             for (const auto member : members) {
1526                 generateDetailedQmlMember(member, qcn, marker);
1527                 out() << "<br/>\n";
1528             }
1529         }
1530     }
1531     generateFooter(qcn);
1532     Generator::setQmlTypeContext(nullptr);
1533 }
1534 
1535 /*!
1536   Generate the HTML page for the QML basic type represented
1537   by the QML basic type node \a qbtn.
1538  */
generateQmlBasicTypePage(QmlBasicTypeNode * qbtn,CodeMarker * marker)1539 void HtmlGenerator::generateQmlBasicTypePage(QmlBasicTypeNode *qbtn, CodeMarker *marker)
1540 {
1541     SubTitleSize subTitleSize = LargeSubTitle;
1542     QString htmlTitle = qbtn->fullTitle();
1543     if (qbtn->isJsType())
1544         htmlTitle += " JavaScript Basic Type";
1545     else
1546         htmlTitle += " QML Basic Type";
1547 
1548     marker = CodeMarker::markerForLanguage(QLatin1String("QML"));
1549 
1550     generateHeader(htmlTitle, qbtn, marker);
1551     Sections sections(qbtn);
1552     generateTableOfContents(qbtn, marker, &sections.stdQmlTypeSummarySections());
1553     generateKeywordAnchors(qbtn);
1554     generateTitle(htmlTitle, Text() << qbtn->subtitle(), subTitleSize, qbtn, marker);
1555 
1556     const QVector<Section> &stdQmlTypeSummarySections = sections.stdQmlTypeSummarySections();
1557     for (const auto &section : stdQmlTypeSummarySections) {
1558         if (!section.isEmpty()) {
1559             QString ref = registerRef(section.title().toLower());
1560             out() << "<a name=\"" << ref << "\"></a>" << divNavTop << '\n';
1561             out() << "<h2 id=\"" << ref << "\">" << protectEnc(section.title()) << "</h2>\n";
1562             generateQmlSummary(section.members(), qbtn, marker);
1563         }
1564     }
1565 
1566     generateExtractionMark(qbtn, DetailedDescriptionMark);
1567     out() << "<div class=\"descr\"> <a name=\"" << registerRef("details")
1568           << "\"></a>\n"; // QTBUG-9504
1569 
1570     generateBody(qbtn, marker);
1571     out() << "</div>\n"; // QTBUG-9504
1572     generateAlsoList(qbtn, marker);
1573     generateExtractionMark(qbtn, EndMark);
1574 
1575     const QVector<Section> &stdQmlTypeDetailsSections = sections.stdQmlTypeDetailsSections();
1576     for (const auto &section : stdQmlTypeDetailsSections) {
1577         if (!section.isEmpty()) {
1578             out() << "<h2>" << protectEnc(section.title()) << "</h2>\n";
1579             const QVector<Node *> members = section.members();
1580             for (const auto member : members) {
1581                 generateDetailedQmlMember(member, qbtn, marker);
1582                 out() << "<br/>\n";
1583             }
1584         }
1585     }
1586     generateFooter(qbtn);
1587 }
1588 
1589 /*!
1590   Generate the HTML page for an entity that doesn't map
1591   to any underlying parsable C++, QML, or Javascript element.
1592  */
generatePageNode(PageNode * pn,CodeMarker * marker)1593 void HtmlGenerator::generatePageNode(PageNode *pn, CodeMarker *marker)
1594 {
1595     SubTitleSize subTitleSize = LargeSubTitle;
1596     QString fullTitle = pn->fullTitle();
1597 
1598     generateHeader(fullTitle, pn, marker);
1599     /*
1600       Generate the TOC for the new doc format.
1601       Don't generate a TOC for the home page.
1602     */
1603     if ((pn->name() != QLatin1String("index.html")))
1604         generateTableOfContents(pn, marker, nullptr);
1605 
1606     generateKeywordAnchors(pn);
1607     generateTitle(fullTitle, Text() << pn->subtitle(), subTitleSize, pn, marker);
1608     if (pn->isExample()) {
1609         generateBrief(pn, marker, nullptr, false);
1610     }
1611 
1612     generateExtractionMark(pn, DetailedDescriptionMark);
1613     out() << "<div class=\"descr\"> <a name=\"" << registerRef("details")
1614           << "\"></a>\n"; // QTBUG-9504
1615 
1616     generateBody(pn, marker);
1617     out() << "</div>\n"; // QTBUG-9504
1618     generateAlsoList(pn, marker);
1619     generateExtractionMark(pn, EndMark);
1620 
1621     generateFooter(pn);
1622 }
1623 
1624 /*!
1625   Generate the HTML page for a group, module, or QML module.
1626  */
generateCollectionNode(CollectionNode * cn,CodeMarker * marker)1627 void HtmlGenerator::generateCollectionNode(CollectionNode *cn, CodeMarker *marker)
1628 {
1629     SubTitleSize subTitleSize = LargeSubTitle;
1630     QString fullTitle = cn->fullTitle();
1631     QString ref;
1632 
1633     generateHeader(fullTitle, cn, marker);
1634     generateTableOfContents(cn, marker, nullptr);
1635     generateKeywordAnchors(cn);
1636     generateTitle(fullTitle, Text() << cn->subtitle(), subTitleSize, cn, marker);
1637 
1638     // Generate brief for C++ modules, status for all modules.
1639     if (cn->genus() != Node::DOC && cn->genus() != Node::DontCare) {
1640         if (cn->isModule())
1641             generateBrief(cn, marker);
1642         generateStatus(cn, marker);
1643         generateSince(cn, marker);
1644     }
1645 
1646     if (cn->isModule()) {
1647         if (!cn->noAutoList()) {
1648             NodeMultiMap nmm;
1649             cn->getMemberNamespaces(nmm);
1650             if (!nmm.isEmpty()) {
1651                 ref = registerRef("namespaces");
1652                 out() << "<a name=\"" << ref << "\"></a>" << divNavTop << '\n';
1653                 out() << "<h2 id=\"" << ref << "\">Namespaces</h2>\n";
1654                 generateAnnotatedList(cn, marker, nmm);
1655             }
1656             nmm.clear();
1657             cn->getMemberClasses(nmm);
1658             if (!nmm.isEmpty()) {
1659                 ref = registerRef("classes");
1660                 out() << "<a name=\"" << ref << "\"></a>" << divNavTop << '\n';
1661                 out() << "<h2 id=\"" << ref << "\">Classes</h2>\n";
1662                 generateAnnotatedList(cn, marker, nmm);
1663             }
1664         }
1665     }
1666 
1667     if (cn->isModule() && !cn->doc().briefText().isEmpty()) {
1668         generateExtractionMark(cn, DetailedDescriptionMark);
1669         ref = registerRef("details");
1670         out() << "<a name=\"" << ref << "\"></a>" << divNavTop << '\n';
1671         out() << "<div class=\"descr\">\n"; // QTBUG-9504
1672         out() << "<h2 id=\"" << ref << "\">"
1673               << "Detailed Description"
1674               << "</h2>\n";
1675     } else {
1676         generateExtractionMark(cn, DetailedDescriptionMark);
1677         out() << "<div class=\"descr\"> <a name=\"" << registerRef("details")
1678               << "\"></a>\n"; // QTBUG-9504
1679     }
1680 
1681     generateBody(cn, marker);
1682     out() << "</div>\n"; // QTBUG-9504
1683     generateAlsoList(cn, marker);
1684     generateExtractionMark(cn, EndMark);
1685 
1686     if (!cn->noAutoList()) {
1687         if (cn->isGroup())
1688             generateAnnotatedList(cn, marker, cn->members());
1689         else if (cn->isQmlModule() || cn->isJsModule())
1690             generateAnnotatedList(cn, marker, cn->members());
1691     }
1692     generateFooter(cn);
1693 }
1694 
1695 /*!
1696   Generate the HTML page for a generic collection. This is usually
1697   a collection of C++ elements that are related to an element in
1698   a different module.
1699  */
generateGenericCollectionPage(CollectionNode * cn,CodeMarker * marker)1700 void HtmlGenerator::generateGenericCollectionPage(CollectionNode *cn, CodeMarker *marker)
1701 {
1702     SubTitleSize subTitleSize = LargeSubTitle;
1703     QString fullTitle = cn->name();
1704     QString ref;
1705 
1706     generateHeader(fullTitle, cn, marker);
1707     generateKeywordAnchors(cn);
1708     generateTitle(fullTitle, Text() << cn->subtitle(), subTitleSize, cn, marker);
1709 
1710     Text brief;
1711     brief << "Each function or type documented here is related to a class or "
1712           << "namespace that is documented in a different module. The reference "
1713           << "page for that class or namespace will link to the function or type "
1714           << "on this page.";
1715     out() << "<p>";
1716     generateText(brief, cn, marker);
1717     out() << "</p>\n";
1718 
1719     const QList<Node *> members = cn->members();
1720     for (const auto &member : members)
1721         generateDetailedMember(member, cn, marker);
1722 
1723     generateFooter(cn);
1724 }
1725 
1726 /*!
1727   Returns "html" for this subclass of Generator.
1728  */
fileExtension() const1729 QString HtmlGenerator::fileExtension() const
1730 {
1731     return "html";
1732 }
1733 
1734 /*!
1735   Output navigation list in the html file.
1736  */
generateNavigationBar(const QString & title,const Node * node,CodeMarker * marker,const QString & buildversion,bool tableItems)1737 void HtmlGenerator::generateNavigationBar(const QString &title, const Node *node,
1738                                           CodeMarker *marker, const QString &buildversion,
1739                                           bool tableItems)
1740 {
1741     if (noNavigationBar || node == nullptr)
1742         return;
1743 
1744     Text navigationbar;
1745 
1746     // Set list item types based on the navigation bar type
1747     Atom::AtomType itemLeft = tableItems ? Atom::TableItemLeft : Atom::ListItemLeft;
1748     Atom::AtomType itemRight = tableItems ? Atom::TableItemRight : Atom::ListItemRight;
1749 
1750     if (hometitle == title)
1751         return;
1752     if (!homepage.isEmpty())
1753         navigationbar << Atom(itemLeft) << Atom(Atom::NavLink, homepage)
1754                       << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1755                       << Atom(Atom::String, hometitle)
1756                       << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom(itemRight);
1757     if (!landingpage.isEmpty() && landingtitle != title)
1758         navigationbar << Atom(itemLeft) << Atom(Atom::NavLink, landingpage)
1759                       << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1760                       << Atom(Atom::String, landingtitle)
1761                       << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom(itemRight);
1762 
1763     if (node->isClassNode()) {
1764         if (!cppclassespage.isEmpty() && !cppclassestitle.isEmpty())
1765             navigationbar << Atom(itemLeft) << Atom(Atom::NavLink, cppclassespage)
1766                           << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1767                           << Atom(Atom::String, cppclassestitle)
1768                           << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom(itemRight);
1769 
1770         if (!node->name().isEmpty())
1771             navigationbar << Atom(itemLeft) << Atom(Atom::String, node->name()) << Atom(itemRight);
1772     } else if (node->isQmlType() || node->isQmlBasicType() || node->isJsType()
1773                || node->isJsBasicType()) {
1774         if (!qmltypespage.isEmpty() && !qmltypestitle.isEmpty())
1775             navigationbar << Atom(itemLeft) << Atom(Atom::NavLink, qmltypespage)
1776                           << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1777                           << Atom(Atom::String, qmltypestitle)
1778                           << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom(itemRight)
1779                           << Atom(itemLeft) << Atom(Atom::String, title) << Atom(itemRight);
1780     } else {
1781         if (node->isAggregate()) {
1782             QStringList groups = static_cast<const Aggregate *>(node)->groupNames();
1783             if (groups.length() == 1) {
1784                 const Node *groupNode =
1785                         qdb_->findNodeByNameAndType(QStringList(groups[0]), &Node::isGroup);
1786                 if (groupNode && !groupNode->title().isEmpty()) {
1787                     navigationbar << Atom(itemLeft) << Atom(Atom::NavLink, groupNode->name())
1788                                   << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1789                                   << Atom(Atom::String, groupNode->title())
1790                                   << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK)
1791                                   << Atom(itemRight);
1792                 }
1793             }
1794         }
1795         if (!navigationbar.isEmpty()) {
1796             navigationbar << Atom(itemLeft) << Atom(Atom::String, title) << Atom(itemRight);
1797         }
1798     }
1799 
1800     generateText(navigationbar, node, marker);
1801 
1802     if (buildversion.isEmpty())
1803         return;
1804 
1805     navigationbar.clear();
1806 
1807     if (tableItems) {
1808         out() << "</tr></table><table class=\"buildversion\"><tr>\n"
1809               << "<td id=\"buildversion\" width=\"100%\" align=\"right\">";
1810     } else {
1811         out() << "<li id=\"buildversion\">";
1812     }
1813 
1814     // Link buildversion string to navigation.landingpage
1815     if (!landingpage.isEmpty() && landingtitle != title) {
1816         navigationbar << Atom(Atom::NavLink, landingpage)
1817                       << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1818                       << Atom(Atom::String, buildversion)
1819                       << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1820         generateText(navigationbar, node, marker);
1821     } else {
1822         out() << buildversion;
1823     }
1824     if (tableItems)
1825         out() << "</td>\n";
1826     else
1827         out() << "</li>\n";
1828 }
1829 
generateHeader(const QString & title,const Node * node,CodeMarker * marker)1830 void HtmlGenerator::generateHeader(const QString &title, const Node *node, CodeMarker *marker)
1831 {
1832     out() << "<!DOCTYPE html>\n";
1833     out() << QString("<html lang=\"%1\">\n").arg(naturalLanguage);
1834     out() << "<head>\n";
1835     out() << "  <meta charset=\"utf-8\">\n";
1836     if (node && !node->doc().location().isEmpty())
1837         out() << "<!-- " << node->doc().location().fileName() << " -->\n";
1838 
1839     // determine the rest of the <title> element content: "title | titleSuffix version"
1840     QString titleSuffix;
1841     if (!landingtitle.isEmpty()) {
1842         // for normal pages: "title | landingtitle version"
1843         titleSuffix = landingtitle;
1844     } else if (!hometitle.isEmpty()) {
1845         // for pages that set the homepage title but not landing page title:
1846         // "title | hometitle version"
1847         if (title != hometitle)
1848             titleSuffix = hometitle;
1849     } else if (!project.isEmpty()) {
1850         // for projects outside of Qt or Qt 5: "title | project version"
1851         if (title != project)
1852             titleSuffix = project;
1853     } else
1854         // default: "title | Qt version"
1855         titleSuffix = QLatin1String("Qt ");
1856 
1857     if (title == titleSuffix)
1858         titleSuffix.clear();
1859 
1860     QString divider;
1861     if (!titleSuffix.isEmpty() && !title.isEmpty())
1862         divider = QLatin1String(" | ");
1863 
1864     // Generating page title
1865     out() << "  <title>" << protectEnc(title) << divider << titleSuffix;
1866 
1867     // append a full version to the suffix if neither suffix nor title
1868     // include (a prefix of) version information
1869     QVersionNumber projectVersion = QVersionNumber::fromString(qdb_->version());
1870     if (!projectVersion.isNull()) {
1871         QVersionNumber titleVersion;
1872         QRegExp re("\\d+\\.\\d+");
1873         const QString &versionedTitle = titleSuffix.isEmpty() ? title : titleSuffix;
1874         if (versionedTitle.contains(re))
1875             titleVersion = QVersionNumber::fromString(re.cap());
1876         if (titleVersion.isNull() || !titleVersion.isPrefixOf(projectVersion))
1877             out() << QLatin1Char(' ') << projectVersion.toString();
1878     }
1879     out() << "</title>\n";
1880 
1881     // Include style sheet and script links.
1882     out() << headerStyles;
1883     out() << headerScripts;
1884     if (endHeader.isEmpty())
1885         out() << "</head>\n<body>\n";
1886     else
1887         out() << endHeader;
1888 
1889 #ifdef GENERATE_MAC_REFS
1890     if (mainPage)
1891         generateMacRef(node, marker);
1892 #endif
1893 
1894     out() << QString(postHeader).replace("\\" + COMMAND_VERSION, qdb_->version());
1895     bool usingTable = postHeader.trimmed().endsWith(QLatin1String("<tr>"));
1896     generateNavigationBar(title, node, marker, buildversion, usingTable);
1897     out() << QString(postPostHeader).replace("\\" + COMMAND_VERSION, qdb_->version());
1898 
1899     navigationLinks.clear();
1900     refMap.clear();
1901 
1902     if (node && !node->links().empty()) {
1903         QPair<QString, QString> linkPair;
1904         QPair<QString, QString> anchorPair;
1905         const Node *linkNode;
1906         bool useSeparator = false;
1907 
1908         if (node->links().contains(Node::PreviousLink)) {
1909             linkPair = node->links()[Node::PreviousLink];
1910             linkNode = qdb_->findNodeForTarget(linkPair.first, node);
1911             if (linkNode == nullptr)
1912                 node->doc().location().warning(tr("Cannot link to '%1'").arg(linkPair.first));
1913             if (linkNode == nullptr || linkNode == node)
1914                 anchorPair = linkPair;
1915             else
1916                 anchorPair = anchorForNode(linkNode);
1917 
1918             out() << "  <link rel=\"prev\" href=\"" << anchorPair.first << "\" />\n";
1919 
1920             navigationLinks += "<a class=\"prevPage\" href=\"" + anchorPair.first + "\">";
1921             if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty())
1922                 navigationLinks += protect(anchorPair.second);
1923             else
1924                 navigationLinks += protect(linkPair.second);
1925             navigationLinks += "</a>\n";
1926             useSeparator = !navigationSeparator.isEmpty();
1927         }
1928         if (node->links().contains(Node::NextLink)) {
1929             linkPair = node->links()[Node::NextLink];
1930             linkNode = qdb_->findNodeForTarget(linkPair.first, node);
1931             if (linkNode == nullptr)
1932                 node->doc().location().warning(tr("Cannot link to '%1'").arg(linkPair.first));
1933             if (linkNode == nullptr || linkNode == node)
1934                 anchorPair = linkPair;
1935             else
1936                 anchorPair = anchorForNode(linkNode);
1937 
1938             out() << "  <link rel=\"next\" href=\"" << anchorPair.first << "\" />\n";
1939 
1940             if (useSeparator)
1941                 navigationLinks += navigationSeparator;
1942 
1943             navigationLinks += "<a class=\"nextPage\" href=\"" + anchorPair.first + "\">";
1944             if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty())
1945                 navigationLinks += protect(anchorPair.second);
1946             else
1947                 navigationLinks += protect(linkPair.second);
1948             navigationLinks += "</a>\n";
1949         }
1950         if (node->links().contains(Node::StartLink)) {
1951             linkPair = node->links()[Node::StartLink];
1952             linkNode = qdb_->findNodeForTarget(linkPair.first, node);
1953             if (linkNode == nullptr)
1954                 node->doc().location().warning(tr("Cannot link to '%1'").arg(linkPair.first));
1955             if (linkNode == nullptr || linkNode == node)
1956                 anchorPair = linkPair;
1957             else
1958                 anchorPair = anchorForNode(linkNode);
1959             out() << "  <link rel=\"start\" href=\"" << anchorPair.first << "\" />\n";
1960         }
1961     }
1962 
1963     if (node && !node->links().empty())
1964         out() << "<p class=\"naviNextPrevious headerNavi\">\n" << navigationLinks << "</p><p/>\n";
1965 }
1966 
generateTitle(const QString & title,const Text & subtitle,SubTitleSize subTitleSize,const Node * relative,CodeMarker * marker)1967 void HtmlGenerator::generateTitle(const QString &title, const Text &subtitle,
1968                                   SubTitleSize subTitleSize, const Node *relative,
1969                                   CodeMarker *marker)
1970 {
1971     out() << QString(prologue).replace("\\" + COMMAND_VERSION, qdb_->version());
1972     if (!title.isEmpty())
1973         out() << "<h1 class=\"title\">" << protectEnc(title) << "</h1>\n";
1974     if (!subtitle.isEmpty()) {
1975         out() << "<span";
1976         if (subTitleSize == SmallSubTitle)
1977             out() << " class=\"small-subtitle\">";
1978         else
1979             out() << " class=\"subtitle\">";
1980         generateText(subtitle, relative, marker);
1981         out() << "</span>\n";
1982     }
1983 }
1984 
generateFooter(const Node * node)1985 void HtmlGenerator::generateFooter(const Node *node)
1986 {
1987     if (node && !node->links().empty())
1988         out() << "<p class=\"naviNextPrevious footerNavi\">\n" << navigationLinks << "</p>\n";
1989 
1990     out() << QString(footer).replace("\\" + COMMAND_VERSION, qdb_->version())
1991           << QString(address).replace("\\" + COMMAND_VERSION, qdb_->version());
1992 
1993     out() << "</body>\n";
1994     out() << "</html>\n";
1995 }
1996 
1997 /*!
1998 Lists the required imports and includes in a table.
1999 The number of rows is known.
2000 */
generateRequisites(Aggregate * aggregate,CodeMarker * marker)2001 void HtmlGenerator::generateRequisites(Aggregate *aggregate, CodeMarker *marker)
2002 {
2003     QMap<QString, Text> requisites;
2004     Text text;
2005 
2006     const QString headerText = "Header";
2007     const QString sinceText = "Since";
2008     const QString inheritedBytext = "Inherited By";
2009     const QString inheritsText = "Inherits";
2010     const QString instantiatedByText = "Instantiated By";
2011     const QString qtVariableText = "qmake";
2012 
2013     // add the include files to the map
2014     if (!aggregate->includeFiles().isEmpty()) {
2015         text.clear();
2016         text << highlightedCode(
2017                 indent(codeIndent, marker->markedUpIncludes(aggregate->includeFiles())), aggregate);
2018         requisites.insert(headerText, text);
2019     }
2020 
2021     // The order of the requisites matter
2022     QStringList requisiteorder;
2023     requisiteorder << headerText << qtVariableText << sinceText << instantiatedByText
2024                    << inheritsText << inheritedBytext;
2025 
2026     // add the since and project into the map
2027     if (!aggregate->since().isEmpty()) {
2028         text.clear();
2029         text << formatSince(aggregate) << Atom::ParaRight;
2030         requisites.insert(sinceText, text);
2031     }
2032 
2033     if (aggregate->isClassNode() || aggregate->isNamespace()) {
2034         // add the QT variable to the map
2035         if (!aggregate->physicalModuleName().isEmpty()) {
2036             const CollectionNode *cn =
2037                     qdb_->getCollectionNode(aggregate->physicalModuleName(), Node::Module);
2038             if (cn && !cn->qtVariable().isEmpty()) {
2039                 text.clear();
2040                 text << "QT += " + cn->qtVariable();
2041                 requisites.insert(qtVariableText, text);
2042             }
2043         }
2044     }
2045 
2046     if (aggregate->isClassNode()) {
2047         ClassNode *classe = static_cast<ClassNode *>(aggregate);
2048         if (classe->qmlElement() != nullptr && !classe->isInternal()) {
2049             text.clear();
2050             text << Atom(Atom::LinkNode, CodeMarker::stringForNode(classe->qmlElement()))
2051                  << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
2052                  << Atom(Atom::String, classe->qmlElement()->name())
2053                  << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
2054             requisites.insert(instantiatedByText, text);
2055         }
2056 
2057         // add the inherits to the map
2058         if (!classe->baseClasses().isEmpty()) {
2059             int index = 0;
2060             text.clear();
2061             const auto baseClasses = classe->baseClasses();
2062             for (const auto &cls : baseClasses) {
2063                 if (cls.node_) {
2064                     appendFullName(text, cls.node_, classe);
2065 
2066                     if (cls.access_ == Node::Protected) {
2067                         text << " (protected)";
2068                     } else if (cls.access_ == Node::Private) {
2069                         text << " (private)";
2070                     }
2071                     text << comma(index++, classe->baseClasses().count());
2072                 }
2073             }
2074             text << Atom::ParaRight;
2075             if (index > 0)
2076                 requisites.insert(inheritsText, text);
2077         }
2078 
2079         // add the inherited-by to the map
2080         if (!classe->derivedClasses().isEmpty()) {
2081             text.clear();
2082             text << Atom::ParaLeft;
2083             int count = appendSortedNames(text, classe, classe->derivedClasses());
2084             text << Atom::ParaRight;
2085             if (count > 0)
2086                 requisites.insert(inheritedBytext, text);
2087         }
2088     }
2089 
2090     if (!requisites.isEmpty()) {
2091         // generate the table
2092         out() << "<div class=\"table\"><table class=\"alignedsummary\">\n";
2093 
2094         for (auto it = requisiteorder.constBegin(); it != requisiteorder.constEnd(); ++it) {
2095 
2096             if (requisites.contains(*it)) {
2097                 out() << "<tr>"
2098                       << "<td class=\"memItemLeft rightAlign topAlign\"> " << *it
2099                       << ":"
2100                          "</td><td class=\"memItemRight bottomAlign\"> ";
2101 
2102                 if (*it == headerText)
2103                     out() << requisites.value(*it).toString();
2104                 else
2105                     generateText(requisites.value(*it), aggregate, marker);
2106                 out() << "</td></tr>";
2107             }
2108         }
2109         out() << "</table></div>";
2110     }
2111 }
2112 
2113 /*!
2114 Lists the required imports and includes in a table.
2115 The number of rows is known.
2116 */
generateQmlRequisites(QmlTypeNode * qcn,CodeMarker * marker)2117 void HtmlGenerator::generateQmlRequisites(QmlTypeNode *qcn, CodeMarker *marker)
2118 {
2119     if (qcn == nullptr)
2120         return;
2121     QMap<QString, Text> requisites;
2122     Text text;
2123 
2124     const QString importText = "Import Statement:";
2125     const QString sinceText = "Since:";
2126     const QString inheritedBytext = "Inherited By:";
2127     const QString inheritsText = "Inherits:";
2128     const QString instantiatesText = "Instantiates:";
2129 
2130     // add the module name and version to the map
2131     QString logicalModuleVersion;
2132     const CollectionNode *collection = qcn->logicalModule();
2133 
2134     // skip import statement of \internal collections
2135     if (!collection || !collection->isInternal() || showInternal_) {
2136         logicalModuleVersion =
2137                 collection ? collection->logicalModuleVersion() : qcn->logicalModuleVersion();
2138 
2139         if (logicalModuleVersion.isEmpty() || qcn->logicalModuleName().isEmpty())
2140             qcn->doc().location().warning(tr("Could not resolve QML import "
2141                                              "statement for type '%1'")
2142                                                   .arg(qcn->name()),
2143                                           tr("Maybe you forgot to use the "
2144                                              "'\\%1' command?")
2145                                                   .arg(COMMAND_INQMLMODULE));
2146 
2147         text.clear();
2148         text << "import " + qcn->logicalModuleName() + QLatin1Char(' ') + logicalModuleVersion;
2149         requisites.insert(importText, text);
2150     }
2151 
2152     // add the since and project into the map
2153     if (!qcn->since().isEmpty()) {
2154         text.clear();
2155         text << formatSince(qcn) << Atom::ParaRight;
2156         requisites.insert(sinceText, text);
2157     }
2158 
2159     // add the instantiates to the map
2160     ClassNode *cn = qcn->classNode();
2161     if (cn && !cn->isInternal()) {
2162         text.clear();
2163         text << Atom(Atom::LinkNode, CodeMarker::stringForNode(qcn));
2164         text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
2165         text << Atom(Atom::LinkNode, CodeMarker::stringForNode(cn));
2166         text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
2167         text << Atom(Atom::String, cn->name());
2168         text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
2169         requisites.insert(instantiatesText, text);
2170     }
2171 
2172     // add the inherits to the map
2173     QmlTypeNode *base = qcn->qmlBaseNode();
2174     while (base && base->isInternal()) {
2175         base = base->qmlBaseNode();
2176     }
2177     if (base) {
2178         text.clear();
2179         text << Atom::ParaLeft << Atom(Atom::LinkNode, CodeMarker::stringForNode(base))
2180              << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << Atom(Atom::String, base->name())
2181              << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom::ParaRight;
2182         requisites.insert(inheritsText, text);
2183     }
2184 
2185     // add the inherited-by to the map
2186     NodeList subs;
2187     QmlTypeNode::subclasses(qcn, subs);
2188     if (!subs.isEmpty()) {
2189         text.clear();
2190         text << Atom::ParaLeft;
2191         int count = appendSortedQmlNames(text, qcn, subs);
2192         text << Atom::ParaRight;
2193         if (count > 0)
2194             requisites.insert(inheritedBytext, text);
2195     }
2196 
2197     // The order of the requisites matter
2198     const QStringList requisiteorder { importText, sinceText, instantiatesText, inheritsText,
2199                                        inheritedBytext };
2200 
2201     if (!requisites.isEmpty()) {
2202         // generate the table
2203         out() << "<div class=\"table\"><table class=\"alignedsummary\">\n";
2204         for (const auto &requisite : requisiteorder) {
2205 
2206             if (requisites.contains(requisite)) {
2207                 out() << "<tr>"
2208                       << "<td class=\"memItemLeft rightAlign topAlign\"> " << requisite
2209                       << "</td><td class=\"memItemRight bottomAlign\"> ";
2210 
2211                 if (requisite == importText)
2212                     out() << requisites.value(requisite).toString();
2213                 else
2214                     generateText(requisites.value(requisite), qcn, marker);
2215                 out() << "</td></tr>";
2216             }
2217         }
2218         out() << "</table></div>";
2219     }
2220 }
2221 
generateBrief(const Node * node,CodeMarker * marker,const Node * relative,bool addLink)2222 void HtmlGenerator::generateBrief(const Node *node, CodeMarker *marker, const Node *relative,
2223                                   bool addLink)
2224 {
2225     Text brief = node->doc().briefText();
2226 
2227     if (!brief.isEmpty()) {
2228         if (!brief.lastAtom()->string().endsWith('.')) {
2229             brief << Atom(Atom::String, ".");
2230             node->doc().location().warning(
2231                     tr("'\\brief' statement does not end with a full stop."));
2232         }
2233         generateExtractionMark(node, BriefMark);
2234         out() << "<p>";
2235         generateText(brief, node, marker);
2236 
2237         if (addLink) {
2238             if (!relative || node == relative)
2239                 out() << " <a href=\"#";
2240             else
2241                 out() << " <a href=\"" << linkForNode(node, relative) << '#';
2242             out() << registerRef("details") << "\">More...</a>";
2243         }
2244 
2245         out() << "</p>\n";
2246         generateExtractionMark(node, EndMark);
2247     }
2248 }
2249 
2250 /*!
2251   Revised for the new doc format.
2252   Generates a table of contents beginning at \a node.
2253  */
generateTableOfContents(const Node * node,CodeMarker * marker,QVector<Section> * sections)2254 void HtmlGenerator::generateTableOfContents(const Node *node, CodeMarker *marker,
2255                                             QVector<Section> *sections)
2256 {
2257     QVector<Atom *> toc;
2258     if (node->doc().hasTableOfContents())
2259         toc = node->doc().tableOfContents();
2260     if (tocDepth == 0 || (toc.isEmpty() && !sections && !node->isModule())) {
2261         generateSidebar();
2262         return;
2263     }
2264 
2265     int sectionNumber = 1;
2266     int detailsBase = 0;
2267 
2268     // disable nested links in table of contents
2269     inContents_ = true;
2270     inLink_ = true;
2271 
2272     out() << "<div class=\"sidebar\">\n";
2273     out() << "<div class=\"toc\">\n";
2274     out() << "<h3><a name=\"toc\">Contents</a></h3>\n";
2275     out() << "<ul>\n";
2276 
2277     if (node->isModule()) {
2278         if (!static_cast<const CollectionNode *>(node)->noAutoList()) {
2279             if (node->hasNamespaces()) {
2280                 out() << "<li class=\"level" << sectionNumber << "\"><a href=\"#"
2281                       << registerRef("namespaces") << "\">Namespaces</a></li>\n";
2282             }
2283             if (node->hasClasses()) {
2284                 out() << "<li class=\"level" << sectionNumber << "\"><a href=\"#"
2285                       << registerRef("classes") << "\">Classes</a></li>\n";
2286             }
2287         }
2288         out() << "<li class=\"level" << sectionNumber << "\"><a href=\"#" << registerRef("details")
2289               << "\">Detailed Description</a></li>\n";
2290         for (int i = 0; i < toc.size(); ++i) {
2291             if (toc.at(i)->string().toInt() == 1) {
2292                 detailsBase = 1;
2293                 break;
2294             }
2295         }
2296     } else if (sections
2297                && (node->isClassNode() || node->isNamespace() || node->isQmlType()
2298                    || node->isJsType())) {
2299         for (const auto &section : qAsConst(*sections)) {
2300             if (!section.members().isEmpty()) {
2301                 out() << "<li class=\"level" << sectionNumber << "\"><a href=\"#"
2302                       << registerRef(section.plural()) << "\">" << section.title() << "</a></li>\n";
2303             }
2304             if (!section.reimplementedMembers().isEmpty()) {
2305                 QString ref = QString("Reimplemented ") + section.plural();
2306                 out() << "<li class=\"level" << sectionNumber << "\"><a href=\"#"
2307                       << registerRef(ref.toLower()) << "\">"
2308                       << QString("Reimplemented ") + section.title() << "</a></li>\n";
2309             }
2310         }
2311         if (!node->isNamespace() || node->hasDoc()) {
2312             out() << "<li class=\"level" << sectionNumber << "\"><a href=\"#"
2313                   << registerRef("details") << "\">Detailed Description</a></li>\n";
2314         }
2315         for (int i = 0; i < toc.size(); ++i) {
2316             if (toc.at(i)->string().toInt() == 1) {
2317                 detailsBase = 1;
2318                 break;
2319             }
2320         }
2321     }
2322 
2323     for (const auto &atom : toc) {
2324         sectionNumber = atom->string().toInt() + detailsBase;
2325         // restrict the ToC depth to the one set by the HTML.tocdepth variable or
2326         // print all levels if tocDepth is not set.
2327         if (sectionNumber <= tocDepth || tocDepth < 0) {
2328             int numAtoms;
2329             Text headingText = Text::sectionHeading(atom);
2330             QString s = headingText.toString();
2331             out() << "<li class=\"level" << sectionNumber << "\">";
2332             out() << "<a href=\"" << '#' << Doc::canonicalTitle(s) << "\">";
2333             generateAtomList(headingText.firstAtom(), node, marker, true, numAtoms);
2334             out() << "</a></li>\n";
2335         }
2336     }
2337     out() << "</ul>\n";
2338     out() << "</div>\n";
2339     out() << "<div class=\"sidebar-content\" id=\"sidebar-content\"></div>";
2340     out() << "</div>\n";
2341     inContents_ = false;
2342     inLink_ = false;
2343 }
2344 
2345 /*!
2346   Outputs a placeholder div where the style can add customized sidebar content.
2347  */
generateSidebar()2348 void HtmlGenerator::generateSidebar()
2349 {
2350     out() << "<div class=\"sidebar\">";
2351     out() << "<div class=\"sidebar-content\" id=\"sidebar-content\"></div>";
2352     out() << "</div>\n";
2353 }
2354 
generateAllMembersFile(const Section & section,CodeMarker * marker)2355 QString HtmlGenerator::generateAllMembersFile(const Section &section, CodeMarker *marker)
2356 {
2357     if (section.isEmpty())
2358         return QString();
2359 
2360     const Aggregate *aggregate = section.aggregate();
2361     QString fileName = fileBase(aggregate) + "-members." + fileExtension();
2362     beginSubPage(aggregate, fileName);
2363     QString title = "List of All Members for " + aggregate->name();
2364     generateHeader(title, aggregate, marker);
2365     generateSidebar();
2366     generateTitle(title, Text(), SmallSubTitle, aggregate, marker);
2367     out() << "<p>This is the complete list of members for ";
2368     generateFullName(aggregate, nullptr);
2369     out() << ", including inherited members.</p>\n";
2370 
2371     generateSectionList(section, aggregate, marker);
2372 
2373     generateFooter();
2374     endSubPage();
2375     return fileName;
2376 }
2377 
2378 /*!
2379   This function creates an html page on which are listed all
2380   the members of the QML class used to generte the \a sections,
2381   including the inherited members. The \a marker is used for
2382   formatting stuff.
2383  */
generateAllQmlMembersFile(const Sections & sections,CodeMarker * marker)2384 QString HtmlGenerator::generateAllQmlMembersFile(const Sections &sections, CodeMarker *marker)
2385 {
2386 
2387     if (sections.allMembersSection().isEmpty())
2388         return QString();
2389 
2390     const Aggregate *aggregate = sections.aggregate();
2391     QString fileName = fileBase(aggregate) + "-members." + fileExtension();
2392     beginSubPage(aggregate, fileName);
2393     QString title = "List of All Members for " + aggregate->name();
2394     generateHeader(title, aggregate, marker);
2395     generateSidebar();
2396     generateTitle(title, Text(), SmallSubTitle, aggregate, marker);
2397     out() << "<p>This is the complete list of members for ";
2398     generateFullName(aggregate, nullptr);
2399     out() << ", including inherited members.</p>\n";
2400 
2401     ClassKeysNodesList &cknl = sections.allMembersSection().classKeysNodesList();
2402     if (!cknl.isEmpty()) {
2403         for (int i = 0; i < cknl.size(); i++) {
2404             ClassKeysNodes *ckn = cknl[i];
2405             const QmlTypeNode *qcn = ckn->first;
2406             KeysAndNodes &kn = ckn->second;
2407             QStringList &keys = kn.first;
2408             NodeVector &nodes = kn.second;
2409             if (nodes.isEmpty())
2410                 continue;
2411             if (i != 0) {
2412                 out() << "<p>The following members are inherited from ";
2413                 generateFullName(qcn, nullptr);
2414                 out() << ".</p>\n";
2415             }
2416             out() << "<ul>\n";
2417             for (int j = 0; j < keys.size(); j++) {
2418                 Node *node = nodes[j];
2419                 if (node->access() == Node::Private || node->isInternal())
2420                     continue;
2421                 if (node->isSharingComment() && node->sharedCommentNode()->isPropertyGroup())
2422                     continue;
2423 
2424                 std::function<void(Node *)> generate = [&](Node *n) {
2425                     out() << "<li class=\"fn\">";
2426                     generateQmlItem(n, aggregate, marker, true);
2427                     if (n->isDefault())
2428                         out() << " [default]";
2429                     else if (n->isAttached())
2430                         out() << " [attached]";
2431                     // Indent property group members
2432                     if (n->isPropertyGroup()) {
2433                         out() << "<ul>\n";
2434                         const QVector<Node *> &collective =
2435                                 static_cast<SharedCommentNode *>(n)->collective();
2436                         std::for_each(collective.begin(), collective.end(), generate);
2437                         out() << "</ul>\n";
2438                     }
2439                     out() << "</li>\n";
2440                 };
2441                 generate(node);
2442             }
2443             out() << "</ul>\n";
2444         }
2445     }
2446 
2447     generateFooter();
2448     endSubPage();
2449     return fileName;
2450 }
2451 
generateObsoleteMembersFile(const Sections & sections,CodeMarker * marker)2452 QString HtmlGenerator::generateObsoleteMembersFile(const Sections &sections, CodeMarker *marker)
2453 {
2454     SectionPtrVector summary_spv;
2455     SectionPtrVector details_spv;
2456     if (!sections.hasObsoleteMembers(&summary_spv, &details_spv))
2457         return QString();
2458 
2459     Aggregate *aggregate = sections.aggregate();
2460     QString title = "Obsolete Members for " + aggregate->name();
2461     QString fileName = fileBase(aggregate) + "-obsolete." + fileExtension();
2462     QString link;
2463     if (useOutputSubdirs() && !Generator::outputSubdir().isEmpty())
2464         link = QString("../" + Generator::outputSubdir() + QLatin1Char('/'));
2465     link += fileName;
2466     aggregate->setObsoleteLink(link);
2467 
2468     beginSubPage(aggregate, fileName);
2469     generateHeader(title, aggregate, marker);
2470     generateSidebar();
2471     generateTitle(title, Text(), SmallSubTitle, aggregate, marker);
2472 
2473     out() << "<p><b>The following members of class "
2474           << "<a href=\"" << linkForNode(aggregate, nullptr) << "\">"
2475           << protectEnc(aggregate->name()) << "</a>"
2476           << " are obsolete.</b> "
2477           << "They are provided to keep old source code working. "
2478           << "We strongly advise against using them in new code.</p>\n";
2479 
2480     for (const auto &section : summary_spv) {
2481         out() << "<h2>" << protectEnc(section->title()) << "</h2>\n";
2482         generateSectionList(*section, aggregate, marker, Section::Obsolete);
2483     }
2484 
2485     for (const auto &section : details_spv) {
2486         out() << "<h2>" << protectEnc(section->title()) << "</h2>\n";
2487 
2488         const NodeVector &members = section->obsoleteMembers();
2489         for (const auto &member : members) {
2490             if (member->access() != Node::Private)
2491                 generateDetailedMember(member, aggregate, marker);
2492         }
2493     }
2494 
2495     generateFooter();
2496     endSubPage();
2497     return fileName;
2498 }
2499 
2500 /*!
2501   Generates a separate file where obsolete members of the QML
2502   type \a qcn are listed. The \a marker is used to generate
2503   the section lists, which are then traversed and output here.
2504 
2505   Note that this function currently only handles correctly the
2506   case where \a status is \c {Section::Obsolete}.
2507  */
generateObsoleteQmlMembersFile(const Sections & sections,CodeMarker * marker)2508 QString HtmlGenerator::generateObsoleteQmlMembersFile(const Sections &sections, CodeMarker *marker)
2509 {
2510     SectionPtrVector summary_spv;
2511     SectionPtrVector details_spv;
2512     if (!sections.hasObsoleteMembers(&summary_spv, &details_spv))
2513         return QString();
2514 
2515     Aggregate *aggregate = sections.aggregate();
2516     QString title = "Obsolete Members for " + aggregate->name();
2517     QString fileName = fileBase(aggregate) + "-obsolete." + fileExtension();
2518     QString link;
2519     if (useOutputSubdirs() && !Generator::outputSubdir().isEmpty())
2520         link = QString("../" + Generator::outputSubdir() + QLatin1Char('/'));
2521     link += fileName;
2522     aggregate->setObsoleteLink(link);
2523 
2524     beginSubPage(aggregate, fileName);
2525     generateHeader(title, aggregate, marker);
2526     generateSidebar();
2527     generateTitle(title, Text(), SmallSubTitle, aggregate, marker);
2528 
2529     out() << "<p><b>The following members of QML type "
2530           << "<a href=\"" << linkForNode(aggregate, nullptr) << "\">"
2531           << protectEnc(aggregate->name()) << "</a>"
2532           << " are obsolete.</b> "
2533           << "They are provided to keep old source code working. "
2534           << "We strongly advise against using them in new code.</p>\n";
2535 
2536     for (const auto &section : summary_spv) {
2537         QString ref = registerRef(section->title().toLower());
2538         out() << "<a name=\"" << ref << "\"></a>" << divNavTop << '\n';
2539         out() << "<h2 id=\"" << ref << "\">" << protectEnc(section->title()) << "</h2>\n";
2540         generateQmlSummary(section->obsoleteMembers(), aggregate, marker);
2541     }
2542 
2543     for (const auto &section : details_spv) {
2544         out() << "<h2>" << protectEnc(section->title()) << "</h2>\n";
2545         const NodeVector &members = section->obsoleteMembers();
2546         for (const auto &member : members) {
2547             generateDetailedQmlMember(member, aggregate, marker);
2548             out() << "<br/>\n";
2549         }
2550     }
2551 
2552     generateFooter();
2553     endSubPage();
2554     return fileName;
2555 }
2556 
generateClassHierarchy(const Node * relative,NodeMap & classMap)2557 void HtmlGenerator::generateClassHierarchy(const Node *relative, NodeMap &classMap)
2558 {
2559     if (classMap.isEmpty())
2560         return;
2561 
2562     NodeMap topLevel;
2563     for (auto it = classMap.begin(); it != classMap.end(); ++it) {
2564         ClassNode *classe = static_cast<ClassNode *>(*it);
2565         if (classe->baseClasses().isEmpty())
2566             topLevel.insert(classe->name(), classe);
2567     }
2568 
2569     QStack<NodeMap> stack;
2570     stack.push(topLevel);
2571 
2572     out() << "<ul>\n";
2573     while (!stack.isEmpty()) {
2574         if (stack.top().isEmpty()) {
2575             stack.pop();
2576             out() << "</ul>\n";
2577         } else {
2578             ClassNode *child = static_cast<ClassNode *>(*stack.top().begin());
2579             out() << "<li>";
2580             generateFullName(child, relative);
2581             out() << "</li>\n";
2582             stack.top().erase(stack.top().begin());
2583 
2584             NodeMap newTop;
2585             const auto derivedClasses = child->derivedClasses();
2586             for (const RelatedClass &d : derivedClasses) {
2587                 if (d.node_ && d.node_->isInAPI())
2588                     newTop.insert(d.node_->name(), d.node_);
2589             }
2590             if (!newTop.isEmpty()) {
2591                 stack.push(newTop);
2592                 out() << "<ul>\n";
2593             }
2594         }
2595     }
2596 }
2597 
2598 /*!
2599   Output an annotated list of the nodes in \a nodeMap.
2600   A two-column table is output.
2601  */
generateAnnotatedList(const Node * relative,CodeMarker * marker,const NodeMultiMap & nmm)2602 void HtmlGenerator::generateAnnotatedList(const Node *relative, CodeMarker *marker,
2603                                           const NodeMultiMap &nmm)
2604 {
2605     if (nmm.isEmpty() || relative == nullptr)
2606         return;
2607     generateAnnotatedList(relative, marker, nmm.values());
2608 }
2609 
2610 /*!
2611  */
generateAnnotatedList(const Node * relative,CodeMarker * marker,const NodeList & unsortedNodes)2612 void HtmlGenerator::generateAnnotatedList(const Node *relative, CodeMarker *marker,
2613                                           const NodeList &unsortedNodes)
2614 {
2615     NodeMultiMap nmm;
2616     bool allInternal = true;
2617     for (auto *node : unsortedNodes) {
2618         if (!node->isInternal() && !node->isObsolete()) {
2619             allInternal = false;
2620             nmm.insert(node->fullName(relative), node);
2621         }
2622     }
2623     if (allInternal)
2624         return;
2625     out() << "<div class=\"table\"><table class=\"annotated\">\n";
2626     int row = 0;
2627     NodeList nodes = nmm.values();
2628     std::sort(nodes.begin(), nodes.end(), Node::nodeNameLessThan);
2629 
2630     for (const auto *node : qAsConst(nodes)) {
2631         if (++row % 2 == 1)
2632             out() << "<tr class=\"odd topAlign\">";
2633         else
2634             out() << "<tr class=\"even topAlign\">";
2635         out() << "<td class=\"tblName\"><p>";
2636         generateFullName(node, relative);
2637         out() << "</p></td>";
2638 
2639         if (!node->isTextPageNode()) {
2640             Text brief = node->doc().trimmedBriefText(node->name());
2641             if (!brief.isEmpty()) {
2642                 out() << "<td class=\"tblDescr\"><p>";
2643                 generateText(brief, node, marker);
2644                 out() << "</p></td>";
2645             } else if (!node->reconstitutedBrief().isEmpty()) {
2646                 out() << "<td class=\"tblDescr\"><p>";
2647                 out() << node->reconstitutedBrief();
2648                 out() << "</p></td>";
2649             }
2650         } else {
2651             out() << "<td class=\"tblDescr\"><p>";
2652             if (!node->reconstitutedBrief().isEmpty()) {
2653                 out() << node->reconstitutedBrief();
2654             } else
2655                 out() << protectEnc(node->doc().briefText().toString());
2656             out() << "</p></td>";
2657         }
2658         out() << "</tr>\n";
2659     }
2660     out() << "</table></div>\n";
2661 }
2662 
2663 /*!
2664   Outputs a series of annotated lists from the nodes in \a nmm,
2665   divided into sections based by the key names in the multimap.
2666  */
generateAnnotatedLists(const Node * relative,CodeMarker * marker,const NodeMultiMap & nmm)2667 void HtmlGenerator::generateAnnotatedLists(const Node *relative, CodeMarker *marker,
2668                                            const NodeMultiMap &nmm)
2669 {
2670     const auto &uniqueKeys = nmm.uniqueKeys();
2671     for (const QString &name : uniqueKeys) {
2672         if (!name.isEmpty()) {
2673             out() << "<h2 id=\"" << registerRef(name.toLower()) << "\">" << protectEnc(name)
2674                   << "</h2>\n";
2675         }
2676         generateAnnotatedList(relative, marker, nmm.values(name));
2677     }
2678 }
2679 
2680 /*!
2681   This function finds the common prefix of the names of all
2682   the classes in the class map \a nmm and then generates a
2683   compact list of the class names alphabetized on the part
2684   of the name not including the common prefix. You can tell
2685   the function to use \a commonPrefix as the common prefix,
2686   but normally you let it figure it out itself by looking at
2687   the name of the first and last classes in the class map
2688   \a nmm.
2689  */
generateCompactList(ListType listType,const Node * relative,const NodeMultiMap & nmm,bool includeAlphabet,QString commonPrefix)2690 void HtmlGenerator::generateCompactList(ListType listType, const Node *relative,
2691                                         const NodeMultiMap &nmm, bool includeAlphabet,
2692                                         QString commonPrefix)
2693 {
2694     if (nmm.isEmpty())
2695         return;
2696 
2697     const int NumParagraphs = 37; // '0' to '9', 'A' to 'Z', '_'
2698     int commonPrefixLen = commonPrefix.length();
2699 
2700     /*
2701       Divide the data into 37 paragraphs: 0, ..., 9, A, ..., Z,
2702       underscore (_). QAccel will fall in paragraph 10 (A) and
2703       QXtWidget in paragraph 33 (X). This is the only place where we
2704       assume that NumParagraphs is 37. Each paragraph is a NodeMultiMap.
2705     */
2706     NodeMultiMap paragraph[NumParagraphs + 1];
2707     QString paragraphName[NumParagraphs + 1];
2708     QSet<char> usedParagraphNames;
2709 
2710     for (auto c = nmm.constBegin(); c != nmm.constEnd(); ++c) {
2711         QStringList pieces = c.key().split("::");
2712         int idx = commonPrefixLen;
2713         if (idx > 0 && !pieces.last().startsWith(commonPrefix, Qt::CaseInsensitive))
2714             idx = 0;
2715         QString last = pieces.last().toLower();
2716         QString key = last.mid(idx);
2717 
2718         int paragraphNr = NumParagraphs - 1;
2719 
2720         if (key[0].digitValue() != -1) {
2721             paragraphNr = key[0].digitValue();
2722         } else if (key[0] >= QLatin1Char('a') && key[0] <= QLatin1Char('z')) {
2723             paragraphNr = 10 + key[0].unicode() - 'a';
2724         }
2725 
2726         paragraphName[paragraphNr] = key[0].toUpper();
2727         usedParagraphNames.insert(key[0].toLower().cell());
2728         paragraph[paragraphNr].insert(last, c.value());
2729     }
2730 
2731     /*
2732       Each paragraph j has a size: paragraph[j].count(). In the
2733       discussion, we will assume paragraphs 0 to 5 will have sizes
2734       3, 1, 4, 1, 5, 9.
2735 
2736       We now want to compute the paragraph offset. Paragraphs 0 to 6
2737       start at offsets 0, 3, 4, 8, 9, 14, 23.
2738     */
2739     int paragraphOffset[NumParagraphs + 1]; // 37 + 1
2740     paragraphOffset[0] = 0;
2741     for (int i = 0; i < NumParagraphs; i++) // i = 0..36
2742         paragraphOffset[i + 1] = paragraphOffset[i] + paragraph[i].count();
2743 
2744     /*
2745       Output the alphabet as a row of links.
2746      */
2747     if (includeAlphabet) {
2748         out() << "<p  class=\"centerAlign functionIndex\"><b>";
2749         for (int i = 0; i < 26; i++) {
2750             QChar ch('a' + i);
2751             if (usedParagraphNames.contains(char('a' + i)))
2752                 out() << QString("<a href=\"#%1\">%2</a>&nbsp;").arg(ch).arg(ch.toUpper());
2753         }
2754         out() << "</b></p>\n";
2755     }
2756 
2757     /*
2758       Output a <div> element to contain all the <dl> elements.
2759      */
2760     out() << "<div class=\"flowListDiv\">\n";
2761     numTableRows_ = 0;
2762 
2763     int curParNr = 0;
2764     int curParOffset = 0;
2765     QString previousName;
2766     bool multipleOccurrences = false;
2767 
2768     for (int i = 0; i < nmm.count(); i++) {
2769         while ((curParNr < NumParagraphs) && (curParOffset == paragraph[curParNr].count())) {
2770             ++curParNr;
2771             curParOffset = 0;
2772         }
2773 
2774         /*
2775           Starting a new paragraph means starting a new <dl>.
2776         */
2777         if (curParOffset == 0) {
2778             if (i > 0)
2779                 out() << "</dl>\n";
2780             if (++numTableRows_ % 2 == 1)
2781                 out() << "<dl class=\"flowList odd\">";
2782             else
2783                 out() << "<dl class=\"flowList even\">";
2784             out() << "<dt class=\"alphaChar\">";
2785             if (includeAlphabet) {
2786                 QChar c = paragraphName[curParNr][0].toLower();
2787                 out() << QString("<a name=\"%1\"></a>").arg(c);
2788             }
2789             out() << "<b>" << paragraphName[curParNr] << "</b>";
2790             out() << "</dt>\n";
2791         }
2792 
2793         /*
2794           Output a <dd> for the current offset in the current paragraph.
2795          */
2796         out() << "<dd>";
2797         if ((curParNr < NumParagraphs) && !paragraphName[curParNr].isEmpty()) {
2798             NodeMultiMap::Iterator it;
2799             NodeMultiMap::Iterator next;
2800             it = paragraph[curParNr].begin();
2801             for (int i = 0; i < curParOffset; i++)
2802                 ++it;
2803 
2804             if (listType == Generic) {
2805                 /*
2806                   Previously, we used generateFullName() for this, but we
2807                   require some special formatting.
2808                 */
2809                 out() << "<a href=\"" << linkForNode(it.value(), relative) << "\">";
2810             } else if (listType == Obsolete) {
2811                 QString fileName = fileBase(it.value()) + "-obsolete." + fileExtension();
2812                 QString link;
2813                 if (useOutputSubdirs()) {
2814                     link = QString("../" + it.value()->outputSubdirectory() + QLatin1Char('/'));
2815                 }
2816                 link += fileName;
2817                 out() << "<a href=\"" << link << "\">";
2818             }
2819 
2820             QStringList pieces;
2821             if (it.value()->isQmlType() || it.value()->isJsType()) {
2822                 QString name = it.value()->name();
2823                 next = it;
2824                 ++next;
2825                 if (name != previousName)
2826                     multipleOccurrences = false;
2827                 if ((next != paragraph[curParNr].end()) && (name == next.value()->name())) {
2828                     multipleOccurrences = true;
2829                     previousName = name;
2830                 }
2831                 if (multipleOccurrences)
2832                     name += ": " + it.value()->tree()->camelCaseModuleName();
2833                 pieces << name;
2834             } else
2835                 pieces = it.value()->fullName(relative).split("::");
2836             out() << protectEnc(pieces.last());
2837             out() << "</a>";
2838             if (pieces.size() > 1) {
2839                 out() << " (";
2840                 generateFullName(it.value()->parent(), relative);
2841                 out() << ')';
2842             }
2843         }
2844         out() << "</dd>\n";
2845         curParOffset++;
2846     }
2847     if (nmm.count() > 0)
2848         out() << "</dl>\n";
2849 
2850     out() << "</div>\n";
2851 }
2852 
generateFunctionIndex(const Node * relative)2853 void HtmlGenerator::generateFunctionIndex(const Node *relative)
2854 {
2855     out() << "<p  class=\"centerAlign functionIndex\"><b>";
2856     for (int i = 0; i < 26; i++) {
2857         QChar ch('a' + i);
2858         out() << QString("<a href=\"#%1\">%2</a>&nbsp;").arg(ch).arg(ch.toUpper());
2859     }
2860     out() << "</b></p>\n";
2861 
2862     char nextLetter = 'a';
2863     char currentLetter;
2864 
2865     out() << "<ul>\n";
2866     NodeMapMap &funcIndex = qdb_->getFunctionIndex();
2867     for (auto fnMap = funcIndex.constBegin(); fnMap != funcIndex.constEnd(); ++fnMap) {
2868         out() << "<li>";
2869         out() << protectEnc(fnMap.key()) << ':';
2870 
2871         currentLetter = fnMap.key()[0].unicode();
2872         while (islower(currentLetter) && currentLetter >= nextLetter) {
2873             out() << QString("<a name=\"%1\"></a>").arg(nextLetter);
2874             nextLetter++;
2875         }
2876 
2877         for (auto it = (*fnMap).constBegin(); it != (*fnMap).constEnd(); ++it) {
2878             out() << ' ';
2879             generateFullName((*it)->parent(), relative, *it);
2880         }
2881         out() << "</li>";
2882         out() << '\n';
2883     }
2884     out() << "</ul>\n";
2885 }
2886 
generateLegaleseList(const Node * relative,CodeMarker * marker)2887 void HtmlGenerator::generateLegaleseList(const Node *relative, CodeMarker *marker)
2888 {
2889     TextToNodeMap &legaleseTexts = qdb_->getLegaleseTexts();
2890     QMap<Text, const Node *>::ConstIterator it = legaleseTexts.constBegin();
2891     while (it != legaleseTexts.constEnd()) {
2892         Text text = it.key();
2893         generateText(text, relative, marker);
2894         out() << "<ul>\n";
2895         do {
2896             out() << "<li>";
2897             generateFullName(it.value(), relative);
2898             out() << "</li>\n";
2899             ++it;
2900         } while (it != legaleseTexts.constEnd() && it.key() == text);
2901         out() << "</ul>\n";
2902     }
2903 }
2904 
generateQmlItem(const Node * node,const Node * relative,CodeMarker * marker,bool summary)2905 void HtmlGenerator::generateQmlItem(const Node *node, const Node *relative, CodeMarker *marker,
2906                                     bool summary)
2907 {
2908     QString marked = marker->markedUpQmlItem(node, summary);
2909     QRegExp templateTag("(<[^@>]*>)");
2910     if (marked.indexOf(templateTag) != -1) {
2911         QString contents = protectEnc(marked.mid(templateTag.pos(1), templateTag.cap(1).length()));
2912         marked.replace(templateTag.pos(1), templateTag.cap(1).length(), contents);
2913     }
2914 
2915     // Look for the _ character in the member name followed by a number (or n):
2916     // this is intended to be rendered as a subscript.
2917     marked.replace(QRegExp("<@param>([a-z]+)_([0-9]+|n)</@param>"), "<i>\\1<sub>\\2</sub></i>");
2918 
2919     // Replace some markup by HTML tags. Do both the opening and the closing tag
2920     // in one go (instead of <@param> and </@param> separately, for instance).
2921     marked.replace("@param>", "i>");
2922     if (summary)
2923         marked.replace("@name>", "b>");
2924     marked.replace("@extra>", "code>");
2925 
2926     if (summary) {
2927         marked.remove("<@type>");
2928         marked.remove("</@type>");
2929     }
2930     out() << highlightedCode(marked, relative, false, Node::QML);
2931 }
2932 
2933 /*!
2934   This function generates a simple bullet list for the members
2935   of collection node \a {cn}. The collection node must be a group
2936   and must not be empty. If it is empty, nothing is output, and
2937   false is returned. Otherewise, the list is generated and true is returned.
2938  */
generateGroupList(CollectionNode * cn)2939 bool HtmlGenerator::generateGroupList(CollectionNode *cn)
2940 {
2941     qdb_->mergeCollections(cn);
2942     if (cn->members().isEmpty())
2943         return false;
2944     out() << "<ul>\n";
2945     const auto members = cn->members();
2946     for (const auto *node : members) {
2947         out() << "<li>"
2948               << "<a href=\"#" << Doc::canonicalTitle(node->title()) << "\">" << node->title()
2949               << "</a></li>\n";
2950     }
2951     out() << "</ul>\n";
2952     return true;
2953 }
2954 
generateList(const Node * relative,CodeMarker * marker,const QString & selector)2955 void HtmlGenerator::generateList(const Node *relative, CodeMarker *marker, const QString &selector)
2956 {
2957     CNMap cnm;
2958     Node::NodeType type = Node::NoType;
2959     if (selector == QLatin1String("overviews"))
2960         type = Node::Group;
2961     else if (selector == QLatin1String("cpp-modules"))
2962         type = Node::Module;
2963     else if (selector == QLatin1String("qml-modules"))
2964         type = Node::QmlModule;
2965     else if (selector == QLatin1String("js-modules"))
2966         type = Node::JsModule;
2967     if (type != Node::NoType) {
2968         NodeList nodeList;
2969         qdb_->mergeCollections(type, cnm, relative);
2970         const auto collectionList = cnm.values();
2971         nodeList.reserve(collectionList.size());
2972         for (auto *collectionNode : collectionList)
2973             nodeList.append(collectionNode);
2974         generateAnnotatedList(relative, marker, nodeList);
2975     } else {
2976         /*
2977           \generatelist {selector} is only allowed in a
2978           comment where the topic is \group, \module,
2979           \qmlmodule, or \jsmodule
2980         */
2981         if (relative && !relative->isCollectionNode()) {
2982             relative->doc().location().warning(tr("\\generatelist {%1} is only allowed in \\group, "
2983                                                   "\\module, \\qmlmodule, and \\jsmodule comments.")
2984                                                        .arg(selector));
2985             return;
2986         }
2987         Node *n = const_cast<Node *>(relative);
2988         CollectionNode *cn = static_cast<CollectionNode *>(n);
2989         qdb_->mergeCollections(cn);
2990         generateAnnotatedList(cn, marker, cn->members());
2991     }
2992 }
2993 
generateSection(const NodeVector & nv,const Node * relative,CodeMarker * marker)2994 void HtmlGenerator::generateSection(const NodeVector &nv, const Node *relative, CodeMarker *marker)
2995 {
2996     bool alignNames = true;
2997     if (!nv.isEmpty()) {
2998         bool twoColumn = false;
2999         if (nv.first()->isProperty()) {
3000             twoColumn = (nv.count() >= 5);
3001             alignNames = false;
3002         }
3003         if (alignNames) {
3004             out() << "<div class=\"table\"><table class=\"alignedsummary\">\n";
3005         } else {
3006             if (twoColumn)
3007                 out() << "<div class=\"table\"><table class=\"propsummary\">\n"
3008                       << "<tr><td class=\"topAlign\">";
3009             out() << "<ul>\n";
3010         }
3011 
3012         int i = 0;
3013         for (const auto &member : nv) {
3014             if (member->access() == Node::Private)
3015                 continue;
3016 
3017             if (alignNames) {
3018                 out() << "<tr><td class=\"memItemLeft rightAlign topAlign\"> ";
3019             } else {
3020                 if (twoColumn && i == (nv.count() + 1) / 2)
3021                     out() << "</ul></td><td class=\"topAlign\"><ul>\n";
3022                 out() << "<li class=\"fn\">";
3023             }
3024 
3025             generateSynopsis(member, relative, marker, Section::Summary, alignNames);
3026             if (alignNames)
3027                 out() << "</td></tr>\n";
3028             else
3029                 out() << "</li>\n";
3030             i++;
3031         }
3032         if (alignNames)
3033             out() << "</table></div>\n";
3034         else {
3035             out() << "</ul>\n";
3036             if (twoColumn)
3037                 out() << "</td></tr>\n</table></div>\n";
3038         }
3039     }
3040 }
3041 
generateSectionList(const Section & section,const Node * relative,CodeMarker * marker,Section::Status status)3042 void HtmlGenerator::generateSectionList(const Section &section, const Node *relative,
3043                                         CodeMarker *marker, Section::Status status)
3044 {
3045     bool alignNames = true;
3046     const NodeVector &members =
3047             (status == Section::Obsolete ? section.obsoleteMembers() : section.members());
3048     if (!members.isEmpty()) {
3049         bool hasPrivateSignals = false;
3050         bool isInvokable = false;
3051         bool twoColumn = false;
3052         if (section.style() == Section::AllMembers) {
3053             alignNames = false;
3054             twoColumn = (members.count() >= 16);
3055         } else if (members.first()->isProperty()) {
3056             twoColumn = (members.count() >= 5);
3057             alignNames = false;
3058         }
3059         if (alignNames) {
3060             out() << "<div class=\"table\"><table class=\"alignedsummary\">\n";
3061         } else {
3062             if (twoColumn)
3063                 out() << "<div class=\"table\"><table class=\"propsummary\">\n"
3064                       << "<tr><td class=\"topAlign\">";
3065             out() << "<ul>\n";
3066         }
3067 
3068         int i = 0;
3069         for (const auto &member : members) {
3070             if (member->access() == Node::Private)
3071                 continue;
3072 
3073             if (alignNames) {
3074                 out() << "<tr><td class=\"memItemLeft topAlign rightAlign\"> ";
3075             } else {
3076                 if (twoColumn && i == (members.count() + 1) / 2)
3077                     out() << "</ul></td><td class=\"topAlign\"><ul>\n";
3078                 out() << "<li class=\"fn\">";
3079             }
3080 
3081             QString prefix;
3082             const QStringList &keys = section.keys(status);
3083             if (!keys.isEmpty()) {
3084                 prefix = keys.at(i).mid(1);
3085                 prefix = prefix.left(keys.at(i).indexOf("::") + 1);
3086             }
3087             generateSynopsis(member, relative, marker, section.style(), alignNames, &prefix);
3088             if (member->isFunction()) {
3089                 const FunctionNode *fn = static_cast<const FunctionNode *>(member);
3090                 if (fn->isPrivateSignal()) {
3091                     hasPrivateSignals = true;
3092                     if (alignNames)
3093                         out() << "</td><td class=\"memItemRight bottomAlign\">[see note below]";
3094                 } else if (fn->isInvokable()) {
3095                     isInvokable = true;
3096                     if (alignNames)
3097                         out() << "</td><td class=\"memItemRight bottomAlign\">[see note below]";
3098                 }
3099             }
3100             if (alignNames)
3101                 out() << "</td></tr>\n";
3102             else
3103                 out() << "</li>\n";
3104             i++;
3105         }
3106         if (alignNames)
3107             out() << "</table></div>\n";
3108         else {
3109             out() << "</ul>\n";
3110             if (twoColumn)
3111                 out() << "</td></tr>\n</table></div>\n";
3112         }
3113         if (alignNames) {
3114             if (hasPrivateSignals)
3115                 generateAddendum(relative, Generator::PrivateSignal, marker);
3116             if (isInvokable)
3117                 generateAddendum(relative, Generator::Invokable, marker);
3118         }
3119     }
3120 
3121     if (status != Section::Obsolete && section.style() == Section::Summary
3122         && !section.inheritedMembers().isEmpty()) {
3123         out() << "<ul>\n";
3124         generateSectionInheritedList(section, relative);
3125         out() << "</ul>\n";
3126     }
3127 }
3128 
generateSectionInheritedList(const Section & section,const Node * relative)3129 void HtmlGenerator::generateSectionInheritedList(const Section &section, const Node *relative)
3130 {
3131     const QVector<QPair<Aggregate *, int>> &inheritedMembers = section.inheritedMembers();
3132     for (const auto &member : inheritedMembers) {
3133         out() << "<li class=\"fn\">";
3134         out() << member.second << ' ';
3135         if (member.second == 1) {
3136             out() << section.singular();
3137         } else {
3138             out() << section.plural();
3139         }
3140         out() << " inherited from <a href=\"" << fileName(member.first) << '#'
3141               << Generator::cleanRef(section.title().toLower()) << "\">"
3142               << protectEnc(member.first->plainFullName(relative)) << "</a></li>\n";
3143     }
3144 }
3145 
generateSynopsis(const Node * node,const Node * relative,CodeMarker * marker,Section::Style style,bool alignNames,const QString * prefix)3146 void HtmlGenerator::generateSynopsis(const Node *node, const Node *relative, CodeMarker *marker,
3147                                      Section::Style style, bool alignNames, const QString *prefix)
3148 {
3149     QString marked = marker->markedUpSynopsis(node, relative, style);
3150 
3151     if (prefix)
3152         marked.prepend(*prefix);
3153     QRegExp templateTag("(<[^@>]*>)");
3154     if (marked.indexOf(templateTag) != -1) {
3155         QString contents = protectEnc(marked.mid(templateTag.pos(1), templateTag.cap(1).length()));
3156         marked.replace(templateTag.pos(1), templateTag.cap(1).length(), contents);
3157     }
3158     marked.replace(QRegExp("<@param>([a-z]+)_([1-9n])</@param>"), "<i>\\1<sub>\\2</sub></i>");
3159     marked.replace("<@param>", "<i>");
3160     marked.replace("</@param>", "</i>");
3161 
3162     if (style == Section::Summary) {
3163         marked.remove("<@name>"); // was "<b>"
3164         marked.remove("</@name>"); // was "</b>"
3165     }
3166 
3167     if (style == Section::AllMembers) {
3168         QRegExp extraRegExp("<@extra>.*</@extra>");
3169         extraRegExp.setMinimal(true);
3170         marked.remove(extraRegExp);
3171     } else {
3172         marked.replace("<@extra>", "<code>");
3173         marked.replace("</@extra>", "</code>");
3174     }
3175 
3176     if (style != Section::Details) {
3177         marked.remove("<@type>");
3178         marked.remove("</@type>");
3179     }
3180 
3181     out() << highlightedCode(marked, relative, alignNames);
3182 }
3183 
highlightedCode(const QString & markedCode,const Node * relative,bool alignNames,Node::Genus genus)3184 QString HtmlGenerator::highlightedCode(const QString &markedCode, const Node *relative,
3185                                        bool alignNames, Node::Genus genus)
3186 {
3187     QString src = markedCode;
3188     QString html;
3189     html.reserve(src.size());
3190     QStringRef arg;
3191     QStringRef par1;
3192 
3193     const QChar charLangle = '<';
3194     const QChar charAt = '@';
3195 
3196     static const QString typeTag("type");
3197     static const QString headerTag("headerfile");
3198     static const QString funcTag("func");
3199     static const QString linkTag("link");
3200 
3201     // replace all <@link> tags: "(<@link node=\"([^\"]+)\">).*(</@link>)"
3202     // replace all <@func> tags: "(<@func target=\"([^\"]*)\">)(.*)(</@func>)"
3203     // replace all "(<@(type|headerfile)(?: +[^>]*)?>)(.*)(</@\\2>)" tags
3204     bool done = false;
3205     for (int i = 0, srcSize = src.size(); i < srcSize;) {
3206         if (src.at(i) == charLangle && src.at(i + 1) == charAt) {
3207             if (alignNames && !done) {
3208                 html += QLatin1String("</td><td class=\"memItemRight bottomAlign\">");
3209                 done = true;
3210             }
3211             i += 2;
3212             if (parseArg(src, linkTag, &i, srcSize, &arg, &par1)) {
3213                 html += QLatin1String("<b>");
3214                 const Node *n = CodeMarker::nodeForString(par1.toString());
3215                 QString link = linkForNode(n, relative);
3216                 addLink(link, arg, &html);
3217                 html += QLatin1String("</b>");
3218             } else if (parseArg(src, funcTag, &i, srcSize, &arg, &par1)) {
3219                 const FunctionNode *fn = qdb_->findFunctionNode(par1.toString(), relative, genus);
3220                 QString link = linkForNode(fn, relative);
3221                 addLink(link, arg, &html);
3222                 par1 = QStringRef();
3223             } else if (parseArg(src, typeTag, &i, srcSize, &arg, &par1)) {
3224                 par1 = QStringRef();
3225                 const Node *n = qdb_->findTypeNode(arg.toString(), relative, genus);
3226                 html += QLatin1String("<span class=\"type\">");
3227                 if (n && (n->isQmlBasicType() || n->isJsBasicType())) {
3228                     if (relative && (relative->genus() == n->genus() || genus == n->genus()))
3229                         addLink(linkForNode(n, relative), arg, &html);
3230                     else
3231                         html += arg;
3232                 } else
3233                     addLink(linkForNode(n, relative), arg, &html);
3234                 html += QLatin1String("</span>");
3235             } else if (parseArg(src, headerTag, &i, srcSize, &arg, &par1)) {
3236                 par1 = QStringRef();
3237                 if (arg.startsWith(QLatin1Char('&')))
3238                     html += arg;
3239                 else {
3240                     const Node *n = qdb_->findNodeForInclude(QStringList(arg.toString()));
3241                     if (n && n != relative)
3242                         addLink(linkForNode(n, relative), arg, &html);
3243                     else
3244                         html += arg;
3245                 }
3246             } else {
3247                 html += charLangle;
3248                 html += charAt;
3249             }
3250         } else {
3251             html += src.at(i++);
3252         }
3253     }
3254 
3255     // replace all
3256     // "<@comment>" -> "<span class=\"comment\">";
3257     // "<@preprocessor>" -> "<span class=\"preprocessor\">";
3258     // "<@string>" -> "<span class=\"string\">";
3259     // "<@char>" -> "<span class=\"char\">";
3260     // "<@number>" -> "<span class=\"number\">";
3261     // "<@op>" -> "<span class=\"operator\">";
3262     // "<@type>" -> "<span class=\"type\">";
3263     // "<@name>" -> "<span class=\"name\">";
3264     // "<@keyword>" -> "<span class=\"keyword\">";
3265     // "</@(?:comment|preprocessor|string|char|number|op|type|name|keyword)>" -> "</span>"
3266     src = html;
3267     html = QString();
3268     html.reserve(src.size());
3269     static const QLatin1String spanTags[] = {
3270         QLatin1String("comment>"),      QLatin1String("<span class=\"comment\">"),
3271         QLatin1String("preprocessor>"), QLatin1String("<span class=\"preprocessor\">"),
3272         QLatin1String("string>"),       QLatin1String("<span class=\"string\">"),
3273         QLatin1String("char>"),         QLatin1String("<span class=\"char\">"),
3274         QLatin1String("number>"),       QLatin1String("<span class=\"number\">"),
3275         QLatin1String("op>"),           QLatin1String("<span class=\"operator\">"),
3276         QLatin1String("type>"),         QLatin1String("<span class=\"type\">"),
3277         QLatin1String("name>"),         QLatin1String("<span class=\"name\">"),
3278         QLatin1String("keyword>"),      QLatin1String("<span class=\"keyword\">")
3279     };
3280     int nTags = 9;
3281     // Update the upper bound of k in the following code to match the length
3282     // of the above array.
3283     for (int i = 0, n = src.size(); i < n;) {
3284         if (src.at(i) == QLatin1Char('<')) {
3285             if (src.at(i + 1) == QLatin1Char('@')) {
3286                 i += 2;
3287                 bool handled = false;
3288                 for (int k = 0; k != nTags; ++k) {
3289                     const QLatin1String &tag = spanTags[2 * k];
3290                     if (i + tag.size() <= src.length() && tag == QStringRef(&src, i, tag.size())) {
3291                         html += spanTags[2 * k + 1];
3292                         i += tag.size();
3293                         handled = true;
3294                         break;
3295                     }
3296                 }
3297                 if (!handled) {
3298                     // drop 'our' unknown tags (the ones still containing '@')
3299                     while (i < n && src.at(i) != QLatin1Char('>'))
3300                         ++i;
3301                     ++i;
3302                 }
3303                 continue;
3304             } else if (src.at(i + 1) == QLatin1Char('/') && src.at(i + 2) == QLatin1Char('@')) {
3305                 i += 3;
3306                 bool handled = false;
3307                 for (int k = 0; k != nTags; ++k) {
3308                     const QLatin1String &tag = spanTags[2 * k];
3309                     if (i + tag.size() <= src.length() && tag == QStringRef(&src, i, tag.size())) {
3310                         html += QLatin1String("</span>");
3311                         i += tag.size();
3312                         handled = true;
3313                         break;
3314                     }
3315                 }
3316                 if (!handled) {
3317                     // drop 'our' unknown tags (the ones still containing '@')
3318                     while (i < n && src.at(i) != QLatin1Char('>'))
3319                         ++i;
3320                     ++i;
3321                 }
3322                 continue;
3323             }
3324         }
3325         html += src.at(i);
3326         ++i;
3327     }
3328     return html;
3329 }
3330 
generateLink(const Atom * atom,CodeMarker * marker)3331 void HtmlGenerator::generateLink(const Atom *atom, CodeMarker *marker)
3332 {
3333     static QRegExp camelCase("[A-Z][A-Z][a-z]|[a-z][A-Z0-9]|_");
3334 
3335     if (funcLeftParen.indexIn(atom->string()) != -1 && marker->recognizeLanguage("Cpp")) {
3336         // hack for C++: move () outside of link
3337         int k = funcLeftParen.pos(1);
3338         out() << protectEnc(atom->string().left(k));
3339         if (link_.isEmpty()) {
3340             if (showBrokenLinks)
3341                 out() << "</i>";
3342         } else {
3343             out() << "</a>";
3344         }
3345         inLink_ = false;
3346         out() << protectEnc(atom->string().mid(k));
3347     } else {
3348         out() << protectEnc(atom->string());
3349     }
3350 }
3351 
protectEnc(const QString & string)3352 QString HtmlGenerator::protectEnc(const QString &string)
3353 {
3354 #ifndef QT_NO_TEXTCODEC
3355     return protect(string, outputEncoding);
3356 #else
3357     return protect(string);
3358 #endif
3359 }
3360 
protect(const QString & string,const QString & outputEncoding)3361 QString HtmlGenerator::protect(const QString &string, const QString &outputEncoding)
3362 {
3363 #define APPEND(x)                                                                                  \
3364     if (html.isEmpty()) {                                                                          \
3365         html = string;                                                                             \
3366         html.truncate(i);                                                                          \
3367     }                                                                                              \
3368     html += (x);
3369 
3370     QString html;
3371     int n = string.length();
3372 
3373     for (int i = 0; i < n; ++i) {
3374         QChar ch = string.at(i);
3375 
3376         if (ch == QLatin1Char('&')) {
3377             APPEND("&amp;");
3378         } else if (ch == QLatin1Char('<')) {
3379             APPEND("&lt;");
3380         } else if (ch == QLatin1Char('>')) {
3381             APPEND("&gt;");
3382         } else if (ch == QLatin1Char('"')) {
3383             APPEND("&quot;");
3384         } else if ((outputEncoding == QLatin1String("ISO-8859-1") && ch.unicode() > 0x007F)
3385                    || (ch == QLatin1Char('*') && i + 1 < n && string.at(i) == QLatin1Char('/'))
3386                    || (ch == QLatin1Char('.') && i > 2 && string.at(i - 2) == QLatin1Char('.'))) {
3387             // we escape '*/' and the last dot in 'e.g.' and 'i.e.' for the Javadoc generator
3388             APPEND("&#x");
3389             html += QString::number(ch.unicode(), 16);
3390             html += QLatin1Char(';');
3391         } else {
3392             if (!html.isEmpty())
3393                 html += ch;
3394         }
3395     }
3396 
3397     if (!html.isEmpty())
3398         return html;
3399     return string;
3400 
3401 #undef APPEND
3402 }
3403 
fileBase(const Node * node) const3404 QString HtmlGenerator::fileBase(const Node *node) const
3405 {
3406     QString result = Generator::fileBase(node);
3407     if (!node->isAggregate() && node->isObsolete())
3408         result += QLatin1String("-obsolete");
3409     return result;
3410 }
3411 
fileName(const Node * node)3412 QString HtmlGenerator::fileName(const Node *node)
3413 {
3414     if (node->isExternalPage())
3415         return node->name();
3416     return Generator::fileName(node);
3417 }
3418 
generateFullName(const Node * apparentNode,const Node * relative,const Node * actualNode)3419 void HtmlGenerator::generateFullName(const Node *apparentNode, const Node *relative,
3420                                      const Node *actualNode)
3421 {
3422     if (actualNode == nullptr)
3423         actualNode = apparentNode;
3424     out() << "<a href=\"" << linkForNode(actualNode, relative);
3425     if (actualNode->isObsolete())
3426         out() << "\" class=\"obsolete";
3427     out() << "\">";
3428     out() << protectEnc(apparentNode->fullName(relative));
3429     out() << "</a>";
3430 }
3431 
generateDetailedMember(const Node * node,const PageNode * relative,CodeMarker * marker)3432 void HtmlGenerator::generateDetailedMember(const Node *node, const PageNode *relative,
3433                                            CodeMarker *marker)
3434 {
3435     const EnumNode *etn;
3436 #ifdef GENERATE_MAC_REFS
3437     generateMacRef(node, marker);
3438 #endif
3439     generateExtractionMark(node, MemberMark);
3440     generateKeywordAnchors(node);
3441     QString nodeRef = nullptr;
3442     if (node->isSharedCommentNode()) {
3443         const SharedCommentNode *scn = reinterpret_cast<const SharedCommentNode *>(node);
3444         const QVector<Node *> &collective = scn->collective();
3445         if (collective.size() > 1)
3446             out() << "<div class=\"fngroup\">\n";
3447         for (const auto *node : collective) {
3448             nodeRef = refForNode(node);
3449             out() << "<h3 class=\"fn fngroupitem\" id=\"" << nodeRef << "\">";
3450             out() << "<a name=\"" + nodeRef + "\"></a>";
3451             generateSynopsis(node, relative, marker, Section::Details);
3452             out() << "</h3>";
3453         }
3454         if (collective.size() > 1)
3455             out() << "</div>";
3456         out() << divNavTop << '\n';
3457     } else {
3458         nodeRef = refForNode(node);
3459         if (node->isEnumType() && (etn = static_cast<const EnumNode *>(node))->flagsType()) {
3460 #ifdef GENERATE_MAC_REFS
3461             generateMacRef(etn->flagsType(), marker);
3462 #endif
3463             out() << "<h3 class=\"flags\" id=\"" << nodeRef << "\">";
3464             out() << "<a name=\"" + nodeRef + "\"></a>";
3465             generateSynopsis(etn, relative, marker, Section::Details);
3466             out() << "<br/>";
3467             generateSynopsis(etn->flagsType(), relative, marker, Section::Details);
3468             out() << "</h3>\n";
3469         } else {
3470             out() << "<h3 class=\"fn\" id=\"" << nodeRef << "\">";
3471             out() << "<a name=\"" + nodeRef + "\"></a>";
3472             generateSynopsis(node, relative, marker, Section::Details);
3473             out() << "</h3>" << divNavTop << '\n';
3474         }
3475     }
3476 
3477     generateStatus(node, marker);
3478     generateBody(node, marker);
3479     generateOverloadedSignal(node, marker);
3480     generateThreadSafeness(node, marker);
3481     generateSince(node, marker);
3482 
3483     if (node->isProperty()) {
3484         const auto property = static_cast<const PropertyNode *>(node);
3485         Section section(Section::Accessors, Section::Active);
3486 
3487         section.appendMembers(property->getters().toVector());
3488         section.appendMembers(property->setters().toVector());
3489         section.appendMembers(property->resetters().toVector());
3490 
3491         if (!section.members().isEmpty()) {
3492             out() << "<p><b>Access functions:</b></p>\n";
3493             generateSectionList(section, node, marker);
3494         }
3495 
3496         Section notifiers(Section::Accessors, Section::Active);
3497         notifiers.appendMembers(property->notifiers().toVector());
3498 
3499         if (!notifiers.members().isEmpty()) {
3500             out() << "<p><b>Notifier signal:</b></p>\n";
3501             generateSectionList(notifiers, node, marker);
3502         }
3503     } else if (node->isEnumType()) {
3504         const EnumNode *etn = static_cast<const EnumNode *>(node);
3505         if (etn->flagsType()) {
3506             out() << "<p>The " << protectEnc(etn->flagsType()->name()) << " type is a typedef for "
3507                   << "<a href=\"" << qflagsHref_ << "\">QFlags</a>&lt;" << protectEnc(etn->name())
3508                   << "&gt;. It stores an OR combination of " << protectEnc(etn->name())
3509                   << " values.</p>\n";
3510         }
3511     }
3512     generateAlsoList(node, marker);
3513     generateExtractionMark(node, EndMark);
3514 }
3515 
3516 #ifdef GENERATE_MAC_REFS
3517 /*
3518   No longer valid.
3519  */
generateMacRef(const Node * node,CodeMarker * marker)3520 void HtmlGenerator::generateMacRef(const Node *node, CodeMarker *marker)
3521 {
3522     if (!pleaseGenerateMacRef || marker == 0)
3523         return;
3524 
3525     const QStringList macRefs = marker->macRefsForNode(node);
3526     for (const auto &macRef : macRefs)
3527         out() << "<a name=\""
3528               << "//apple_ref/" << macRef << "\"></a>\n";
3529 }
3530 #endif
3531 
3532 /*!
3533   This version of the function is called when outputting the link
3534   to an example file or example image, where the \a link is known
3535   to be correct.
3536  */
beginLink(const QString & link)3537 void HtmlGenerator::beginLink(const QString &link)
3538 {
3539     link_ = link;
3540     if (link_.isEmpty()) {
3541         if (showBrokenLinks)
3542             out() << "<i>";
3543     }
3544     out() << "<a href=\"" << link_ << "\">";
3545     inLink_ = true;
3546 }
3547 
beginLink(const QString & link,const Node * node,const Node * relative)3548 void HtmlGenerator::beginLink(const QString &link, const Node *node, const Node *relative)
3549 {
3550     link_ = link;
3551     if (link_.isEmpty()) {
3552         if (showBrokenLinks)
3553             out() << "<i>";
3554     } else if (node == nullptr || (relative != nullptr && node->status() == relative->status()))
3555         out() << "<a href=\"" << link_ << "\">";
3556     else if (node->isObsolete())
3557         out() << "<a href=\"" << link_ << "\" class=\"obsolete\">";
3558     else
3559         out() << "<a href=\"" << link_ << "\">";
3560     inLink_ = true;
3561 }
3562 
endLink()3563 void HtmlGenerator::endLink()
3564 {
3565     if (inLink_) {
3566         if (link_.isEmpty()) {
3567             if (showBrokenLinks)
3568                 out() << "</i>";
3569         } else {
3570             if (inObsoleteLink) {
3571                 out() << "<sup>(obsolete)</sup>";
3572             }
3573             out() << "</a>";
3574         }
3575     }
3576     inLink_ = false;
3577     inObsoleteLink = false;
3578 }
3579 
3580 /*!
3581   Generates the summary list for the \a members. Only used for
3582   sections of QML element documentation.
3583  */
generateQmlSummary(const NodeVector & members,const Node * relative,CodeMarker * marker)3584 void HtmlGenerator::generateQmlSummary(const NodeVector &members, const Node *relative,
3585                                        CodeMarker *marker)
3586 {
3587     if (!members.isEmpty()) {
3588         out() << "<ul>\n";
3589         for (const auto &member : members) {
3590             out() << "<li class=\"fn\">";
3591             generateQmlItem(member, relative, marker, true);
3592             if (member->isPropertyGroup()) {
3593                 const SharedCommentNode *scn = static_cast<const SharedCommentNode *>(member);
3594                 if (scn->count() > 0) {
3595                     out() << "<ul>\n";
3596                     const QVector<Node *> sharedNodes = scn->collective();
3597                     for (const auto &node : sharedNodes) {
3598                         if (node->isQmlProperty() || node->isJsProperty()) {
3599                             out() << "<li class=\"fn\">";
3600                             generateQmlItem(node, relative, marker, true);
3601                             out() << "</li>\n";
3602                         }
3603                     }
3604                     out() << "</ul>\n";
3605                 }
3606             }
3607             out() << "</li>\n";
3608         }
3609         out() << "</ul>\n";
3610     }
3611 }
3612 
3613 /*!
3614   Outputs the html detailed documentation for a section
3615   on a QML element reference page.
3616  */
generateDetailedQmlMember(Node * node,const Aggregate * relative,CodeMarker * marker)3617 void HtmlGenerator::generateDetailedQmlMember(Node *node, const Aggregate *relative,
3618                                               CodeMarker *marker)
3619 {
3620 #ifdef GENERATE_MAC_REFS
3621     generateMacRef(node, marker);
3622 #endif
3623     generateExtractionMark(node, MemberMark);
3624     generateKeywordAnchors(node);
3625 
3626     QString qmlItemHeader("<div class=\"qmlproto\">\n"
3627                           "<div class=\"table\"><table class=\"qmlname\">\n");
3628 
3629     QString qmlItemStart("<tr valign=\"top\" class=\"odd\" id=\"%1\">\n"
3630                          "<td class=\"%2\"><p>\n<a name=\"%1\"></a>");
3631     QString qmlItemEnd("</p></td></tr>\n");
3632 
3633     QString qmlItemFooter("</table></div></div>\n");
3634 
3635     std::function<void(QmlPropertyNode *)> generateQmlProperty = [&](QmlPropertyNode *n) {
3636         out() << qmlItemStart.arg(refForNode(n), "tblQmlPropNode");
3637 
3638         if (!n->isReadOnlySet() && n->declarativeCppNode())
3639             n->markReadOnly(!n->isWritable());
3640 
3641         if (n->isReadOnly())
3642             out() << "<span class=\"qmlreadonly\">[read-only] </span>";
3643         if (n->isDefault())
3644             out() << "<span class=\"qmldefault\">[default] </span>";
3645 
3646         generateQmlItem(n, relative, marker, false);
3647         out() << qmlItemEnd;
3648     };
3649 
3650     std::function<void(Node *)> generateQmlMethod = [&](Node *n) {
3651         out() << qmlItemStart.arg(refForNode(n), "tblQmlFuncNode");
3652         generateSynopsis(n, relative, marker, Section::Details, false);
3653         out() << qmlItemEnd;
3654     };
3655 
3656     out() << "<div class=\"qmlitem\">";
3657     if (node->isPropertyGroup()) {
3658         const SharedCommentNode *scn = static_cast<const SharedCommentNode *>(node);
3659         out() << qmlItemHeader;
3660         if (!scn->name().isEmpty()) {
3661             const QString nodeRef = refForNode(scn);
3662             out() << "<tr valign=\"top\" class=\"even\" id=\"" << nodeRef << "\">";
3663             out() << "<th class=\"centerAlign\"><p>";
3664             out() << "<a name=\"" + nodeRef + "\"></a>";
3665             out() << "<b>" << scn->name() << " group</b>";
3666             out() << "</p></th></tr>\n";
3667         }
3668         const QVector<Node *> sharedNodes = scn->collective();
3669         for (const auto &node : sharedNodes) {
3670             if (node->isQmlProperty() || node->isJsProperty())
3671                 generateQmlProperty(static_cast<QmlPropertyNode *>(node));
3672         }
3673         out() << qmlItemFooter;
3674     } else if (node->isQmlProperty() || node->isJsProperty()) {
3675         out() << qmlItemHeader;
3676         generateQmlProperty(static_cast<QmlPropertyNode *>(node));
3677         out() << qmlItemFooter;
3678     } else if (node->isSharedCommentNode()) {
3679         const SharedCommentNode *scn = reinterpret_cast<const SharedCommentNode *>(node);
3680         const QVector<Node *> &sharedNodes = scn->collective();
3681         if (sharedNodes.size() > 1)
3682             out() << "<div class=\"fngroup\">\n";
3683         out() << qmlItemHeader;
3684         for (const auto &node : sharedNodes) {
3685             if (node->isFunction(Node::QML) || node->isFunction(Node::JS))
3686                 generateQmlMethod(node);
3687             else if (node->isQmlProperty() || node->isJsProperty())
3688                 generateQmlProperty(static_cast<QmlPropertyNode *>(node));
3689         }
3690         out() << qmlItemFooter;
3691         if (sharedNodes.size() > 1)
3692             out() << "</div>"; // fngroup
3693     } else { // assume the node is a method/signal handler
3694         out() << qmlItemHeader;
3695         generateQmlMethod(node);
3696         out() << qmlItemFooter;
3697     }
3698 
3699     out() << "<div class=\"qmldoc\">";
3700     generateStatus(node, marker);
3701     generateBody(node, marker);
3702     generateThreadSafeness(node, marker);
3703     generateSince(node, marker);
3704     generateAlsoList(node, marker);
3705     out() << "</div></div>";
3706     generateExtractionMark(node, EndMark);
3707 }
3708 
3709 /*!
3710   Output the "Inherits" line for the QML element,
3711   if there should be one.
3712  */
generateQmlInherits(QmlTypeNode * qcn,CodeMarker * marker)3713 void HtmlGenerator::generateQmlInherits(QmlTypeNode *qcn, CodeMarker *marker)
3714 {
3715     if (!qcn)
3716         return;
3717     QmlTypeNode *base = qcn->qmlBaseNode();
3718     while (base && base->isInternal()) {
3719         base = base->qmlBaseNode();
3720     }
3721     if (base) {
3722         Text text;
3723         text << Atom::ParaLeft << "Inherits ";
3724         text << Atom(Atom::LinkNode, CodeMarker::stringForNode(base));
3725         text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
3726         text << Atom(Atom::String, base->name());
3727         text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
3728         text << Atom::ParaRight;
3729         generateText(text, qcn, marker);
3730     }
3731 }
3732 
3733 /*!
3734   Output the "[Xxx instantiates the C++ class QmlGraphicsXxx]"
3735   line for the QML element, if there should be one.
3736 
3737   If there is no class node, or if the class node status
3738   is set to Node::Internal, do nothing.
3739  */
generateQmlInstantiates(QmlTypeNode * qcn,CodeMarker * marker)3740 void HtmlGenerator::generateQmlInstantiates(QmlTypeNode *qcn, CodeMarker *marker)
3741 {
3742     ClassNode *cn = qcn->classNode();
3743     if (cn && !cn->isInternal()) {
3744         Text text;
3745         text << Atom::ParaLeft;
3746         text << Atom(Atom::LinkNode, CodeMarker::stringForNode(qcn));
3747         text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
3748         QString name = qcn->name();
3749         /*
3750           Remove the "QML:" prefix, if present.
3751           It shouldn't be present anymore.
3752         */
3753         if (name.startsWith(QLatin1String("QML:")))
3754             name = name.mid(4); // remove the "QML:" prefix
3755         text << Atom(Atom::String, name);
3756         text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
3757         text << " instantiates the C++ class ";
3758         text << Atom(Atom::LinkNode, CodeMarker::stringForNode(cn));
3759         text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
3760         text << Atom(Atom::String, cn->name());
3761         text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
3762         text << Atom::ParaRight;
3763         generateText(text, qcn, marker);
3764     }
3765 }
3766 
3767 /*!
3768   Output the "[QmlGraphicsXxx is instantiated by QML Type Xxx]"
3769   line for the class, if there should be one.
3770 
3771   If there is no QML element, or if the class node status
3772   is set to Node::Internal, do nothing.
3773  */
generateInstantiatedBy(ClassNode * cn,CodeMarker * marker)3774 void HtmlGenerator::generateInstantiatedBy(ClassNode *cn, CodeMarker *marker)
3775 {
3776     if (cn && !cn->isInternal() && cn->qmlElement() != nullptr) {
3777         const QmlTypeNode *qcn = cn->qmlElement();
3778         Text text;
3779         text << Atom::ParaLeft;
3780         text << Atom(Atom::LinkNode, CodeMarker::stringForNode(cn));
3781         text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
3782         text << Atom(Atom::String, cn->name());
3783         text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
3784         if (qcn->isQmlType())
3785             text << " is instantiated by QML Type ";
3786         else
3787             text << " is instantiated by Javascript Type ";
3788         text << Atom(Atom::LinkNode, CodeMarker::stringForNode(qcn));
3789         text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
3790         text << Atom(Atom::String, qcn->name());
3791         text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
3792         text << Atom::ParaRight;
3793         generateText(text, cn, marker);
3794     }
3795 }
3796 
generateExtractionMark(const Node * node,ExtractionMarkType markType)3797 void HtmlGenerator::generateExtractionMark(const Node *node, ExtractionMarkType markType)
3798 {
3799     if (markType != EndMark) {
3800         out() << "<!-- $$$" + node->name();
3801         if (markType == MemberMark) {
3802             if (node->isFunction()) {
3803                 const FunctionNode *func = static_cast<const FunctionNode *>(node);
3804                 if (!func->hasAssociatedProperties()) {
3805                     if (func->overloadNumber() == 0)
3806                         out() << "[overload1]";
3807                     out() << "$$$" + func->name() + func->parameters().rawSignature().remove(' ');
3808                 }
3809             } else if (node->isProperty()) {
3810                 out() << "-prop";
3811                 const PropertyNode *prop = static_cast<const PropertyNode *>(node);
3812                 const NodeList &list = prop->functions();
3813                 for (const auto *propFuncNode : list) {
3814                     if (propFuncNode->isFunction()) {
3815                         const FunctionNode *func = static_cast<const FunctionNode *>(propFuncNode);
3816                         out() << "$$$" + func->name()
3817                                         + func->parameters().rawSignature().remove(' ');
3818                     }
3819                 }
3820             } else if (node->isEnumType()) {
3821                 const EnumNode *enumNode = static_cast<const EnumNode *>(node);
3822                 const auto items = enumNode->items();
3823                 for (const auto &item : items)
3824                     out() << "$$$" + item.name();
3825             }
3826         } else if (markType == BriefMark) {
3827             out() << "-brief";
3828         } else if (markType == DetailedDescriptionMark) {
3829             out() << "-description";
3830         }
3831         out() << " -->\n";
3832     } else {
3833         out() << "<!-- @@@" + node->name() + " -->\n";
3834     }
3835 }
3836 
3837 /*!
3838   This function outputs one or more manifest files in XML.
3839   They are used by Creator.
3840  */
generateManifestFiles()3841 void HtmlGenerator::generateManifestFiles()
3842 {
3843     generateManifestFile("examples", "example");
3844     generateManifestFile("demos", "demo");
3845     qdb_->exampleNodeMap().clear();
3846     manifestMetaContent.clear();
3847 }
3848 
3849 /*!
3850   Retrieve the install path for the \a example as specified with
3851   the \meta command, or fall back to the one defined in .qdocconf.
3852  */
retrieveInstallPath(const ExampleNode * example)3853 QString HtmlGenerator::retrieveInstallPath(const ExampleNode *example)
3854 {
3855     QString installPath = example->doc().metaTagMap().value(QLatin1String("installpath"));
3856     if (installPath.isEmpty())
3857         installPath = examplesPath;
3858     if (!installPath.isEmpty() && !installPath.endsWith(QLatin1Char('/')))
3859         installPath += QLatin1Char('/');
3860 
3861     return installPath;
3862 }
3863 
3864 /*!
3865   This function is called by generateManifestFiles(), once
3866   for each manifest file to be generated. \a manifest is the
3867   type of manifest file.
3868  */
generateManifestFile(const QString & manifest,const QString & element)3869 void HtmlGenerator::generateManifestFile(const QString &manifest, const QString &element)
3870 {
3871     ExampleNodeMap &exampleNodeMap = qdb_->exampleNodeMap();
3872     if (exampleNodeMap.isEmpty())
3873         return;
3874     QString fileName = manifest + "-manifest.xml";
3875     QFile file(outputDir() + QLatin1Char('/') + fileName);
3876     bool demos = false;
3877     if (manifest == QLatin1String("demos"))
3878         demos = true;
3879 
3880     bool proceed = false;
3881     for (auto map = exampleNodeMap.begin(); map != exampleNodeMap.end(); ++map) {
3882         const ExampleNode *en = map.value();
3883         if (demos) {
3884             if (en->name().startsWith("demos")) {
3885                 proceed = true;
3886                 break;
3887             }
3888         } else if (!en->name().startsWith("demos")) {
3889             proceed = true;
3890             break;
3891         }
3892     }
3893     if (!proceed || !file.open(QFile::WriteOnly | QFile::Text))
3894         return;
3895 
3896     QXmlStreamWriter writer(&file);
3897     writer.setAutoFormatting(true);
3898     writer.writeStartDocument();
3899     writer.writeStartElement("instructionals");
3900     writer.writeAttribute("module", project);
3901     writer.writeStartElement(manifest);
3902 
3903     QStringList usedAttributes;
3904     for (auto map = exampleNodeMap.begin(); map != exampleNodeMap.end(); ++map) {
3905         const ExampleNode *en = map.value();
3906         if (demos) {
3907             if (!en->name().startsWith("demos"))
3908                 continue;
3909         } else if (en->name().startsWith("demos")) {
3910             continue;
3911         }
3912 
3913         const QString installPath = retrieveInstallPath(en);
3914         // attributes that are always written for the element
3915         usedAttributes.clear();
3916         usedAttributes << "name"
3917                        << "docUrl"
3918                        << "projectPath";
3919 
3920         writer.writeStartElement(element);
3921         writer.writeAttribute("name", en->title());
3922         QString docUrl = manifestDir + fileBase(en) + ".html";
3923         writer.writeAttribute("docUrl", docUrl);
3924         const auto exampleFiles = en->files();
3925         if (!en->projectFile().isEmpty())
3926             writer.writeAttribute("projectPath", installPath + en->projectFile());
3927         if (!en->imageFileName().isEmpty()) {
3928             writer.writeAttribute("imageUrl", manifestDir + en->imageFileName());
3929             usedAttributes << "imageUrl";
3930         }
3931 
3932         QString fullName = project + QLatin1Char('/') + en->title();
3933         QSet<QString> tags;
3934         for (const auto &index : manifestMetaContent) {
3935             const auto &names = index.names;
3936             for (const QString &name : names) {
3937                 bool match = false;
3938                 int wildcard = name.indexOf(QChar('*'));
3939                 switch (wildcard) {
3940                 case -1: // no wildcard, exact match
3941                     match = (fullName == name);
3942                     break;
3943                 case 0: // '*' matches all
3944                     match = true;
3945                     break;
3946                 default: // match with wildcard at the end
3947                     match = fullName.startsWith(name.left(wildcard));
3948                 }
3949                 if (match) {
3950                     tags += index.tags;
3951                     const auto attributes = index.attributes;
3952                     for (const QString &attr : attributes) {
3953                         QLatin1Char div(':');
3954                         QStringList attrList = attr.split(div);
3955                         if (attrList.count() == 1)
3956                             attrList.append(QStringLiteral("true"));
3957                         QString attrName = attrList.takeFirst();
3958                         if (!usedAttributes.contains(attrName)) {
3959                             writer.writeAttribute(attrName, attrList.join(div));
3960                             usedAttributes << attrName;
3961                         }
3962                     }
3963                 }
3964             }
3965         }
3966 
3967         writer.writeStartElement("description");
3968         Text brief = en->doc().briefText();
3969         if (!brief.isEmpty())
3970             writer.writeCDATA(brief.toString());
3971         else
3972             writer.writeCDATA(QString("No description available"));
3973         writer.writeEndElement(); // description
3974 
3975         // Add words from module name as tags
3976         // QtQuickControls -> qt,quick,controls
3977         // QtOpenGL -> qt,opengl
3978         QRegExp re("([A-Z]+[a-z0-9]*(3D|GL)?)");
3979         int pos = 0;
3980         while ((pos = re.indexIn(project, pos)) != -1) {
3981             tags << re.cap(1).toLower();
3982             pos += re.matchedLength();
3983         }
3984 
3985         // Include tags added via \meta {tag} {tag1[,tag2,...]}
3986         // within \example topic
3987         for (const auto &tag : en->doc().metaTagMap().values("tag")) {
3988             const auto &tagList = tag.toLower().split(QLatin1Char(','));
3989             tags += QSet<QString>(tagList.cbegin(), tagList.cend());
3990         }
3991 
3992         const auto &titleWords = en->title().toLower().split(QLatin1Char(' '));
3993         tags += QSet<QString>(titleWords.cbegin(), titleWords.cend());
3994 
3995         // Clean up tags, exclude invalid and common words
3996         QSet<QString>::iterator tag_it = tags.begin();
3997         QSet<QString> modified;
3998         while (tag_it != tags.end()) {
3999             QString s = *tag_it;
4000             if (s.at(0) == '(')
4001                 s.remove(0, 1).chop(1);
4002             if (s.endsWith(QLatin1Char(':')))
4003                 s.chop(1);
4004 
4005             if (s.length() < 2 || s.at(0).isDigit() || s.at(0) == '-' || s == QLatin1String("qt")
4006                 || s == QLatin1String("the") || s == QLatin1String("and")
4007                 || s.startsWith(QLatin1String("example")) || s.startsWith(QLatin1String("chapter")))
4008                 tag_it = tags.erase(tag_it);
4009             else if (s != *tag_it) {
4010                 modified << s;
4011                 tag_it = tags.erase(tag_it);
4012             } else
4013                 ++tag_it;
4014         }
4015         tags += modified;
4016 
4017         if (!tags.isEmpty()) {
4018             writer.writeStartElement("tags");
4019             bool wrote_one = false;
4020             QStringList sortedTags = tags.values();
4021             sortedTags.sort();
4022             for (const auto &tag : qAsConst(sortedTags)) {
4023                 if (wrote_one)
4024                     writer.writeCharacters(",");
4025                 writer.writeCharacters(tag);
4026                 wrote_one = true;
4027             }
4028             writer.writeEndElement(); // tags
4029         }
4030 
4031         QString ename = en->name().mid(en->name().lastIndexOf('/') + 1);
4032         QMap<int, QString> filesToOpen;
4033         const auto files = en->files();
4034         for (const QString &file : files) {
4035             QFileInfo fileInfo(file);
4036             QString fileName = fileInfo.fileName().toLower();
4037             // open .qml, .cpp and .h files with a
4038             // basename matching the example (project) name
4039             // QMap key indicates the priority -
4040             // the lowest value will be the top-most file
4041             if ((fileInfo.baseName().compare(ename, Qt::CaseInsensitive) == 0)) {
4042                 if (fileName.endsWith(".qml"))
4043                     filesToOpen.insert(0, file);
4044                 else if (fileName.endsWith(".cpp"))
4045                     filesToOpen.insert(1, file);
4046                 else if (fileName.endsWith(".h"))
4047                     filesToOpen.insert(2, file);
4048             }
4049             // main.qml takes precedence over main.cpp
4050             else if (fileName.endsWith("main.qml")) {
4051                 filesToOpen.insert(3, file);
4052             } else if (fileName.endsWith("main.cpp")) {
4053                 filesToOpen.insert(4, file);
4054             }
4055         }
4056 
4057         for (auto it = filesToOpen.constEnd(); it != filesToOpen.constBegin();) {
4058             writer.writeStartElement("fileToOpen");
4059             if (--it == filesToOpen.constBegin()) {
4060                 writer.writeAttribute(QStringLiteral("mainFile"), QStringLiteral("true"));
4061             }
4062             writer.writeCharacters(installPath + it.value());
4063             writer.writeEndElement();
4064         }
4065 
4066         writer.writeEndElement(); // example
4067     }
4068 
4069     writer.writeEndElement(); // examples
4070     writer.writeEndElement(); // instructionals
4071     writer.writeEndDocument();
4072     file.close();
4073 }
4074 
4075 /*!
4076   Reads metacontent - additional attributes and tags to apply
4077   when generating manifest files, read from config. Takes the
4078   configuration class \a config as a parameter.
4079 
4080   The manifest metacontent map is cleared immediately after
4081   the manifest files have been generated.
4082  */
readManifestMetaContent()4083 void HtmlGenerator::readManifestMetaContent()
4084 {
4085     Config &config = Config::instance();
4086     const QStringList names =
4087             config.getStringList(CONFIG_MANIFESTMETA + Config::dot + QStringLiteral("filters"));
4088 
4089     for (const auto &manifest : names) {
4090         ManifestMetaFilter filter;
4091         QString prefix = CONFIG_MANIFESTMETA + Config::dot + manifest + Config::dot;
4092         filter.names = config.getStringSet(prefix + QStringLiteral("names"));
4093         filter.attributes = config.getStringSet(prefix + QStringLiteral("attributes"));
4094         filter.tags = config.getStringSet(prefix + QStringLiteral("tags"));
4095         manifestMetaContent.append(filter);
4096     }
4097 }
4098 
4099 /*!
4100   Find global entities that have documentation but no
4101   \e{relates} comand. Report these as errors if they
4102   are not also marked \e {internal}.
4103  */
reportOrphans(const Aggregate * parent)4104 void HtmlGenerator::reportOrphans(const Aggregate *parent)
4105 {
4106     const NodeList &children = parent->childNodes();
4107     if (children.size() == 0)
4108         return;
4109 
4110     QString message = "has documentation but no \\relates command";
4111     for (const auto *child : children) {
4112         if (!child || child->isInternal() || child->doc().isEmpty() || !child->isRelatedNonmember())
4113             continue;
4114         switch (child->nodeType()) {
4115         case Node::Enum:
4116             child->location().warning(tr("Global enum, %1, %2").arg(child->name()).arg(message));
4117             break;
4118         case Node::Typedef:
4119             child->location().warning(tr("Global typedef, %1, %2").arg(child->name()).arg(message));
4120             break;
4121         case Node::Function: {
4122             const FunctionNode *fn = static_cast<const FunctionNode *>(child);
4123             switch (fn->metaness()) {
4124             case FunctionNode::QmlSignal:
4125                 child->location().warning(
4126                         tr("Global QML, signal, %1 %2").arg(child->name()).arg(message));
4127                 break;
4128             case FunctionNode::QmlSignalHandler:
4129                 child->location().warning(
4130                         tr("Global QML signal handler, %1, %2").arg(child->name()).arg(message));
4131                 break;
4132             case FunctionNode::QmlMethod:
4133                 child->location().warning(
4134                         tr("Global QML method, %1, %2").arg(child->name()).arg(message));
4135                 break;
4136             case FunctionNode::JsSignal:
4137                 child->location().warning(
4138                         tr("Global JS, signal, %1 %2").arg(child->name()).arg(message));
4139                 break;
4140             case FunctionNode::JsSignalHandler:
4141                 child->location().warning(
4142                         tr("Global JS signal handler, %1, %2").arg(child->name()).arg(message));
4143                 break;
4144             case FunctionNode::JsMethod:
4145                 child->location().warning(
4146                         tr("Global JS method, %1, %2").arg(child->name()).arg(message));
4147                 break;
4148             default:
4149                 if (fn->isMacro())
4150                     child->location().warning(
4151                             tr("Global macro, %1, %2").arg(child->name()).arg(message));
4152                 else
4153                     child->location().warning(
4154                             tr("Global function, %1(), %2").arg(child->name()).arg(message));
4155                 break;
4156             }
4157             break;
4158         }
4159         case Node::Variable:
4160             child->location().warning(
4161                     tr("Global variable, %1, %2").arg(child->name()).arg(message));
4162             break;
4163         case Node::JsProperty:
4164             child->location().warning(
4165                     tr("Global JS property, %1, %2").arg(child->name()).arg(message));
4166             break;
4167         case Node::QmlProperty:
4168             child->location().warning(
4169                     tr("Global QML property, %1, %2").arg(child->name()).arg(message));
4170             break;
4171         default:
4172             break;
4173         }
4174     }
4175 }
4176 
4177 /*!
4178   Returns a reference to the XML stream writer currently in use.
4179   There is one XML stream writer open for each XML file being
4180   written, and they are kept on a stack. The one on top of the
4181   stack is the one being written to at the moment. In the HTML
4182   output generator, it is perhaps impossible for there to ever
4183   be more than one writer open.
4184  */
xmlWriter()4185 QXmlStreamWriter &HtmlGenerator::xmlWriter()
4186 {
4187     return *xmlWriterStack.top();
4188 }
4189 
4190 QT_END_NAMESPACE
4191