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