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

source code of qttools/src/qdoc/qdoc/htmlgenerator.cpp