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 §ion : 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 §ion : 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() << " ";
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\"><Missing HTML></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 = §ions.stdSummarySections();
1170 detailsSections = §ions.stdDetailsSections();
1171 } else if (aggregate->isClassNode()) {
1172 rawTitle = aggregate->plainName();
1173 fullTitle = aggregate->plainFullName();
1174 title = rawTitle + QLatin1Char(' ') + word;
1175 summarySections = §ions.stdCppClassSummarySections();
1176 detailsSections = §ions.stdCppClassDetailsSections();
1177 } else if (aggregate->isHeader()) {
1178 title = fullTitle = rawTitle = aggregate->fullTitle();
1179 summarySections = §ions.stdSummarySections();
1180 detailsSections = §ions.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 §ion : 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 §ion : 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 §ion : 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 = §ions.stdSummarySections();
1383 detailsSections = §ions.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 §ion : 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, §ions.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 §ion : 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 §ion : 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, §ions.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 §ion : 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 §ion : 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 §ion : 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 §ion, 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 §ions, 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 §ions, 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 §ion : summary_spv) {
2481 out() << "<h2>" << protectEnc(section->title()) << "</h2>\n";
2482 generateSectionList(*section, aggregate, marker, Section::Obsolete);
2483 }
2484
2485 for (const auto §ion : 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 §ions, 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 §ion : 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 §ion : 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> ").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> ").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 §ion, 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 §ion, 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("&");
3378 } else if (ch == QLatin1Char('<')) {
3379 APPEND("<");
3380 } else if (ch == QLatin1Char('>')) {
3381 APPEND(">");
3382 } else if (ch == QLatin1Char('"')) {
3383 APPEND(""");
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><" << protectEnc(etn->name())
3508 << ">. 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