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 Sections sections(aggregate);
1110 const SectionVector &summarySections = sections.summarySections();
1111 const SectionVector &detailsSections = sections.detailsSections();
1112
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 } else if (aggregate->isClassNode()) {
1120 fullTitle = aggregate->plainFullName();
1121 title = "%1 %2"_L1.arg(fullTitle, typeWord);
1122 } else if (aggregate->isHeader()) {
1123 title = fullTitle = aggregate->fullTitle();
1124 if (!aggregate->doc().title().isEmpty())
1125 titleText << aggregate->name() << " - "_L1 << aggregate->doc().title();
1126 }
1127
1128 Text subtitleText;
1129 // Generate a subtitle if there are parents to link to, or a template declaration
1130 if (aggregate->parent()->isInAPI() || templateDecl) {
1131 if (templateDecl)
1132 subtitleText << "%1 "_L1.arg((*templateDecl).to_qstring());
1133 subtitleText << aggregate->typeWord(false) << " "_L1;
1134 auto ancestors = fullTitle.split("::"_L1);
1135 ancestors.pop_back();
1136 for (const auto &a : ancestors)
1137 subtitleText << Atom(Atom::AutoLink, a) << "::"_L1;
1138 subtitleText << aggregate->plainName();
1139 }
1140
1141 generateHeader(title, aggregate, marker);
1142 generateTableOfContents(aggregate, marker, &summarySections);
1143 if (!titleText.isEmpty())
1144 generateTitle(titleText, subtitleText, SmallSubTitle, aggregate, marker);
1145 else
1146 generateTitle(title, subtitleText, SmallSubTitle, aggregate, marker);
1147 if (ns && !ns->hasDoc() && ns->docNode()) {
1148 NamespaceNode *fullNamespace = ns->docNode();
1149 Text brief;
1150 brief << "The " << ns->name() << " namespace includes the following elements from module "
1151 << ns->tree()->camelCaseModuleName() << ". The full namespace is "
1152 << "documented in module " << fullNamespace->tree()->camelCaseModuleName();
1153 addNodeLink(brief, fullNamespace, " here.");
1154 out() << "<p>";
1155 generateText(brief, ns, marker);
1156 out() << "</p>\n";
1157 } else
1158 generateBrief(aggregate, marker);
1159
1160 const auto parentIsClass = aggregate->parent()->isClassNode();
1161
1162 if (!parentIsClass)
1163 generateRequisites(aggregate, marker);
1164 generateStatus(aggregate, marker);
1165 if (parentIsClass)
1166 generateSince(aggregate, marker);
1167
1168 QString membersLink = generateAllMembersFile(sections.allMembersSection(), marker);
1169 if (!membersLink.isEmpty()) {
1170 openUnorderedList();
1171 out() << "<li><a href=\"" << membersLink << "\">"
1172 << "List of all members, including inherited members</a></li>\n";
1173 }
1174 QString obsoleteLink = generateObsoleteMembersFile(sections, marker);
1175 if (!obsoleteLink.isEmpty()) {
1176 openUnorderedList();
1177 out() << "<li><a href=\"" << obsoleteLink << "\">"
1178 << "Deprecated members</a></li>\n";
1179 }
1180
1181 if (QString groups_text{groupReferenceText(aggregate)}; !groups_text.isEmpty()) {
1182 openUnorderedList();
1183
1184 out() << "<li>" << groups_text << "</li>\n";
1185 }
1186
1187 closeUnorderedList();
1188
1189 generateThreadSafeness(aggregate, marker);
1190 generateComparisonTable(aggregate);
1191
1192 bool needOtherSection = false;
1193
1194 for (const auto &section : summarySections) {
1195 if (section.members().isEmpty() && section.reimplementedMembers().isEmpty()) {
1196 if (!section.inheritedMembers().isEmpty())
1197 needOtherSection = true;
1198 } else {
1199 if (!section.members().isEmpty()) {
1200 QString ref = registerRef(section.title().toLower());
1201 out() << "<h2 id=\"" << ref << "\">" << protectEnc(section.title()) << "</h2>\n";
1202 generateSection(section.members(), aggregate, marker);
1203 }
1204 if (!section.reimplementedMembers().isEmpty()) {
1205 QString name = QString("Reimplemented ") + section.title();
1206 QString ref = registerRef(name.toLower());
1207 out() << "<h2 id=\"" << ref << "\">" << protectEnc(name) << "</h2>\n";
1208 generateSection(section.reimplementedMembers(), aggregate, marker);
1209 }
1210
1211 if (!section.inheritedMembers().isEmpty()) {
1212 out() << "<ul>\n";
1213 generateSectionInheritedList(section, aggregate);
1214 out() << "</ul>\n";
1215 }
1216 }
1217 }
1218
1219 if (needOtherSection) {
1220 out() << "<h3>Additional Inherited Members</h3>\n"
1221 "<ul>\n";
1222
1223 for (const auto &section : summarySections) {
1224 if (section.members().isEmpty() && !section.inheritedMembers().isEmpty())
1225 generateSectionInheritedList(section, aggregate);
1226 }
1227 out() << "</ul>\n";
1228 }
1229
1230 if (aggregate->doc().isEmpty()) {
1231 QString command = "documentation";
1232 if (aggregate->isClassNode())
1233 command = R"('\class' comment)";
1234 if (!ns || ns->isDocumentedHere()) {
1235 aggregate->location().warning(
1236 QStringLiteral("No %1 for '%2'").arg(command, aggregate->plainSignature()));
1237 }
1238 } else {
1239 generateExtractionMark(aggregate, DetailedDescriptionMark);
1240 out() << "<div class=\"descr\">\n"
1241 << "<h2 id=\"" << registerRef("details") << "\">"
1242 << "Detailed Description"
1243 << "</h2>\n";
1244 generateBody(aggregate, marker);
1245 out() << "</div>\n";
1246 generateAlsoList(aggregate, marker);
1247 generateExtractionMark(aggregate, EndMark);
1248 }
1249
1250 for (const auto &section : detailsSections) {
1251 bool headerGenerated = false;
1252 if (section.isEmpty())
1253 continue;
1254
1255 const QList<Node *> &members = section.members();
1256 for (const auto &member : members) {
1257 if (!headerGenerated) {
1258 if (!section.divClass().isEmpty())
1259 out() << "<div class=\"" << section.divClass() << "\">\n";
1260 out() << "<h2>" << protectEnc(section.title()) << "</h2>\n";
1261 headerGenerated = true;
1262 }
1263 if (!member->isClassNode())
1264 generateDetailedMember(member, aggregate, marker);
1265 else {
1266 out() << "<h3";
1267 if (const auto &attrs = getClassAttr(member, ""_L1); !attrs.isEmpty())
1268 out() << " class=\"%1\""_L1.arg(attrs);
1269 out() << "> class ";
1270 generateFullName(member, aggregate);
1271 out() << "</h3>";
1272 generateBrief(member, marker, aggregate);
1273 }
1274 }
1275 if (headerGenerated && !section.divClass().isEmpty())
1276 out() << "</div>\n";
1277 }
1278 generateFooter(aggregate);
1279}
1280
1282{
1283 Q_ASSERT(aggregate->isProxyNode());
1284
1285 Text subtitleText;
1286
1287 Sections sections(aggregate);
1288 const SectionVector &summarySections = sections.summarySections();
1289 const SectionVector &detailsSections = sections.detailsSections();
1290
1291 QString rawTitle = aggregate->plainName();
1292 QString fullTitle = aggregate->plainFullName();
1293 QString title = rawTitle + " Proxy Page";
1294 generateHeader(title, aggregate, marker);
1295 generateTitle(title, subtitleText, SmallSubTitle, aggregate, marker);
1296 generateBrief(aggregate, marker);
1297 for (const auto &section : summarySections) {
1298 if (!section.members().isEmpty()) {
1299 QString ref = registerRef(section.title().toLower());
1300 out() << "<h2 id=\"" << ref << "\">" << protectEnc(section.title()) << "</h2>\n";
1301 generateSection(section.members(), aggregate, marker);
1302 }
1303 }
1304
1305 if (!aggregate->doc().isEmpty()) {
1306 generateExtractionMark(aggregate, DetailedDescriptionMark);
1307 out() << "<div class=\"descr\">\n"
1308 << "<h2 id=\"" << registerRef("details") << "\">"
1309 << "Detailed Description"
1310 << "</h2>\n";
1311 generateBody(aggregate, marker);
1312 out() << "</div>\n";
1313 generateAlsoList(aggregate, marker);
1314 generateExtractionMark(aggregate, EndMark);
1315 }
1316
1317 for (const auto &section : detailsSections) {
1318 if (section.isEmpty())
1319 continue;
1320
1321 if (!section.divClass().isEmpty())
1322 out() << "<div class=\"" << section.divClass() << "\">\n";
1323 out() << "<h2>" << protectEnc(section.title()) << "</h2>\n";
1324
1325 const QList<Node *> &members = section.members();
1326 for (const auto &member : members) {
1327 if (!member->isClassNode()) {
1328 generateDetailedMember(member, aggregate, marker);
1329 } else {
1330 out() << "<h3";
1331 if (const auto &attrs = getClassAttr(member, ""_L1); !attrs.isEmpty())
1332 out() << " class=\"%1\""_L1.arg(attrs);
1333 out() << "> class ";
1334 generateFullName(member, aggregate);
1335 out() << "</h3>";
1336 generateBrief(member, marker, aggregate);
1337 }
1338 }
1339 if (!section.divClass().isEmpty())
1340 out() << "</div>\n";
1341 }
1342 generateFooter(aggregate);
1343}
1344
1345/*!
1346 Generate the HTML page for a QML type. \qcn is the QML type.
1347 \marker is the code markeup object.
1348 */
1350{
1352 SubTitleSize subTitleSize = LargeSubTitle;
1353 QString htmlTitle = qcn->name();
1354 if (qcn->isQmlBasicType())
1355 htmlTitle.append(" QML Value Type");
1356 else
1357 htmlTitle.append(" QML Type");
1358
1359 if (qcn->isSingleton())
1360 htmlTitle.append(" (Singleton)"_L1);
1361
1362 generateHeader(htmlTitle, qcn, marker);
1363 Sections sections(qcn);
1364 generateTableOfContents(qcn, marker, &sections.summarySections());
1365 marker = CodeMarker::markerForLanguage(QLatin1String("QML"));
1366 generateTitle(htmlTitle, Text() << qcn->subtitle(), subTitleSize, qcn, marker);
1367 generateBrief(qcn, marker);
1368 generateQmlRequisites(qcn, marker);
1369 generateStatus(qcn, marker);
1370
1371 if (qcn->isSingleton()) {
1372 out() << "<p><strong>Note:</strong> This type is a QML singleton. "_L1
1373 << "There is only one instance of this type in the QML engine.</p>\n"_L1;
1374 }
1375
1376 QString allQmlMembersLink;
1377
1378 // No 'All Members' file for QML value types
1379 if (!qcn->isQmlBasicType())
1380 allQmlMembersLink = generateAllQmlMembersFile(sections, marker);
1381 QString obsoleteLink = generateObsoleteQmlMembersFile(sections, marker);
1382 if (!allQmlMembersLink.isEmpty() || !obsoleteLink.isEmpty()) {
1383 openUnorderedList();
1384
1385 if (!allQmlMembersLink.isEmpty()) {
1386 out() << "<li><a href=\"" << allQmlMembersLink << "\">"
1387 << "List of all members, including inherited members</a></li>\n";
1388 }
1389 if (!obsoleteLink.isEmpty()) {
1390 out() << "<li><a href=\"" << obsoleteLink << "\">"
1391 << "Deprecated members</a></li>\n";
1392 }
1393 }
1394
1395 if (QString groups_text{groupReferenceText(qcn)}; !groups_text.isEmpty()) {
1396 openUnorderedList();
1397
1398 out() << "<li>" << groups_text << "</li>\n";
1399 }
1400
1401 closeUnorderedList();
1402
1403 const SectionVector &qmlSummarySections = sections.summarySections();
1404 for (const auto &section : qmlSummarySections) {
1405 if (!section.isEmpty()) {
1406 QString ref = registerRef(section.title().toLower());
1407 out() << "<h2 id=\"" << ref << "\">" << protectEnc(section.title()) << "</h2>\n";
1408 generateQmlSummary(section.members(), qcn, marker);
1409 }
1410 }
1411
1412 generateExtractionMark(qcn, DetailedDescriptionMark);
1413 out() << "<h2 id=\"" << registerRef("details") << "\">"
1414 << "Detailed Description"
1415 << "</h2>\n";
1416 generateBody(qcn, marker);
1417 generateAlsoList(qcn, marker);
1418 generateExtractionMark(qcn, EndMark);
1419
1420 const SectionVector &qmlDetailsSections = sections.detailsSections();
1421 for (const auto &section : qmlDetailsSections) {
1422 if (section.isEmpty())
1423 continue;
1424 out() << "<h2>" << protectEnc(section.title()) << "</h2>\n";
1425 const QList<Node *> &members = section.members();
1426 for (const auto member : members)
1427 generateDetailedQmlMember(member, qcn, marker);
1428 }
1429 generateFooter(qcn);
1431}
1432
1433/*!
1434 Generate the HTML page for an entity that doesn't map
1435 to any underlying parsable C++ or QML element.
1436 */
1438{
1439 generateHeader(pn->fullTitle(), pn, marker);
1440 /*
1441 Generate the TOC for the new doc format.
1442 Don't generate a TOC for the home page.
1443 */
1444 if ((pn->name() != QLatin1String("index.html")))
1445 generateTableOfContents(pn, marker, nullptr);
1446
1447 generateTitle(pn->doc().title(), Text() << pn->subtitle(), LargeSubTitle, pn, marker);
1448 if (pn->isExample()) {
1449 generateBrief(pn, marker, nullptr, false);
1450 }
1451
1452 generateExtractionMark(pn, DetailedDescriptionMark);
1453 out() << R"(<div class="descr" id=")" << registerRef("details")
1454 << "\">\n";
1455
1456 generateBody(pn, marker);
1457 out() << "</div>\n";
1458 generateAlsoList(pn, marker);
1459 generateExtractionMark(pn, EndMark);
1460
1461 generateFooter(pn);
1462}
1463
1464/*!
1465 Generate the HTML page for a group, module, or QML module.
1466 */
1468{
1469 SubTitleSize subTitleSize = LargeSubTitle;
1470 QString ref;
1471
1472 generateHeader(cn->fullTitle(), cn, marker);
1473 generateTableOfContents(cn, marker, nullptr);
1474 generateTitle(cn->doc().title(), Text() << cn->subtitle(), subTitleSize, cn, marker);
1475
1476 // Generate brief for C++ modules, status for all modules.
1477 if (cn->genus() != Genus::DOC && cn->genus() != Genus::DontCare) {
1478 if (cn->isModule())
1479 generateBrief(cn, marker);
1480 generateStatus(cn, marker);
1481 generateSince(cn, marker);
1482 }
1483
1484 if (cn->isModule()) {
1485 if (!cn->noAutoList()) {
1487 if (!nmm.isEmpty()) {
1488 ref = registerRef("namespaces");
1489 out() << "<h2 id=\"" << ref << "\">Namespaces</h2>\n";
1490 generateAnnotatedList(cn, marker, nmm.values());
1491 }
1492 nmm = cn->getMembers([](const Node *n){ return n->isClassNode(); });
1493 if (!nmm.isEmpty()) {
1494 ref = registerRef("classes");
1495 out() << "<h2 id=\"" << ref << "\">Classes</h2>\n";
1496 generateAnnotatedList(cn, marker, nmm.values());
1497 }
1498 }
1499 }
1500
1501 if (cn->isModule() && !cn->doc().briefText().isEmpty()) {
1502 generateExtractionMark(cn, DetailedDescriptionMark);
1503 ref = registerRef("details");
1504 out() << "<div class=\"descr\">\n";
1505 out() << "<h2 id=\"" << ref << "\">"
1506 << "Detailed Description"
1507 << "</h2>\n";
1508 } else {
1509 generateExtractionMark(cn, DetailedDescriptionMark);
1510 out() << R"(<div class="descr" id=")" << registerRef("details")
1511 << "\">\n";
1512 }
1513
1514 generateBody(cn, marker);
1515 out() << "</div>\n";
1516 generateAlsoList(cn, marker);
1517 generateExtractionMark(cn, EndMark);
1518
1519 if (!cn->noAutoList()) {
1520 if (cn->isGroup() || cn->isQmlModule())
1521 generateAnnotatedList(cn, marker, cn->members());
1522 }
1523 generateFooter(cn);
1524}
1525
1526/*!
1527 Generate the HTML page for a generic collection. This is usually
1528 a collection of C++ elements that are related to an element in
1529 a different module.
1530 */
1532{
1533 SubTitleSize subTitleSize = LargeSubTitle;
1534 QString fullTitle = cn->name();
1535
1536 generateHeader(fullTitle, cn, marker);
1537 generateTitle(fullTitle, Text() << cn->subtitle(), subTitleSize, cn, marker);
1538
1539 Text brief;
1540 brief << "Each function or type documented here is related to a class or "
1541 << "namespace that is documented in a different module. The reference "
1542 << "page for that class or namespace will link to the function or type "
1543 << "on this page.";
1544 out() << "<p>";
1545 generateText(brief, cn, marker);
1546 out() << "</p>\n";
1547
1548 const QList<Node *> members = cn->members();
1549 for (const auto &member : members)
1550 generateDetailedMember(member, cn, marker);
1551
1552 generateFooter(cn);
1553}
1554
1555/*!
1556 Returns "html" for this subclass of Generator.
1557 */
1559{
1560 return "html";
1561}
1562
1563/*!
1564 Output a navigation bar (breadcrumbs) for the html file.
1565 For API reference pages, items for the navigation bar are (in order):
1566 \table
1567 \header \li Item \li Related configuration variable \li Notes
1568 \row \li home \li navigation.homepage \li e.g. 'Qt 6.2'
1569 \row \li landing \li navigation.landingpage \li Module landing page
1570 \row \li types \li navigation.cppclassespage (C++)\br
1571 navigation.qmltypespage (QML) \li Types only
1572 \row \li module \li n/a (automatic) \li Module page if different
1573 from previous item
1574 \row \li page \li n/a \li Current page title
1575 \endtable
1576
1577 For other page types (page nodes) the navigation bar is constructed from home
1578 page, landing page, and the chain of PageNode::navigationParent() items (if one exists).
1579 This chain is constructed from the \\list structure on a page or pages defined in
1580 \c navigation.toctitles configuration variable.
1581
1582 Finally, if no other navigation data exists for a page but it is a member of a
1583 single group (using \\ingroup), add that group page to the navigation bar.
1584 */
1585void HtmlGenerator::generateNavigationBar(const QString &title, const Node *node,
1586 CodeMarker *marker, const QString &buildversion,
1587 bool tableItems)
1588{
1589 if (m_noNavigationBar || node == nullptr)
1590 return;
1591
1592 Text navigationbar;
1593
1594 // Set list item types based on the navigation bar type
1595 // TODO: Do we still need table items?
1596 Atom::AtomType itemLeft = tableItems ? Atom::TableItemLeft : Atom::ListItemLeft;
1597 Atom::AtomType itemRight = tableItems ? Atom::TableItemRight : Atom::ListItemRight;
1598
1599 // Helper to add an item to navigation bar based on a string link target
1600 auto addNavItem = [&](const QString &link, const QString &title) {
1601 navigationbar << Atom(itemLeft) << Atom(Atom::NavLink, link)
1603 << Atom(Atom::String, title)
1604 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom(itemRight);
1605 };
1606
1607 // Helper to add an item to navigation bar based on a target node
1608 auto addNavItemNode = [&](const Node *node, const QString &title) {
1609 navigationbar << Atom(itemLeft);
1610 addNodeLink(navigationbar, node, title);
1611 navigationbar << Atom(itemRight);
1612 };
1613
1614 // Resolve the associated module (collection) node and its 'state' description
1615 const auto *moduleNode = m_qdb->getModuleNode(node);
1616 QString moduleState;
1617 if (moduleNode && !moduleNode->state().isEmpty())
1618 moduleState = QStringLiteral(" (%1)").arg(moduleNode->state());
1619
1620 if (m_hometitle == title)
1621 return;
1622 if (!m_homepage.isEmpty())
1623 addNavItem(m_homepage, m_hometitle);
1624 if (!m_landingpage.isEmpty() && m_landingtitle != title)
1625 addNavItem(m_landingpage, m_landingtitle);
1626
1627 if (node->isClassNode()) {
1628 if (!m_cppclassespage.isEmpty() && !m_cppclassestitle.isEmpty())
1629 addNavItem(m_cppclassespage, m_cppclassestitle);
1630 if (!node->physicalModuleName().isEmpty()) {
1631 // Add explicit link to the \module page if:
1632 // - It's not the C++ classes page that's already added, OR
1633 // - It has a \modulestate associated with it
1634 if (moduleNode && (!moduleState.isEmpty() || moduleNode->title() != m_cppclassespage))
1635 addNavItemNode(moduleNode, moduleNode->name() + moduleState);
1636 }
1637 navigationbar << Atom(itemLeft) << Atom(Atom::String, node->name()) << Atom(itemRight);
1638 } else if (node->isQmlType()) {
1639 if (!m_qmltypespage.isEmpty() && !m_qmltypestitle.isEmpty())
1640 addNavItem(m_qmltypespage, m_qmltypestitle);
1641 // Add explicit link to the \qmlmodule page if:
1642 // - It's not the QML types page that's already added, OR
1643 // - It has a \modulestate associated with it
1644 if (moduleNode && (!moduleState.isEmpty() || moduleNode->title() != m_qmltypespage)) {
1645 addNavItemNode(moduleNode, moduleNode->name() + moduleState);
1646 }
1647 navigationbar << Atom(itemLeft) << Atom(Atom::String, node->name()) << Atom(itemRight);
1648 } else {
1649 if (node->isPageNode()) {
1650 auto currentNode{static_cast<const PageNode*>(node)};
1651 std::deque<const Node *> navNodes;
1652 // Cutoff at 16 items in case there's a circular dependency
1653 qsizetype navItems = 0;
1654 while (currentNode->navigationParent() && ++navItems < 16) {
1655 if (std::find(navNodes.cbegin(), navNodes.cend(),
1656 currentNode->navigationParent()) == navNodes.cend())
1657 navNodes.push_front(currentNode->navigationParent());
1658 currentNode = currentNode->navigationParent();
1659 }
1660 // If no nav. parent was found but the page is a \group member, add a link to the
1661 // (first) group page.
1662 if (navNodes.empty()) {
1663 const QStringList groups = static_cast<const PageNode *>(node)->groupNames();
1664 for (const auto &groupName : groups) {
1665 const auto *groupNode = m_qdb->findNodeByNameAndType(QStringList{groupName}, &Node::isGroup);
1666 if (groupNode && !groupNode->title().isEmpty()) {
1667 navNodes.push_front(groupNode);
1668 break;
1669 }
1670 }
1671 }
1672 while (!navNodes.empty()) {
1673 if (navNodes.front()->isPageNode())
1674 addNavItemNode(navNodes.front(), navNodes.front()->title());
1675 navNodes.pop_front();
1676 }
1677 }
1678 if (!navigationbar.isEmpty()) {
1679 navigationbar << Atom(itemLeft) << Atom(Atom::String, title) << Atom(itemRight);
1680 }
1681 }
1682
1683 generateText(navigationbar, node, marker);
1684
1685 if (buildversion.isEmpty())
1686 return;
1687
1688 navigationbar.clear();
1689
1690 if (tableItems) {
1691 out() << "</tr></table><table class=\"buildversion\"><tr>\n"
1692 << R"(<td id="buildversion" width="100%" align="right">)";
1693 } else {
1694 out() << "<li id=\"buildversion\">";
1695 }
1696
1697 // Link buildversion string to navigation.landingpage
1698 if (!m_landingpage.isEmpty() && m_landingtitle != title) {
1699 navigationbar << Atom(Atom::NavLink, m_landingpage)
1700 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1701 << Atom(Atom::String, buildversion)
1702 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1703 generateText(navigationbar, node, marker);
1704 } else {
1705 out() << buildversion;
1706 }
1707 if (tableItems)
1708 out() << "</td>\n";
1709 else
1710 out() << "</li>\n";
1711}
1712
1713void HtmlGenerator::generateHeader(const QString &title, const Node *node, CodeMarker *marker)
1714{
1715 out() << "<!DOCTYPE html>\n";
1716 out() << QString("<html lang=\"%1\">\n").arg(naturalLanguage);
1717 out() << "<head>\n";
1718 out() << " <meta charset=\"utf-8\">\n";
1719 if (node && !node->doc().location().isEmpty())
1720 out() << "<!-- " << node->doc().location().fileName() << " -->\n";
1721
1722 if (node && !node->doc().briefText().isEmpty()) {
1723 out() << " <meta name=\"description\" content=\""
1724 << protectEnc(node->doc().briefText().toString())
1725 << "\">\n";
1726 }
1727
1728 // Write entries from `\meta keywords` as document meta-information
1729 if (node) {
1730 if (const auto *metaTags = node->doc().metaTagMap()) {
1731 QStringList keywords;
1732 for (const auto &kw : metaTags->values(u"keywords"_s))
1733 keywords << kw.split(','_L1, Qt::SkipEmptyParts);
1734
1735 if (!keywords.isEmpty()) {
1736 std::transform(keywords.begin(), keywords.end(), keywords.begin(),
1737 [](const QString &k) { return k.trimmed(); });
1738 out() << " <meta name=\"keywords\" content=\""
1739 << protectEnc(keywords.join(','_L1))
1740 << "\">\n";
1741 }
1742 }
1743 }
1744
1745 // determine the rest of the <title> element content: "title | titleSuffix version"
1746 QString titleSuffix;
1747 if (!m_landingtitle.isEmpty()) {
1748 // for normal pages: "title | landingtitle version"
1749 titleSuffix = m_landingtitle;
1750 } else if (!m_hometitle.isEmpty()) {
1751 // for pages that set the homepage title but not landing page title:
1752 // "title | hometitle version"
1753 if (title != m_hometitle)
1754 titleSuffix = m_hometitle;
1755 } else {
1756 // "title | productname version"
1757 titleSuffix = m_productName.isEmpty() ? m_project : m_productName;
1758 }
1759 if (title == titleSuffix)
1760 titleSuffix.clear();
1761
1762 out() << " <title>";
1763 if (!titleSuffix.isEmpty() && !title.isEmpty()) {
1764 out() << "%1 | %2"_L1.arg(protectEnc(title), titleSuffix);
1765 } else {
1766 out() << protectEnc(title);
1767 }
1768
1769 // append a full version to the suffix if neither suffix nor title
1770 // include (a prefix of) version information
1771 QVersionNumber projectVersion = QVersionNumber::fromString(m_qdb->version());
1772 if (!projectVersion.isNull()) {
1773 QVersionNumber titleVersion;
1774 static const QRegularExpression re(QLatin1String(R"(\d+\.\d+)"));
1775 const QString &versionedTitle = titleSuffix.isEmpty() ? title : titleSuffix;
1776 auto match = re.match(versionedTitle);
1777 if (match.hasMatch())
1778 titleVersion = QVersionNumber::fromString(match.captured());
1779 if (titleVersion.isNull() || !titleVersion.isPrefixOf(projectVersion)) {
1780 // Prefix with product name if one exists
1781 if (!m_productName.isEmpty() && titleSuffix != m_productName)
1782 out() << " | %1"_L1.arg(m_productName);
1783 out() << " %1"_L1.arg(projectVersion.toString());
1784 }
1785 }
1786 out() << "</title>\n";
1787
1788 // Include style sheet and script links.
1789 out() << m_headerStyles;
1790 out() << m_headerScripts;
1791 out() << m_endHeader;
1792
1793 out() << QString(m_postHeader).replace("\\" + COMMAND_VERSION, m_qdb->version());
1794 bool usingTable = m_postHeader.trimmed().endsWith(QLatin1String("<tr>"));
1795 generateNavigationBar(title, node, marker, m_buildversion, usingTable);
1796 out() << QString(m_postPostHeader).replace("\\" + COMMAND_VERSION, m_qdb->version());
1797
1798 m_navigationLinks.clear();
1799 refMap.clear();
1800
1801 if (node && !node->links().empty()) {
1802 std::pair<QString, QString> linkPair;
1803 std::pair<QString, QString> anchorPair;
1804 const Node *linkNode;
1805 bool useSeparator = false;
1806
1807 if (node->links().contains(Node::PreviousLink)) {
1808 linkPair = node->links()[Node::PreviousLink];
1809 linkNode = m_qdb->findNodeForTarget(linkPair.first, node);
1810 if (linkNode == nullptr && !noLinkErrors())
1811 node->doc().location().warning(
1812 QStringLiteral("Cannot link to '%1'").arg(linkPair.first));
1813 if (linkNode == nullptr || linkNode == node)
1814 anchorPair = linkPair;
1815 else
1816 anchorPair = anchorForNode(linkNode);
1817
1818 out() << R"( <link rel="prev" href=")" << anchorPair.first << "\" />\n";
1819
1820 m_navigationLinks += R"(<a class="prevPage" href=")" + anchorPair.first + "\">";
1821 if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty())
1822 m_navigationLinks += protect(anchorPair.second);
1823 else
1824 m_navigationLinks += protect(linkPair.second);
1825 m_navigationLinks += "</a>\n";
1826 useSeparator = !m_navigationSeparator.isEmpty();
1827 }
1828 if (node->links().contains(Node::NextLink)) {
1829 linkPair = node->links()[Node::NextLink];
1830 linkNode = m_qdb->findNodeForTarget(linkPair.first, node);
1831 if (linkNode == nullptr && !noLinkErrors())
1832 node->doc().location().warning(
1833 QStringLiteral("Cannot link to '%1'").arg(linkPair.first));
1834 if (linkNode == nullptr || linkNode == node)
1835 anchorPair = linkPair;
1836 else
1837 anchorPair = anchorForNode(linkNode);
1838
1839 out() << R"( <link rel="next" href=")" << anchorPair.first << "\" />\n";
1840
1841 if (useSeparator)
1842 m_navigationLinks += m_navigationSeparator;
1843
1844 m_navigationLinks += R"(<a class="nextPage" href=")" + anchorPair.first + "\">";
1845 if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty())
1846 m_navigationLinks += protect(anchorPair.second);
1847 else
1848 m_navigationLinks += protect(linkPair.second);
1849 m_navigationLinks += "</a>\n";
1850 }
1851 if (node->links().contains(Node::StartLink)) {
1852 linkPair = node->links()[Node::StartLink];
1853 linkNode = m_qdb->findNodeForTarget(linkPair.first, node);
1854 if (linkNode == nullptr && !noLinkErrors())
1855 node->doc().location().warning(
1856 QStringLiteral("Cannot link to '%1'").arg(linkPair.first));
1857 if (linkNode == nullptr || linkNode == node)
1858 anchorPair = std::move(linkPair);
1859 else
1860 anchorPair = anchorForNode(linkNode);
1861 out() << R"( <link rel="start" href=")" << anchorPair.first << "\" />\n";
1862 }
1863 }
1864
1865 if (node && !node->links().empty())
1866 out() << "<p class=\"naviNextPrevious headerNavi\">\n" << m_navigationLinks << "</p>\n";
1867}
1868
1869void HtmlGenerator::generateTitle(const Text &title, const Text &subtitle,
1870 SubTitleSize subTitleSize, const Node *relative,
1871 CodeMarker *marker)
1872{
1873 out() << QString(m_prologue).replace("\\" + COMMAND_VERSION, m_qdb->version());
1874 QString attribute;
1875 if (isApiGenus(relative->genus()))
1876 attribute = R"( translate="no")";
1877
1878 if (!title.isEmpty()) {
1879 out() << "<h1 class=\"title\"" << attribute << ">";
1880 generateText(title, relative, marker);
1881 out() << "</h1>\n";
1882 }
1883 if (!subtitle.isEmpty()) {
1884 out() << "<span";
1885 if (subTitleSize == SmallSubTitle)
1886 out() << " class=\"small-subtitle\"" << attribute << ">";
1887 else
1888 out() << " class=\"subtitle\"" << attribute << ">";
1889 generateText(subtitle, relative, marker);
1890 out() << "</span>\n";
1891 }
1892}
1893
1894void HtmlGenerator::generateFooter(const Node *node)
1895{
1896 if (node && !node->links().empty())
1897 out() << "<p class=\"naviNextPrevious footerNavi\">\n" << m_navigationLinks << "</p>\n";
1898
1899 out() << QString(m_footer).replace("\\" + COMMAND_VERSION, m_qdb->version())
1900 << QString(m_address).replace("\\" + COMMAND_VERSION, m_qdb->version());
1901
1902 out() << "</body>\n";
1903 out() << "</html>\n";
1904}
1905
1906/*!
1907 Lists the required imports and includes in a table.
1908 The number of rows is known.
1909*/
1910void HtmlGenerator::generateRequisites(Aggregate *aggregate, CodeMarker *marker)
1911{
1912 QMap<QString, Text> requisites;
1913 Text text;
1914
1915 const QString headerText = "Header";
1916 const QString sinceText = "Since";
1917 const QString inheritedByText = "Inherited By";
1918 const QString inheritsText = "Inherits";
1919 const QString nativeTypeText = "In QML";
1920 const QString qtVariableText = "qmake";
1921 const QString cmakeText = "CMake";
1922 const QString statusText = "Status";
1923
1924 // The order of the requisites matter
1925 const QStringList requisiteorder { headerText, cmakeText, qtVariableText, sinceText,
1926 nativeTypeText, inheritsText, inheritedByText, statusText };
1927
1928 addIncludeFileToMap(aggregate, requisites, text, headerText);
1929 addSinceToMap(aggregate, requisites, &text, sinceText);
1930
1931 if (aggregate->isClassNode() || aggregate->isNamespace()) {
1932 addCMakeInfoToMap(aggregate, requisites, &text, cmakeText);
1933 addQtVariableToMap(aggregate, requisites, &text, qtVariableText);
1934 }
1935
1936 if (aggregate->isClassNode()) {
1937 auto *classe = dynamic_cast<ClassNode *>(aggregate);
1938 if (classe && classe->isQmlNativeType()) {
1939 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
1940 const NodeContext context = classe->createContext();
1941 if (InclusionFilter::isIncluded(policy, context))
1942 addQmlNativeTypesToMap(requisites, &text, nativeTypeText, classe);
1943 }
1944
1945 addInheritsToMap(requisites, &text, inheritsText, classe);
1946 addInheritedByToMap(requisites, &text, inheritedByText, classe);
1947 }
1948
1949 // Add the state description (if any) to the map
1950 addStatusToMap(aggregate, requisites, text, statusText);
1951
1952 if (!requisites.isEmpty()) {
1953 // generate the table
1954 generateTheTable(requisiteorder, requisites, aggregate, marker);
1955 }
1956}
1957
1958/*!
1959 * \internal
1960 */
1961void HtmlGenerator::generateTheTable(const QStringList &requisiteOrder,
1962 const QMap<QString, Text> &requisites,
1963 const Aggregate *aggregate, CodeMarker *marker)
1964{
1965 out() << "<div class=\"table\"><table class=\"alignedsummary requisites\" translate=\"no\">\n";
1966
1967 for (auto it = requisiteOrder.constBegin(); it != requisiteOrder.constEnd(); ++it) {
1968
1969 if (requisites.contains(*it)) {
1970 out() << "<tr>"
1971 << "<td class=\"memItemLeft rightAlign topAlign\"> " << *it
1972 << ":"
1973 "</td><td class=\"memItemRight bottomAlign\"> ";
1974
1975 generateText(requisites.value(*it), aggregate, marker);
1976 out() << "</td></tr>\n";
1977 }
1978 }
1979 out() << "</table></div>\n";
1980}
1981
1982/*!
1983 * \internal
1984 * Adds inherited by information to the map.
1985 */
1986void HtmlGenerator::addInheritedByToMap(QMap<QString, Text> &requisites, Text *text,
1987 const QString &inheritedByText, ClassNode *classe)
1988{
1989 if (!classe->derivedClasses().isEmpty()) {
1990 text->clear();
1991 *text << Atom::ParaLeft;
1992 int count = appendSortedNames(*text, classe, classe->derivedClasses());
1993 *text << Atom::ParaRight;
1994 if (count > 0)
1995 requisites.insert(inheritedByText, *text);
1996 }
1997}
1998
1999/*!
2000 * \internal
2001 * Adds base classes to the map.
2002 */
2003void HtmlGenerator::addInheritsToMap(QMap<QString, Text> &requisites, Text *text,
2004 const QString &inheritsText, ClassNode *classe)
2005{
2006 if (!classe->baseClasses().isEmpty()) {
2007 int index = 0;
2008 text->clear();
2009 const auto baseClasses = classe->baseClasses();
2010 for (const auto &cls : baseClasses) {
2011 if (cls.m_node) {
2012 appendFullName(*text, cls.m_node, classe);
2013
2014 if (cls.m_access == Access::Protected) {
2015 *text << " (protected)";
2016 } else if (cls.m_access == Access::Private) {
2017 *text << " (private)";
2018 }
2019 *text << Utilities::comma(index++, classe->baseClasses().size());
2020 }
2021 }
2022 *text << Atom::ParaRight;
2023 if (index > 0)
2024 requisites.insert(inheritsText, *text);
2025 }
2026}
2027
2028/*!
2029 \internal
2030 Add the QML/C++ native type information to the map.
2031 */
2032void HtmlGenerator::addQmlNativeTypesToMap(QMap<QString, Text> &requisites, Text *text,
2033 const QString &nativeTypeText, ClassNode *classe) const
2034{
2035 if (!text)
2036 return;
2037
2038 text->clear();
2039
2040 QList<QmlTypeNode *> nativeTypes { classe->qmlNativeTypes().cbegin(), classe->qmlNativeTypes().cend()};
2041 std::sort(nativeTypes.begin(), nativeTypes.end(), Node::nodeNameLessThan);
2042 qsizetype index { 0 };
2043
2044 for (const auto &item : std::as_const(nativeTypes)) {
2045 addNodeLink(*text, item);
2046 *text << Utilities::comma(index++, nativeTypes.size());
2047 }
2048 requisites.insert(nativeTypeText, *text);
2049}
2050
2051/*!
2052 * \internal
2053 * Adds the CMake package and link library information to the map.
2054 */
2055void HtmlGenerator::addCMakeInfoToMap(const Aggregate *aggregate, QMap<QString, Text> &requisites,
2056 Text *text, const QString &CMakeInfo) const
2057{
2058 if (!aggregate->physicalModuleName().isEmpty() && text != nullptr) {
2059 const CollectionNode *cn =
2060 m_qdb->getCollectionNode(aggregate->physicalModuleName(), NodeType::Module);
2061
2062 const auto result = cmakeRequisite(cn);
2063
2064 if (!result) {
2065 return;
2066 }
2067
2068 text->clear();
2069
2070 const Atom lineBreak = Atom(Atom::RawString, "<br/>\n");
2071
2072 *text << openCodeTag << result->first << closeCodeTag << lineBreak
2073 << openCodeTag << result->second << closeCodeTag;
2074
2075 requisites.insert(CMakeInfo, *text);
2076 }
2077}
2078
2079/*!
2080 * \internal
2081 * Adds the Qt variable (from the \\qtvariable command) to the map.
2082 */
2083void HtmlGenerator::addQtVariableToMap(const Aggregate *aggregate, QMap<QString, Text> &requisites,
2084 Text *text, const QString &qtVariableText) const
2085{
2086 if (!aggregate->physicalModuleName().isEmpty()) {
2087 const CollectionNode *cn =
2088 m_qdb->getCollectionNode(aggregate->physicalModuleName(), NodeType::Module);
2089
2090 if (cn && !cn->qtVariable().isEmpty()) {
2091 text->clear();
2092 *text << openCodeTag << "QT += " + cn->qtVariable() << closeCodeTag;
2093 requisites.insert(qtVariableText, *text);
2094 }
2095 }
2096}
2097
2098/*!
2099 * \internal
2100 * Adds the since information (from the \\since command) to the map.
2101 *
2102 */
2103void HtmlGenerator::addSinceToMap(const Aggregate *aggregate, QMap<QString, Text> &requisites,
2104 Text *text, const QString &sinceText) const
2105{
2106 if (!aggregate->since().isEmpty() && text != nullptr) {
2107 text->clear();
2108 *text << formatSince(aggregate) << Atom::ParaRight;
2109 requisites.insert(sinceText, *text);
2110 }
2111}
2112
2113/*!
2114 * \internal
2115 * Adds the status description for \a aggregate, together with a <span> element, to the \a
2116 * requisites map.
2117 *
2118 * The span element can be used for adding CSS styling/icon associated with a specific status.
2119 * The span class name is constructed by converting the description (sans \\deprecated
2120 * version info) to lowercase and replacing all non-alphanum characters with hyphens. In
2121 * addition, the span has a class \c status. For example,
2122 * 'Tech Preview' -> class="status tech-preview"
2123*/
2124void HtmlGenerator::addStatusToMap(const Aggregate *aggregate, QMap<QString, Text> &requisites,
2125 Text &text, const QString &statusText) const
2126{
2127 auto status{formatStatus(aggregate, m_qdb)};
2128 if (!status)
2129 return;
2130
2131 QString spanClass;
2132 if (aggregate->status() == Status::Deprecated)
2133 spanClass = u"deprecated"_s; // Disregard any version info
2134 else
2135 spanClass = Utilities::asAsciiPrintable(status.value());
2136
2137 text.clear();
2138 text << Atom(Atom::String, status.value())
2139 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_SPAN +
2140 "class=\"status %1\""_L1.arg(spanClass))
2141 << Atom(Atom::FormattingRight, ATOM_FORMATTING_SPAN);
2142 requisites.insert(statusText, text);
2143}
2144
2145/*!
2146 * \internal
2147 * Adds the include file (resolved automatically or set with the
2148 * \\inheaderfile command) to the map.
2149 */
2150void HtmlGenerator::addIncludeFileToMap(const Aggregate *aggregate,
2151 QMap<QString, Text> &requisites, Text& text,
2152 const QString &headerText)
2153{
2154 if (aggregate->includeFile()) {
2155 text.clear();
2156 text << openCodeTag << "#include <%1>"_L1.arg(*aggregate->includeFile()) << closeCodeTag;
2157 requisites.insert(headerText, text);
2158 }
2159}
2160
2161/*!
2162 Lists the required imports and includes in a table.
2163 The number of rows is known.
2164*/
2165void HtmlGenerator::generateQmlRequisites(QmlTypeNode *qcn, CodeMarker *marker)
2166{
2167 if (qcn == nullptr)
2168 return;
2169
2170 QMap<QString, Text> requisites;
2171 Text text;
2172
2173 const QString importText = "Import Statement";
2174 const QString sinceText = "Since";
2175 const QString inheritedByText = "Inherited By";
2176 const QString inheritsText = "Inherits";
2177 const QString nativeTypeText = "In C++";
2178 const QString statusText = "Status";
2179
2180 // add the module name and version to the map
2181 QString logicalModuleVersion;
2182 const CollectionNode *collection = qcn->logicalModule();
2183
2184 // skip import statement of \internal collections
2185 if (!qcn->logicalModuleName().isEmpty()) {
2186 bool generate_import = true;
2187 if (collection) {
2188 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
2189 const NodeContext context = collection->createContext();
2190 generate_import = InclusionFilter::isIncluded(policy, context);
2191 }
2192 if (generate_import) {
2193 QStringList parts = QStringList() << "import" << qcn->logicalModuleName() << qcn->logicalModuleVersion();
2194 text.clear();
2195 text << openCodeTag << parts.join(' ').trimmed() << closeCodeTag;
2196 requisites.insert(importText, text);
2197 }
2198 } else if (!qcn->isQmlBasicType() && qcn->logicalModuleName().isEmpty()) {
2199 qcn->doc().location().warning(QStringLiteral("Could not resolve QML import statement for type '%1'").arg(qcn->name()),
2200 QStringLiteral("Maybe you forgot to use the '\\%1' command?").arg(COMMAND_INQMLMODULE));
2201 }
2202
2203 // add the since and project into the map
2204 if (!qcn->since().isEmpty()) {
2205 text.clear();
2206 text << formatSince(qcn) << Atom::ParaRight;
2207 requisites.insert(sinceText, text);
2208 }
2209
2210 // add the native type to the map
2211 if (ClassNode *cn = qcn->classNode(); cn && cn->isQmlNativeType()) {
2212 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
2213 const NodeContext context = cn->createContext();
2214 if (InclusionFilter::isIncluded(policy, context)) {
2215 text.clear();
2216 addNodeLink(text, cn);
2217 requisites.insert(nativeTypeText, text);
2218 }
2219 }
2220
2221 // add the inherits to the map
2222 QmlTypeNode *base = qcn->qmlBaseNode();
2223 NodeList subs;
2224 QmlTypeNode::subclasses(qcn, subs, true);
2225 QStringList knownTypeNames{qcn->name()};
2226
2227 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
2228 while (base) {
2229 const NodeContext context = base->createContext();
2230 if (InclusionFilter::isIncluded(policy, context))
2231 break;
2232 base = base->qmlBaseNode();
2233 }
2234 if (base) {
2235 knownTypeNames << base->name();
2236 text.clear();
2237 text << Atom::ParaLeft;
2238 addNodeLink(text, base);
2239
2240 // Disambiguate with '(<QML module name>)' if there are clashing type names
2241 for (const auto sub : std::as_const(subs)) {
2242 if (knownTypeNames.contains(sub->name())) {
2243 text << Atom(Atom::String, " (%1)"_L1.arg(base->logicalModuleName()));
2244 break;
2245 }
2246 }
2247 text << Atom::ParaRight;
2248 requisites.insert(inheritsText, text);
2249 }
2250
2251 // add the inherited-by to the map
2252 if (!subs.isEmpty()) {
2253 text.clear();
2254 text << Atom::ParaLeft;
2255 int count = appendSortedQmlNames(text, qcn, knownTypeNames, subs);
2256 text << Atom::ParaRight;
2257 if (count > 0)
2258 requisites.insert(inheritedByText, text);
2259 }
2260
2261 // Add the state description (if any) to the map
2262 addStatusToMap(qcn, requisites, text, statusText);
2263
2264 // The order of the requisites matter
2265 const QStringList requisiteorder {std::move(importText), std::move(sinceText),
2266 std::move(nativeTypeText), std::move(inheritsText),
2267 std::move(inheritedByText), std::move(statusText)};
2268
2269 if (!requisites.isEmpty())
2270 generateTheTable(requisiteorder, requisites, qcn, marker);
2271}
2272
2273void HtmlGenerator::generateBrief(const Node *node, CodeMarker *marker, const Node *relative,
2274 bool addLink)
2275{
2276 Text brief = node->doc().briefText();
2277
2278 if (!brief.isEmpty()) {
2279 if (!brief.lastAtom()->string().endsWith('.')) {
2280 brief << Atom(Atom::String, ".");
2281 node->doc().location().warning(
2282 QStringLiteral("'\\brief' statement does not end with a full stop."));
2283 }
2284 generateExtractionMark(node, BriefMark);
2285 out() << "<p>";
2286 generateText(brief, node, marker);
2287
2288 if (addLink) {
2289 if (!relative || node == relative)
2290 out() << " <a href=\"#";
2291 else
2292 out() << " <a href=\"" << linkForNode(node, relative) << '#';
2293 out() << registerRef("details") << "\">More...</a>";
2294 }
2295
2296 out() << "</p>\n";
2297 generateExtractionMark(node, EndMark);
2298 }
2299}
2300
2301/*!
2302 Revised for the new doc format.
2303 Generates a table of contents beginning at \a node.
2304 */
2305void HtmlGenerator::generateTableOfContents(const Node *node, CodeMarker *marker,
2306 const SectionVector *sections)
2307{
2308 QList<Atom *> toc;
2310 toc = node->doc().tableOfContents();
2311 if (tocDepth == 0 || (toc.isEmpty() && !sections && !node->isModule())) {
2312 generateSidebar();
2313 return;
2314 }
2315
2316 int sectionNumber = 1;
2317 int detailsBase = 0;
2318
2319 // disable nested links in table of contents
2320 m_inContents = true;
2321
2322 out() << "<div class=\"sidebar\">\n";
2323 out() << "<div class=\"toc\">\n";
2324 out() << "<h3 id=\"toc\">Contents</h3>\n";
2325
2326 if (node->isModule()) {
2327 openUnorderedList();
2328 if (!static_cast<const CollectionNode *>(node)->noAutoList()) {
2329 if (node->hasNamespaces()) {
2330 out() << "<li class=\"level" << sectionNumber << "\"><a href=\"#"
2331 << registerRef("namespaces") << "\">Namespaces</a></li>\n";
2332 }
2333 if (node->hasClasses()) {
2334 out() << "<li class=\"level" << sectionNumber << "\"><a href=\"#"
2335 << registerRef("classes") << "\">Classes</a></li>\n";
2336 }
2337 }
2338 out() << "<li class=\"level" << sectionNumber << "\"><a href=\"#" << registerRef("details")
2339 << "\">Detailed Description</a></li>\n";
2340 for (const auto &entry : std::as_const(toc)) {
2341 if (entry->string().toInt() == 1) {
2342 detailsBase = 1;
2343 break;
2344 }
2345 }
2346 } else if (sections && (node->isClassNode() || node->isNamespace() || node->isQmlType())) {
2347 for (const auto &section : *sections) {
2348 if (!section.members().isEmpty()) {
2349 openUnorderedList();
2350 out() << "<li class=\"level" << sectionNumber << "\"><a href=\"#"
2351 << registerRef(section.plural()) << "\">" << section.title() << "</a></li>\n";
2352 }
2353 if (!section.reimplementedMembers().isEmpty()) {
2354 openUnorderedList();
2355 QString ref = QString("Reimplemented ") + section.plural();
2356 out() << "<li class=\"level" << sectionNumber << "\"><a href=\"#"
2357 << registerRef(ref.toLower()) << "\">"
2358 << QString("Reimplemented ") + section.title() << "</a></li>\n";
2359 }
2360 }
2361 if (!node->isNamespace() || node->hasDoc()) {
2362 openUnorderedList();
2363 out() << "<li class=\"level" << sectionNumber << "\"><a href=\"#"
2364 << registerRef("details") << "\">Detailed Description</a></li>\n";
2365 }
2366 for (const auto &entry : toc) {
2367 if (entry->string().toInt() == 1) {
2368 detailsBase = 1;
2369 break;
2370 }
2371 }
2372 }
2373
2374 for (const auto &atom : toc) {
2375 sectionNumber = atom->string().toInt() + detailsBase;
2376 // restrict the ToC depth to the one set by the HTML.tocdepth variable or
2377 // print all levels if tocDepth is not set.
2378 if (sectionNumber <= tocDepth || tocDepth < 0) {
2379 openUnorderedList();
2380 int numAtoms;
2381 Text headingText = Text::sectionHeading(atom);
2382 out() << "<li class=\"level" << sectionNumber << "\">";
2383 out() << "<a href=\"" << '#' << Tree::refForAtom(atom) << "\">";
2384 generateAtomList(headingText.firstAtom(), node, marker, true, numAtoms);
2385 out() << "</a></li>\n";
2386 }
2387 }
2388 closeUnorderedList();
2389 out() << "</div>\n";
2390 out() << R"(<div class="sidebar-content" id="sidebar-content"></div>)";
2391 out() << "</div>\n";
2392 m_inContents = false;
2393 m_inLink = false;
2394}
2395
2396/*!
2397 Outputs a placeholder div where the style can add customized sidebar content.
2398 */
2399void HtmlGenerator::generateSidebar()
2400{
2401 out() << "<div class=\"sidebar\">";
2402 out() << R"(<div class="sidebar-content" id="sidebar-content"></div>)";
2403 out() << "</div>\n";
2404}
2405
2406QString HtmlGenerator::generateAllMembersFile(const Section &section, CodeMarker *marker)
2407{
2408 if (section.isEmpty())
2409 return QString();
2410
2411 const Aggregate *aggregate = section.aggregate();
2412 QString fileName = fileBase(aggregate) + "-members." + fileExtension();
2413 beginSubPage(aggregate, fileName);
2414 QString title = "List of All Members for " + aggregate->plainFullName();
2415 generateHeader(title, aggregate, marker);
2416 generateSidebar();
2417 generateTitle(title, Text(), SmallSubTitle, aggregate, marker);
2418 out() << "<p>This is the complete list of members for ";
2419 generateFullName(aggregate, nullptr);
2420 out() << ", including inherited members.</p>\n";
2421
2422 generateSectionList(section, aggregate, marker);
2423
2424 generateFooter();
2425 endSubPage();
2426 return fileName;
2427}
2428
2429/*!
2430 This function creates an html page on which are listed all
2431 the members of the QML class used to generte the \a sections,
2432 including the inherited members. The \a marker is used for
2433 formatting stuff.
2434 */
2435QString HtmlGenerator::generateAllQmlMembersFile(const Sections &sections, CodeMarker *marker)
2436{
2437
2439 return QString();
2440
2441 const Aggregate *aggregate = sections.aggregate();
2442 QString fileName = fileBase(aggregate) + "-members." + fileExtension();
2443 beginSubPage(aggregate, fileName);
2444 QString title = "List of All Members for " + aggregate->name();
2445 generateHeader(title, aggregate, marker);
2446 generateSidebar();
2447 generateTitle(title, Text(), SmallSubTitle, aggregate, marker);
2448 out() << "<p>This is the complete list of members for ";
2449 generateFullName(aggregate, nullptr);
2450 out() << ", including inherited members.</p>\n";
2451
2453 for (int i = 0; i < cknl.size(); i++) {
2454 const auto &ckn = cknl[i];
2455 const QmlTypeNode *qcn = ckn.first;
2456 const NodeVector &nodes = ckn.second;
2457 if (nodes.isEmpty())
2458 continue;
2459 if (i != 0) {
2460 out() << "<p>The following members are inherited from ";
2461 generateFullName(qcn, nullptr);
2462 out() << ".</p>\n";
2463 }
2464 openUnorderedList();
2465 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
2466 for (int j = 0; j < nodes.size(); j++) {
2467 Node *node = nodes[j];
2468 const NodeContext context = node->createContext();
2469 if (!InclusionFilter::isIncluded(policy, context))
2470 continue;
2472 continue;
2473
2474 std::function<void(Node *)> generate = [&](Node *n) {
2475 out() << "<li class=\"fn\" translate=\"no\">";
2476 generateQmlItem(n, aggregate, marker, true);
2477 if (n->isQmlProperty()) {
2478 auto qpn = static_cast<QmlPropertyNode *>(n);
2479 QStringList hints = qpn->hints();
2480 if (qpn->isAttached())
2481 hints << "attached"_L1;
2482 if (!hints.isEmpty())
2483 out() << " [" << hints.join(' '_L1) << "]";
2484 } else if (n->isAttached()) {
2485 // Non-property attached items (signals, methods) show [attached]
2486 out() << " [attached]";
2487 }
2488 // Indent property group members
2489 if (n->isPropertyGroup()) {
2490 out() << "<ul>\n";
2491 const QList<Node *> &collective =
2492 static_cast<SharedCommentNode *>(n)->collective();
2493 std::for_each(collective.begin(), collective.end(), generate);
2494 out() << "</ul>\n";
2495 }
2496 out() << "</li>\n";
2497 };
2498 generate(node);
2499 }
2500 closeUnorderedList();
2501 }
2502
2503
2504 generateFooter();
2505 endSubPage();
2506 return fileName;
2507}
2508
2509QString HtmlGenerator::generateObsoleteMembersFile(const Sections &sections, CodeMarker *marker)
2510{
2511 SectionPtrVector summary_spv;
2512 SectionPtrVector details_spv;
2513 if (!sections.hasObsoleteMembers(&summary_spv, &details_spv))
2514 return QString();
2515
2516 Aggregate *aggregate = sections.aggregate();
2517 QString title = "Obsolete Members for " + aggregate->plainFullName();
2518 QString fileName = fileBase(aggregate) + "-obsolete." + fileExtension();
2519
2520 beginSubPage(aggregate, fileName);
2521 generateHeader(title, aggregate, marker);
2522 generateSidebar();
2523 generateTitle(title, Text(), SmallSubTitle, aggregate, marker);
2524
2525 out() << "<p><b>The following members of class "
2526 << "<a href=\"" << linkForNode(aggregate, nullptr) << "\" translate=\"no\">"
2527 << protectEnc(aggregate->name()) << "</a>"
2528 << " are deprecated.</b> "
2529 << "They are provided to keep old source code working. "
2530 << "We strongly advise against using them in new code.</p>\n";
2531
2532 for (const auto &section : summary_spv) {
2533 out() << "<h2>" << protectEnc(section->title()) << "</h2>\n";
2534 generateSectionList(*section, aggregate, marker, true);
2535 }
2536
2537 for (const auto &section : details_spv) {
2538 out() << "<h2>" << protectEnc(section->title()) << "</h2>\n";
2539
2540 const NodeVector &members = section->obsoleteMembers();
2541 for (const auto &member : members)
2542 generateDetailedMember(member, aggregate, marker);
2543 }
2544
2545 generateFooter();
2546 endSubPage();
2547 return fileName;
2548}
2549
2550/*!
2551 Generates a separate file where deprecated members of the QML
2552 type \a qcn are listed. The \a marker is used to generate
2553 the section lists, which are then traversed and output here.
2554 */
2555QString HtmlGenerator::generateObsoleteQmlMembersFile(const Sections &sections, CodeMarker *marker)
2556{
2557 SectionPtrVector summary_spv;
2558 SectionPtrVector details_spv;
2559 if (!sections.hasObsoleteMembers(&summary_spv, &details_spv))
2560 return QString();
2561
2562 Aggregate *aggregate = sections.aggregate();
2563 QString title = "Obsolete Members for " + aggregate->name();
2564 QString fileName = fileBase(aggregate) + "-obsolete." + fileExtension();
2565
2566 beginSubPage(aggregate, fileName);
2567 generateHeader(title, aggregate, marker);
2568 generateSidebar();
2569 generateTitle(title, Text(), SmallSubTitle, aggregate, marker);
2570
2571 out() << "<p><b>The following members of QML type "
2572 << "<a href=\"" << linkForNode(aggregate, nullptr) << "\">"
2573 << protectEnc(aggregate->name()) << "</a>"
2574 << " are deprecated.</b> "
2575 << "They are provided to keep old source code working. "
2576 << "We strongly advise against using them in new code.</p>\n";
2577
2578 for (const auto &section : summary_spv) {
2579 QString ref = registerRef(section->title().toLower());
2580 out() << "<h2 id=\"" << ref << "\">" << protectEnc(section->title()) << "</h2>\n";
2581 generateQmlSummary(section->obsoleteMembers(), aggregate, marker);
2582 }
2583
2584 for (const auto &section : details_spv) {
2585 out() << "<h2>" << protectEnc(section->title()) << "</h2>\n";
2586 const NodeVector &members = section->obsoleteMembers();
2587 for (const auto &member : members) {
2588 generateDetailedQmlMember(member, aggregate, marker);
2589 out() << "<br/>\n";
2590 }
2591 }
2592
2593 generateFooter();
2594 endSubPage();
2595 return fileName;
2596}
2597
2598void HtmlGenerator::generateClassHierarchy(const Node *relative, NodeMultiMap &classMap)
2599{
2600 if (classMap.isEmpty())
2601 return;
2602
2603 NodeMap topLevel;
2604 for (const auto &it : classMap) {
2605 auto *classe = static_cast<ClassNode *>(it);
2606 if (classe->baseClasses().isEmpty())
2607 topLevel.insert(classe->name(), classe);
2608 }
2609
2610 QStack<NodeMap> stack;
2611 stack.push(topLevel);
2612
2613 out() << "<ul>\n";
2614 while (!stack.isEmpty()) {
2615 if (stack.top().isEmpty()) {
2616 stack.pop();
2617 out() << "</ul>\n";
2618 } else {
2619 ClassNode *child = static_cast<ClassNode *>(*stack.top().begin());
2620 out() << "<li>";
2621 generateFullName(child, relative);
2622 out() << "</li>\n";
2623 stack.top().erase(stack.top().begin());
2624
2625 NodeMap newTop;
2626 const auto derivedClasses = child->derivedClasses();
2627 for (const RelatedClass &d : derivedClasses) {
2628 if (d.m_node && d.m_node->isInAPI())
2629 newTop.insert(d.m_node->name(), d.m_node);
2630 }
2631 if (!newTop.isEmpty()) {
2632 stack.push(newTop);
2633 out() << "<ul>\n";
2634 }
2635 }
2636 }
2637}
2638
2639/*!
2640 Outputs an annotated list of the nodes in \a unsortedNodes.
2641 A two-column table is output.
2642 */
2643void HtmlGenerator::generateAnnotatedList(const Node *relative, CodeMarker *marker,
2644 const NodeList &unsortedNodes, Qt::SortOrder sortOrder)
2645{
2646 if (unsortedNodes.isEmpty() || relative == nullptr)
2647 return;
2648
2649 NodeMultiMap nmm;
2650 bool allInternal = true;
2651 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
2652 for (auto *node : unsortedNodes) {
2653 const NodeContext context = node->createContext();
2654 if (InclusionFilter::isIncluded(policy, context) && !node->isDeprecated()) {
2655 allInternal = false;
2656 nmm.insert(node->fullName(relative), node);
2657 }
2658 }
2659 if (allInternal)
2660 return;
2661 out() << "<div class=\"table\"><table class=\"annotated\">\n";
2662 int row = 0;
2663 NodeList nodes = nmm.values();
2664
2665 if (sortOrder == Qt::DescendingOrder)
2666 std::sort(nodes.rbegin(), nodes.rend(), Node::nodeSortKeyOrNameLessThan);
2667 else
2668 std::sort(nodes.begin(), nodes.end(), Node::nodeSortKeyOrNameLessThan);
2669
2670 for (const auto *node : std::as_const(nodes)) {
2671 if (++row % 2 == 1)
2672 out() << "<tr class=\"odd topAlign\">";
2673 else
2674 out() << "<tr class=\"even topAlign\">";
2675 out() << "<td class=\"tblName\" translate=\"no\"><p>";
2676 generateFullName(node, relative);
2677 out() << "</p></td>";
2678
2679 if (!node->isTextPageNode()) {
2680 Text brief = node->doc().trimmedBriefText(node->name());
2681 if (!brief.isEmpty()) {
2682 out() << "<td class=\"tblDescr\"><p>";
2683 generateText(brief, node, marker);
2684 out() << "</p></td>";
2685 } else if (!node->reconstitutedBrief().isEmpty()) {
2686 out() << "<td class=\"tblDescr\"><p>";
2687 out() << node->reconstitutedBrief();
2688 out() << "</p></td>";
2689 }
2690 } else {
2691 out() << "<td class=\"tblDescr\"><p>";
2692 if (!node->reconstitutedBrief().isEmpty()) {
2693 out() << node->reconstitutedBrief();
2694 } else
2695 out() << protectEnc(node->doc().briefText().toString());
2696 out() << "</p></td>";
2697 }
2698 out() << "</tr>\n";
2699 }
2700 out() << "</table></div>\n";
2701}
2702
2703/*!
2704 Outputs a series of annotated lists from the nodes in \a nmm,
2705 divided into sections based by the key names in the multimap.
2706 */
2707void HtmlGenerator::generateAnnotatedLists(const Node *relative, CodeMarker *marker,
2708 const NodeMultiMap &nmm)
2709{
2710 const auto &uniqueKeys = nmm.uniqueKeys();
2711 for (const QString &name : uniqueKeys) {
2712 if (!name.isEmpty()) {
2713 out() << "<h2 id=\"" << registerRef(name.toLower()) << "\">" << protectEnc(name)
2714 << "</h2>\n";
2715 }
2716 generateAnnotatedList(relative, marker, nmm.values(name));
2717 }
2718}
2719
2720/*!
2721 This function finds the common prefix of the names of all
2722 the classes in the class map \a nmm and then generates a
2723 compact list of the class names alphabetized on the part
2724 of the name not including the common prefix. You can tell
2725 the function to use \a commonPrefix as the common prefix,
2726 but normally you let it figure it out itself by looking at
2727 the name of the first and last classes in the class map
2728 \a nmm.
2729 */
2730void HtmlGenerator::generateCompactList(ListType listType, const Node *relative,
2731 const NodeMultiMap &nmm, bool includeAlphabet,
2732 const QString &commonPrefix)
2733{
2734 if (nmm.isEmpty())
2735 return;
2736
2737 const int NumParagraphs = 37; // '0' to '9', 'A' to 'Z', '_'
2738 qsizetype commonPrefixLen = commonPrefix.size();
2739
2740 /*
2741 Divide the data into 37 paragraphs: 0, ..., 9, A, ..., Z,
2742 underscore (_). QAccel will fall in paragraph 10 (A) and
2743 QXtWidget in paragraph 33 (X). This is the only place where we
2744 assume that NumParagraphs is 37. Each paragraph is a NodeMultiMap.
2745 */
2746 NodeMultiMap paragraph[NumParagraphs + 1];
2747 QString paragraphName[NumParagraphs + 1];
2748 QSet<char> usedParagraphNames;
2749
2750 for (auto c = nmm.constBegin(); c != nmm.constEnd(); ++c) {
2751 QStringList pieces = c.key().split("::");
2752 int idx = commonPrefixLen;
2753 if (idx > 0 && !pieces.last().startsWith(commonPrefix, Qt::CaseInsensitive))
2754 idx = 0;
2755 QString last = pieces.last().toLower();
2756 QString key = last.mid(idx);
2757
2758 int paragraphNr = NumParagraphs - 1;
2759
2760 if (key[0].digitValue() != -1) {
2761 paragraphNr = key[0].digitValue();
2762 } else if (key[0] >= QLatin1Char('a') && key[0] <= QLatin1Char('z')) {
2763 paragraphNr = 10 + key[0].unicode() - 'a';
2764 }
2765
2766 paragraphName[paragraphNr] = key[0].toUpper();
2767 usedParagraphNames.insert(key[0].toLower().cell());
2768 paragraph[paragraphNr].insert(last, c.value());
2769 }
2770
2771 /*
2772 Each paragraph j has a size: paragraph[j].count(). In the
2773 discussion, we will assume paragraphs 0 to 5 will have sizes
2774 3, 1, 4, 1, 5, 9.
2775
2776 We now want to compute the paragraph offset. Paragraphs 0 to 6
2777 start at offsets 0, 3, 4, 8, 9, 14, 23.
2778 */
2779 qsizetype paragraphOffset[NumParagraphs + 1]; // 37 + 1
2780 paragraphOffset[0] = 0;
2781 for (int i = 0; i < NumParagraphs; i++) // i = 0..36
2782 paragraphOffset[i + 1] = paragraphOffset[i] + paragraph[i].size();
2783
2784 /*
2785 Output the alphabet as a row of links.
2786 */
2787 if (includeAlphabet) {
2788 out() << "<p class=\"centerAlign functionIndex\" translate=\"no\"><b>";
2789 for (int i = 0; i < 26; i++) {
2790 QChar ch('a' + i);
2791 if (usedParagraphNames.contains(char('a' + i)))
2792 out() << QString("<a href=\"#%1\">%2</a>&nbsp;").arg(ch).arg(ch.toUpper());
2793 }
2794 out() << "</b></p>\n";
2795 }
2796
2797 /*
2798 Output a <div> element to contain all the <dl> elements.
2799 */
2800 out() << "<div class=\"flowListDiv\" translate=\"no\">\n";
2801 m_numTableRows = 0;
2802
2803 // Build a map of all duplicate names across the entire list
2804 QHash<QString, int> nameOccurrences;
2805 for (const auto &[key, node] : nmm.asKeyValueRange()) {
2806 QStringList pieces{node->fullName(relative).split("::"_L1)};
2807 const QString &name{pieces.last()};
2808 nameOccurrences[name]++;
2809 }
2810
2811 int curParNr = 0;
2812 int curParOffset = 0;
2813
2814 for (int i = 0; i < nmm.size(); i++) {
2815 while ((curParNr < NumParagraphs) && (curParOffset == paragraph[curParNr].size())) {
2816 ++curParNr;
2817 curParOffset = 0;
2818 }
2819
2820 /*
2821 Starting a new paragraph means starting a new <dl>.
2822 */
2823 if (curParOffset == 0) {
2824 if (i > 0)
2825 out() << "</dl>\n";
2826 if (++m_numTableRows % 2 == 1)
2827 out() << "<dl class=\"flowList odd\">";
2828 else
2829 out() << "<dl class=\"flowList even\">";
2830 out() << "<dt class=\"alphaChar\"";
2831 if (includeAlphabet)
2832 out() << QString(" id=\"%1\"").arg(paragraphName[curParNr][0].toLower());
2833 out() << "><b>" << paragraphName[curParNr] << "</b></dt>\n";
2834 }
2835
2836 /*
2837 Output a <dd> for the current offset in the current paragraph.
2838 */
2839 out() << "<dd>";
2840 if ((curParNr < NumParagraphs) && !paragraphName[curParNr].isEmpty()) {
2841 NodeMultiMap::Iterator it;
2842 NodeMultiMap::Iterator next;
2843 it = paragraph[curParNr].begin();
2844 for (int j = 0; j < curParOffset; j++)
2845 ++it;
2846
2847 if (listType == Generic) {
2848 /*
2849 Previously, we used generateFullName() for this, but we
2850 require some special formatting.
2851 */
2852 out() << "<a href=\"" << linkForNode(it.value(), relative) << "\">";
2853 } else if (listType == Obsolete) {
2854 QString fileName = fileBase(it.value()) + "-obsolete." + fileExtension();
2855 QString link;
2856 if (useOutputSubdirs())
2857 link = "../%1/"_L1.arg(it.value()->tree()->physicalModuleName());
2858 link += fileName;
2859 out() << "<a href=\"" << link << "\">";
2860 }
2861
2862 QStringList pieces{it.value()->fullName(relative).split("::"_L1)};
2863 const auto &name{pieces.last()};
2864
2865 // Add module disambiguation if there are multiple types with the same name
2866 if (nameOccurrences[name] > 1) {
2867 const QString moduleName = it.value()->isQmlNode() ? it.value()->logicalModuleName()
2868 : it.value()->tree()->camelCaseModuleName();
2869 pieces.last().append(": %1"_L1.arg(moduleName));
2870 }
2871
2872 out() << protectEnc(pieces.last());
2873 out() << "</a>";
2874 if (pieces.size() > 1) {
2875 out() << " (";
2876 generateFullName(it.value()->parent(), relative);
2877 out() << ')';
2878 }
2879 }
2880 out() << "</dd>\n";
2881 curParOffset++;
2882 }
2883 if (nmm.size() > 0)
2884 out() << "</dl>\n";
2885
2886 out() << "</div>\n";
2887}
2888
2889void HtmlGenerator::generateFunctionIndex(const Node *relative)
2890{
2891 out() << "<p class=\"centerAlign functionIndex\" translate=\"no\"><b>";
2892 for (int i = 0; i < 26; i++) {
2893 QChar ch('a' + i);
2894 out() << QString("<a href=\"#%1\">%2</a>&nbsp;").arg(ch).arg(ch.toUpper());
2895 }
2896 out() << "</b></p>\n";
2897
2898 char nextLetter = 'a';
2899
2900 out() << "<ul translate=\"no\">\n";
2901 NodeMapMap &funcIndex = m_qdb->getFunctionIndex();
2902 for (auto fnMap = funcIndex.constBegin(); fnMap != funcIndex.constEnd(); ++fnMap) {
2903 const QString &key = fnMap.key();
2904 const QChar firstLetter = key.isEmpty() ? QChar('A') : key.front();
2905 Q_ASSERT_X(firstLetter.unicode() < 256, "generateFunctionIndex",
2906 "Only valid C++ identifiers were expected");
2907 const char currentLetter = firstLetter.isLower() ? firstLetter.unicode() : nextLetter - 1;
2908
2909 if (currentLetter < nextLetter) {
2910 out() << "<li>";
2911 } else {
2912 // TODO: This is not covered by our tests
2913 while (nextLetter < currentLetter)
2914 out() << QStringLiteral("<li id=\"%1\"></li>").arg(nextLetter++);
2915 Q_ASSERT(nextLetter == currentLetter);
2916 out() << QStringLiteral("<li id=\"%1\">").arg(nextLetter++);
2917 }
2918 out() << protectEnc(key) << ':';
2919
2920 for (auto it = (*fnMap).constBegin(); it != (*fnMap).constEnd(); ++it) {
2921 out() << ' ';
2922 generateFullName((*it)->parent(), relative, *it);
2923 }
2924 out() << "</li>\n";
2925 }
2926 while (nextLetter <= 'z')
2927 out() << QStringLiteral("<li id=\"%1\"></li>").arg(nextLetter++);
2928 out() << "</ul>\n";
2929}
2930
2931void HtmlGenerator::generateLegaleseList(const Node *relative, CodeMarker *marker)
2932{
2933 TextToNodeMap &legaleseTexts = m_qdb->getLegaleseTexts();
2934 for (auto it = legaleseTexts.cbegin(), end = legaleseTexts.cend(); it != end; ++it) {
2935 Text text = it.key();
2936 generateText(text, relative, marker);
2937 out() << "<ul>\n";
2938 do {
2939 out() << "<li>";
2940 generateFullName(it.value(), relative);
2941 out() << "</li>\n";
2942 ++it;
2943 } while (it != legaleseTexts.constEnd() && it.key() == text);
2944 out() << "</ul>\n";
2945 }
2946}
2947
2948void HtmlGenerator::generateQmlItem(const Node *node, const Node *relative, CodeMarker *marker,
2949 bool summary)
2950{
2951 QString marked = marker->markedUpQmlItem(node, summary);
2952 marked.replace("@param>", "i>");
2953
2954 marked.replace("<@extra>", "<code class=\"%1 extra\" translate=\"no\">"_L1
2955 .arg(summary ? "summary"_L1 : "details"_L1));
2956 marked.replace("</@extra>", "</code>");
2957
2958
2959 if (summary) {
2960 marked.remove("<@name>");
2961 marked.remove("</@name>");
2962 marked.remove("<@type>");
2963 marked.remove("</@type>");
2964 }
2965 out() << highlightedCode(marked, relative, false, Genus::QML);
2966}
2967
2968/*!
2969 This function generates a simple list (without annotations) for
2970 the members of collection node \a {cn}. The list is sorted
2971 according to \a sortOrder.
2972
2973 Returns \c true if the list was generated (collection has members),
2974 \c false otherwise.
2975 */
2976bool HtmlGenerator::generateGroupList(CollectionNode *cn, Qt::SortOrder sortOrder)
2977{
2978 m_qdb->mergeCollections(cn);
2979 if (cn->members().isEmpty())
2980 return false;
2981
2982 NodeList members{cn->members()};
2983 if (sortOrder == Qt::DescendingOrder)
2984 std::sort(members.rbegin(), members.rend(), Node::nodeSortKeyOrNameLessThan);
2985 else
2986 std::sort(members.begin(), members.end(), Node::nodeSortKeyOrNameLessThan);
2987 out() << "<ul>\n";
2988 for (const auto *node : std::as_const(members)) {
2989 out() << "<li translate=\"no\">";
2990 generateFullName(node, nullptr);
2991 out() << "</li>\n";
2992 }
2993 out() << "</ul>\n";
2994 return true;
2995}
2996
2997void HtmlGenerator::generateList(const Node *relative, CodeMarker *marker,
2998 const QString &selector, Qt::SortOrder sortOrder)
2999{
3000 CNMap cnm;
3002 if (selector == QLatin1String("overviews"))
3003 type = NodeType::Group;
3004 else if (selector == QLatin1String("cpp-modules"))
3005 type = NodeType::Module;
3006 else if (selector == QLatin1String("qml-modules"))
3007 type = NodeType::QmlModule;
3008 if (type != NodeType::NoType) {
3009 NodeList nodeList;
3010 m_qdb->mergeCollections(type, cnm, relative);
3011 const auto collectionList = cnm.values();
3012 nodeList.reserve(collectionList.size());
3013 for (auto *collectionNode : collectionList)
3014 nodeList.append(collectionNode);
3015 generateAnnotatedList(relative, marker, nodeList, sortOrder);
3016 } else {
3017 /*
3018 \generatelist {selector} is only allowed in a
3019 comment where the topic is \group, \module, or
3020 \qmlmodule.
3021 */
3022 if (relative && !relative->isCollectionNode()) {
3023 relative->doc().location().warning(
3024 QStringLiteral("\\generatelist {%1} is only allowed in \\group, "
3025 "\\module and \\qmlmodule comments.")
3026 .arg(selector));
3027 return;
3028 }
3029 auto *node = const_cast<Node *>(relative);
3030 auto *collectionNode = static_cast<CollectionNode *>(node);
3031 if (!collectionNode)
3032 return;
3033 m_qdb->mergeCollections(collectionNode);
3034 generateAnnotatedList(collectionNode, marker, collectionNode->members(), sortOrder);
3035 }
3036}
3037
3038void HtmlGenerator::generateSection(const NodeVector &nv, const Node *relative, CodeMarker *marker)
3039{
3040 bool alignNames = true;
3041 if (!nv.isEmpty()) {
3042 bool twoColumn = false;
3043 if (nv.first()->isProperty()) {
3044 twoColumn = (nv.size() >= 5);
3045 alignNames = false;
3046 }
3047 if (alignNames) {
3048 out() << "<div class=\"table\"><table class=\"alignedsummary\" translate=\"no\">\n";
3049 } else {
3050 if (twoColumn)
3051 out() << "<div class=\"table\"><table class=\"propsummary\" translate=\"no\">\n"
3052 << "<tr><td class=\"topAlign\">";
3053 out() << "<ul>\n";
3054 }
3055
3056 int i = 0;
3057 for (const auto &member : nv) {
3058
3059 if (alignNames) {
3060 out() << "<tr><td class=\"memItemLeft rightAlign topAlign\"> ";
3061 } else {
3062 if (twoColumn && i == (nv.size() + 1) / 2)
3063 out() << "</ul></td><td class=\"topAlign\"><ul>\n";
3064 out() << "<li class=\"fn\" translate=\"no\">";
3065 }
3066
3067 generateSynopsis(member, relative, marker, Section::Summary, alignNames);
3068 if (alignNames)
3069 out() << "</td></tr>\n";
3070 else
3071 out() << "</li>\n";
3072 i++;
3073 }
3074 if (alignNames)
3075 out() << "</table></div>\n";
3076 else {
3077 out() << "</ul>\n";
3078 if (twoColumn)
3079 out() << "</td></tr>\n</table></div>\n";
3080 }
3081 }
3082}
3083
3084void HtmlGenerator::generateSectionList(const Section &section, const Node *relative,
3085 CodeMarker *marker, bool useObsoleteMembers)
3086{
3087 bool alignNames = true;
3088 const NodeVector &members =
3089 (useObsoleteMembers ? section.obsoleteMembers() : section.members());
3090 if (!members.isEmpty()) {
3091 bool hasPrivateSignals = false;
3092 bool isInvokable = false;
3093 bool twoColumn = false;
3094 if (section.style() == Section::AllMembers) {
3095 alignNames = false;
3096 twoColumn = (members.size() >= 16);
3097 } else if (members.first()->isProperty()) {
3098 twoColumn = (members.size() >= 5);
3099 alignNames = false;
3100 }
3101 if (alignNames) {
3102 out() << "<div class=\"table\"><table class=\"alignedsummary\" translate=\"no\">\n";
3103 } else {
3104 if (twoColumn)
3105 out() << "<div class=\"table\"><table class=\"propsummary\" translate=\"no\">\n"
3106 << "<tr><td class=\"topAlign\">";
3107 out() << "<ul>\n";
3108 }
3109
3110 int i = 0;
3111 for (const auto &member : members) {
3112 // Filter out unnamed nodes. This includes regular shared comment
3113 // nodes but not property groups.
3114 if (member->name().isEmpty())
3115 continue;
3116
3117 if (alignNames) {
3118 out() << "<tr><td class=\"memItemLeft topAlign rightAlign\"> ";
3119 } else {
3120 if (twoColumn && i == (members.size() + 1) / 2)
3121 out() << "</ul></td><td class=\"topAlign\"><ul>\n";
3122 out() << "<li class=\"fn\" translate=\"no\">";
3123 }
3124
3125 generateSynopsis(member, relative, marker, section.style(), alignNames);
3126 if (member->isFunction()) {
3127 const auto *fn = static_cast<const FunctionNode *>(member);
3128 if (fn->isPrivateSignal()) {
3129 hasPrivateSignals = true;
3130 if (alignNames)
3131 out() << "</td><td class=\"memItemRight bottomAlign\">[see note below]";
3132 } else if (fn->isInvokable()) {
3133 isInvokable = true;
3134 if (alignNames)
3135 out() << "</td><td class=\"memItemRight bottomAlign\">[see note below]";
3136 }
3137 }
3138 if (alignNames)
3139 out() << "</td></tr>\n";
3140 else
3141 out() << "</li>\n";
3142 i++;
3143 }
3144 if (alignNames)
3145 out() << "</table></div>\n";
3146 else {
3147 out() << "</ul>\n";
3148 if (twoColumn)
3149 out() << "</td></tr>\n</table></div>\n";
3150 }
3151 if (alignNames) {
3152 if (hasPrivateSignals)
3153 generateAddendum(relative, Generator::PrivateSignal, marker);
3154 if (isInvokable)
3155 generateAddendum(relative, Generator::Invokable, marker);
3156 }
3157 }
3158
3159 if (!useObsoleteMembers && section.style() == Section::Summary
3160 && !section.inheritedMembers().isEmpty()) {
3161 out() << "<ul>\n";
3162 generateSectionInheritedList(section, relative);
3163 out() << "</ul>\n";
3164 }
3165}
3166
3167void HtmlGenerator::generateSectionInheritedList(const Section &section, const Node *relative)
3168{
3169 const QList<std::pair<Aggregate *, int>> &inheritedMembers = section.inheritedMembers();
3170 for (const auto &member : inheritedMembers) {
3171 out() << "<li class=\"fn\" translate=\"no\">";
3172 out() << member.second << ' ';
3173 if (member.second == 1) {
3174 out() << section.singular();
3175 } else {
3176 out() << section.plural();
3177 }
3178 out() << " inherited from <a href=\"" << fileName(member.first) << '#'
3179 << Generator::cleanRef(section.title().toLower()) << "\">"
3180 << protectEnc(member.first->plainFullName(relative)) << "</a></li>\n";
3181 }
3182}
3183
3184void HtmlGenerator::generateSynopsis(const Node *node, const Node *relative, CodeMarker *marker,
3185 Section::Style style, bool alignNames)
3186{
3187 QString marked = marker->markedUpSynopsis(node, relative, style);
3188 marked.replace("@param>", "i>");
3189
3190 if (style == Section::Summary) {
3191 marked.remove("<@name>");
3192 marked.remove("</@name>");
3193 }
3194
3195 if (style == Section::AllMembers) {
3196 static const QRegularExpression extraRegExp("<@extra>.*</@extra>",
3197 QRegularExpression::InvertedGreedinessOption);
3198 marked.remove(extraRegExp);
3199 } else {
3200 marked.replace("<@extra>", "<code class=\"%1 extra\" translate=\"no\">"_L1
3201 .arg(style == Section::Summary ? "summary"_L1 : "details"_L1));
3202 marked.replace("</@extra>", "</code>");
3203 }
3204
3205 if (style != Section::Details) {
3206 marked.remove("<@type>");
3207 marked.remove("</@type>");
3208 }
3209
3210 out() << highlightedCode(marked, relative, alignNames);
3211}
3212
3213QString HtmlGenerator::highlightedCode(const QString &markedCode, const Node *relative,
3214 bool alignNames, Genus genus)
3215{
3216 QString src = markedCode;
3217 QString html;
3218 html.reserve(src.size());
3219 QStringView arg;
3220 QStringView par1;
3221
3222 const QChar charLangle = '<';
3223 const QChar charAt = '@';
3224
3225 static const QString typeTag("type");
3226 static const QString headerTag("headerfile");
3227 static const QString funcTag("func");
3228 static const QString linkTag("link");
3229 static const QString extrefTag("extref");
3230
3231 // URL mapping for external references to cppreference.com
3232 static const QHash<QString, QString> extrefUrls = {
3233 {"cpp-explicitly-defaulted"_L1,
3234 "https://en.cppreference.com/w/cpp/language/function#Defaulted_functions"_L1},
3235 {"cpp-deleted-functions"_L1,
3236 "https://en.cppreference.com/w/cpp/language/function#Deleted_functions"_L1},
3237 };
3238
3239 // replace all <@link> tags: "(<@link node=\"([^\"]+)\">).*(</@link>)"
3240 // replace all <@func> tags: "(<@func target=\"([^\"]*)\">)(.*)(</@func>)"
3241 // replace all "(<@(type|headerfile)(?: +[^>]*)?>)(.*)(</@\\2>)" tags
3242 bool done = false;
3243 for (int i = 0, srcSize = src.size(); i < srcSize;) {
3244 if (src.at(i) == charLangle && src.at(i + 1) == charAt) {
3245 if (alignNames && !done) {
3246 html += QLatin1String("</td><td class=\"memItemRight bottomAlign\">");
3247 done = true;
3248 }
3249 i += 2;
3250 if (parseArg(src, linkTag, &i, srcSize, &arg, &par1)) {
3251 html += QLatin1String("<b>");
3252 const Node *n = static_cast<const Node*>(Utilities::nodeForString(par1.toString()));
3253 QString link = linkForNode(n, relative);
3254 addLink(link, arg, &html);
3255 html += QLatin1String("</b>");
3256 } else if (parseArg(src, funcTag, &i, srcSize, &arg, &par1)) {
3257 const FunctionNode *fn = m_qdb->findFunctionNode(par1.toString(), relative, genus);
3258 QString link = linkForNode(fn, relative);
3259 addLink(link, arg, &html);
3260 par1 = QStringView();
3261 } else if (parseArg(src, typeTag, &i, srcSize, &arg, &par1)) {
3262 par1 = QStringView();
3263 const Node *n = m_qdb->findTypeNode(arg.toString(), relative, genus);
3264 html += QLatin1String("<span class=\"type\">");
3265 if (n && (n->isQmlBasicType())) {
3266 if (relative && (relative->genus() == n->genus() || genus == n->genus()))
3267 addLink(linkForNode(n, relative), arg, &html);
3268 else
3269 html += arg;
3270 } else
3271 addLink(linkForNode(n, relative), arg, &html);
3272 html += QLatin1String("</span>");
3273 } else if (parseArg(src, headerTag, &i, srcSize, &arg, &par1)) {
3274 par1 = QStringView();
3275 if (arg.startsWith(QLatin1Char('&')))
3276 html += arg;
3277 else {
3278 const Node *n = m_qdb->findNodeForInclude(QStringList(arg.toString()));
3279 if (n && n != relative)
3280 addLink(linkForNode(n, relative), arg, &html);
3281 else
3282 html += arg;
3283 }
3284 } else if (parseArg(src, extrefTag, &i, srcSize, &arg, &par1)) {
3285 QString url = extrefUrls.value(par1.toString());
3286 if (!url.isEmpty())
3287 addLink(url, arg, &html);
3288 else
3289 html += arg;
3290 } else {
3291 html += charLangle;
3292 html += charAt;
3293 }
3294 } else {
3295 html += src.at(i++);
3296 }
3297 }
3298
3299 // Replace code marker tags with HTML spans:
3300 // "<@tag>" -> "<span ...>"
3301 // "</@tag>" -> "</span>"
3302 src = html;
3303 html = QString();
3304 html.reserve(src.size());
3305
3306 struct SpanTag {
3307 QLatin1StringView tag;
3308 QLatin1StringView span;
3309 };
3310 static constexpr SpanTag spanTags[] = {
3311 {"comment>"_L1, "<span class=\"comment\">"_L1},
3312 {"preprocessor>"_L1, "<span class=\"preprocessor\">"_L1},
3313 {"string>"_L1, "<span class=\"string\">"_L1},
3314 {"char>"_L1, "<span class=\"char\">"_L1},
3315 {"number>"_L1, "<span class=\"number\">"_L1},
3316 {"op>"_L1, "<span class=\"operator\">"_L1},
3317 {"type>"_L1, "<span class=\"type\">"_L1},
3318 {"name>"_L1, "<span class=\"name\">"_L1},
3319 {"keyword>"_L1, "<span class=\"keyword\">"_L1},
3320 {"template-block>"_L1, "<span class=\"template-block\">"_L1},
3321 };
3322
3323 qsizetype i = 0;
3324 const qsizetype n = src.size();
3325 const QStringView sv(src);
3326 while (i < n) {
3327 if (sv.at(i) == '<'_L1) {
3328 if (i + 1 < n && sv.at(i + 1) == '@'_L1) {
3329 i += 2;
3330 bool handled = false;
3331 for (const auto &st : spanTags) {
3332 if (i + st.tag.size() <= n
3333 && st.tag == sv.sliced(i, st.tag.size())) {
3334 html += st.span;
3335 i += st.tag.size();
3336 handled = true;
3337 break;
3338 }
3339 }
3340 if (!handled) {
3341 // drop 'our' unknown tags (the ones still containing '@')
3342 while (i < n && sv.at(i) != '>'_L1)
3343 ++i;
3344 if (i < n)
3345 ++i;
3346 }
3347 continue;
3348 } else if (i + 2 < n && sv.at(i + 1) == '/'_L1 && sv.at(i + 2) == '@'_L1) {
3349 i += 3;
3350 bool handled = false;
3351 for (const auto &st : spanTags) {
3352 if (i + st.tag.size() <= n
3353 && st.tag == sv.sliced(i, st.tag.size())) {
3354 html += "</span>"_L1;
3355 i += st.tag.size();
3356 handled = true;
3357 break;
3358 }
3359 }
3360 if (!handled) {
3361 // drop 'our' unknown tags (the ones still containing '@')
3362 while (i < n && sv.at(i) != '>'_L1)
3363 ++i;
3364 if (i < n)
3365 ++i;
3366 }
3367 continue;
3368 }
3369 }
3370 html += src.at(i);
3371 ++i;
3372 }
3373 return html;
3374}
3375
3376void HtmlGenerator::generateLink(const Atom *atom)
3377{
3378 Q_ASSERT(m_inLink);
3379
3380 if (m_linkNode && m_linkNode->isFunction()) {
3381 auto match = XmlGenerator::m_funcLeftParen.match(atom->string());
3382 if (match.hasMatch()) {
3383 // C++: move () outside of link
3384 qsizetype leftParenLoc = match.capturedStart(1);
3385 out() << protectEnc(atom->string().left(leftParenLoc));
3386 endLink();
3387 out() << protectEnc(atom->string().mid(leftParenLoc));
3388 return;
3389 }
3390 }
3391 out() << protectEnc(atom->string());
3392}
3393
3394QString HtmlGenerator::protectEnc(const QString &string)
3395{
3396 return protect(string);
3397}
3398
3399QString HtmlGenerator::protect(const QString &string)
3400{
3401 if (string.isEmpty())
3402 return string;
3403
3404#define APPEND(x)
3405 if (html.isEmpty()) {
3406 html = string;
3407 html.truncate(i);
3408 }
3409 html += (x);
3410
3411 QString html;
3412 qsizetype n = string.size();
3413
3414 for (int i = 0; i < n; ++i) {
3415 QChar ch = string.at(i);
3416
3417 if (ch == QLatin1Char('&')) {
3418 APPEND("&amp;");
3419 } else if (ch == QLatin1Char('<')) {
3420 APPEND("&lt;");
3421 } else if (ch == QLatin1Char('>')) {
3422 APPEND("&gt;");
3423 } else if (ch == QChar(8211)) {
3424 APPEND("&ndash;");
3425 } else if (ch == QChar(8212)) {
3426 APPEND("&mdash;");
3427 } else if (ch == QLatin1Char('"')) {
3428 APPEND("&quot;");
3429 } else {
3430 if (!html.isEmpty())
3431 html += ch;
3432 }
3433 }
3434
3435 if (!html.isEmpty())
3436 return html;
3437 return string;
3438
3439#undef APPEND
3440}
3441
3442QString HtmlGenerator::fileBase(const Node *node) const
3443{
3444 QString result = Generator::fileBase(node);
3445 if (!node->isAggregate() && node->isDeprecated())
3446 result += QLatin1String("-obsolete");
3447 return result;
3448}
3449
3450QString HtmlGenerator::fileName(const Node *node)
3451{
3452 if (node->isExternalPage())
3453 return node->name();
3454 return Generator::fileName(node);
3455}
3456
3457void HtmlGenerator::generateFullName(const Node *apparentNode, const Node *relative,
3458 const Node *actualNode)
3459{
3460 if (actualNode == nullptr)
3461 actualNode = apparentNode;
3462 bool link = !linkForNode(actualNode, relative).isEmpty();
3463 if (link) {
3464 out() << "<a href=\"" << linkForNode(actualNode, relative);
3465 if (actualNode->isDeprecated())
3466 out() << "\" class=\"obsolete";
3467 out() << "\">";
3468 }
3469 out() << protectEnc(apparentNode->fullName(relative));
3470 if (link)
3471 out() << "</a>";
3472}
3473
3474/*!
3475 Generates a link to the declaration of the C++ API entity
3476 represented by \a node.
3477*/
3478void HtmlGenerator::generateSourceLink(const Node *node)
3479{
3480 Q_ASSERT(node);
3481 if (node->genus() != Genus::CPP)
3482 return;
3483
3484 const auto srcLink = Config::instance().getSourceLink();
3485 if (!srcLink.enabled)
3486 return;
3487
3488 // With no valid configuration or location, do nothing
3489 const auto &loc{node->declLocation()};
3490 if (loc.isEmpty() || srcLink.baseUrl.isEmpty() || srcLink.rootPath.isEmpty())
3491 return;
3492
3493 QString srcUrl{srcLink.baseUrl};
3494 if (!srcUrl.contains('\1'_L1)) {
3495 if (!srcUrl.endsWith('/'_L1))
3496 srcUrl += '/'_L1;
3497 srcUrl += '\1'_L1;
3498 }
3499
3500 QDir rootDir{srcLink.rootPath};
3501 srcUrl.replace('\1'_L1, rootDir.relativeFilePath(loc.filePath()));
3502 srcUrl.replace('\2'_L1, QString::number(loc.lineNo()));
3503 const auto &description{"View declaration of this %1"_L1.arg(node->nodeTypeString())};
3504 out() << "<a class=\"srclink\" href=\"%1\" title=\"%2\">%3</a>"_L1
3505 .arg(srcUrl, description, srcLink.linkText);
3506}
3507
3508void HtmlGenerator::generateDetailedMember(const Node *node, const PageNode *relative,
3509 CodeMarker *marker)
3510{
3511 const EnumNode *etn;
3512 generateExtractionMark(node, MemberMark);
3513 QString nodeRef = nullptr;
3514 if (node->isSharedCommentNode()) {
3515 const auto *scn = reinterpret_cast<const SharedCommentNode *>(node);
3516 const QList<Node *> &collective = scn->collective();
3517 if (collective.size() > 1)
3518 out() << "<div class=\"fngroup\">\n";
3519 for (const auto *sharedNode : collective) {
3520 out() << headingStart.arg(getClassAttr(sharedNode, "fn fngroupitem"_L1),
3521 refForNode(sharedNode));
3522 generateSynopsis(sharedNode, relative, marker, Section::Details);
3523 generateSourceLink(sharedNode);
3524 out() << headingEnd;
3525 }
3526 if (collective.size() > 1)
3527 out() << "</div>";
3528 out() << '\n';
3529 } else {
3530 if (node->isEnumType(Genus::CPP) && (etn = static_cast<const EnumNode *>(node))->flagsType()) {
3531 out() << headingStart.arg(getClassAttr(node, "flags"_L1), refForNode(node));
3532 generateSynopsis(etn, relative, marker, Section::Details);
3533 out() << "<br/>";
3534 generateSynopsis(etn->flagsType(), relative, marker, Section::Details);
3535 generateSourceLink(node);
3536 out() << headingEnd;
3537 } else {
3538 out() << headingStart.arg(getClassAttr(node, "fn"_L1), refForNode(node));
3539 generateSynopsis(node, relative, marker, Section::Details);
3540 generateSourceLink(node);
3541 out() << headingEnd;
3542 }
3543 }
3544
3545 generateStatus(node, marker);
3546 generateBody(node, marker);
3547 if (node->isFunction()) {
3548 const auto *func = static_cast<const FunctionNode *>(node);
3549 if (func->hasOverloads() && (func->isSignal() || func->isSlot()))
3550 generateAddendum(node, OverloadNote, marker, AdmonitionPrefix::Note);
3551 }
3552 generateComparisonCategory(node, marker);
3553 generateThreadSafeness(node, marker);
3554 generateSince(node, marker);
3555 generateNoexceptNote(node, marker);
3556
3557 if (node->isProperty()) {
3558 const auto property = static_cast<const PropertyNode *>(node);
3559 if (property->propertyType() == PropertyNode::PropertyType::StandardProperty ||
3560 property->propertyType() == PropertyNode::PropertyType::BindableProperty) {
3561 Section section("", "", "", "", Section::Accessors);
3562
3563 section.appendMembers(property->getters().toVector());
3564 section.appendMembers(property->setters().toVector());
3565 section.appendMembers(property->resetters().toVector());
3566
3567 if (!section.members().isEmpty()) {
3568 out() << "<p><b>Access functions:</b></p>\n";
3569 generateSectionList(section, node, marker);
3570 }
3571
3572 Section notifiers("", "", "", "", Section::Accessors);
3573 notifiers.appendMembers(property->notifiers().toVector());
3574
3575 if (!notifiers.members().isEmpty()) {
3576 out() << "<p><b>Notifier signal:</b></p>\n";
3577 generateSectionList(notifiers, node, marker);
3578 }
3579 }
3580 } else if (node->isEnumType(Genus::CPP)) {
3581 const auto *enumTypeNode = static_cast<const EnumNode *>(node);
3582 if (enumTypeNode->flagsType()) {
3583 out() << "<p>The " << protectEnc(enumTypeNode->flagsType()->name())
3584 << " type is a typedef for "
3585 << "<a href=\"" << m_qflagsHref << "\">QFlags</a>&lt;"
3586 << protectEnc(enumTypeNode->name()) << "&gt;. It stores an OR combination of "
3587 << protectEnc(enumTypeNode->name()) << " values.</p>\n";
3588 }
3589 }
3590 generateAlsoList(node, marker);
3591 generateExtractionMark(node, EndMark);
3592}
3593
3594/*!
3595 This version of the function is called when outputting the link
3596 to an example file or example image, where the \a link is known
3597 to be correct.
3598 */
3599void HtmlGenerator::beginLink(const QString &link)
3600{
3601 m_link = link;
3602 m_inLink = true;
3603 m_linkNode = nullptr;
3604
3605 if (!m_link.isEmpty())
3606 out() << "<a href=\"" << m_link << "\" translate=\"no\">";
3607}
3608
3609void HtmlGenerator::beginLink(const QString &link, const Node *node, const Node *relative)
3610{
3611 m_link = link;
3612 m_inLink = true;
3613 m_linkNode = node;
3614 if (m_link.isEmpty())
3615 return;
3616
3617 const QString &translate_attr =
3618 (node && isApiGenus(node->genus())) ? " translate=\"no\""_L1 : ""_L1;
3619
3620 if (node == nullptr || (relative != nullptr && node->status() == relative->status()))
3621 out() << "<a href=\"" << m_link << "\"%1>"_L1.arg(translate_attr);
3622 else if (node->isDeprecated())
3623 out() << "<a href=\"" << m_link << "\" class=\"obsolete\"%1>"_L1.arg(translate_attr);
3624 else
3625 out() << "<a href=\"" << m_link << "\"%1>"_L1.arg(translate_attr);
3626}
3627
3628void HtmlGenerator::endLink()
3629{
3630 if (!m_inLink)
3631 return;
3632
3633 m_inLink = false;
3634 m_linkNode = nullptr;
3635
3636 if (!m_link.isEmpty())
3637 out() << "</a>";
3638}
3639
3640/*!
3641 Generates the summary list for the \a members. Only used for
3642 sections of QML element documentation.
3643 */
3644void HtmlGenerator::generateQmlSummary(const NodeVector &members, const Node *relative,
3645 CodeMarker *marker)
3646{
3647 if (!members.isEmpty()) {
3648 out() << "<ul>\n";
3649 for (const auto &member : members) {
3650 out() << "<li class=\"fn\" translate=\"no\">";
3651 generateQmlItem(member, relative, marker, true);
3652 if (member->isPropertyGroup()) {
3653 const auto *scn = static_cast<const SharedCommentNode *>(member);
3654 if (scn->count() > 0) {
3655 out() << "<ul>\n";
3656 const QList<Node *> &sharedNodes = scn->collective();
3657 for (const auto &node : sharedNodes) {
3658 if (node->isQmlProperty()) {
3659 out() << "<li class=\"fn\" translate=\"no\">";
3660 generateQmlItem(node, relative, marker, true);
3661 out() << "</li>\n";
3662 }
3663 }
3664 out() << "</ul>\n";
3665 }
3666 }
3667 out() << "</li>\n";
3668 }
3669 out() << "</ul>\n";
3670 }
3671}
3672
3673/*!
3674 \brief Emits the <h3> header for a property group.
3675*/
3676void HtmlGenerator::emitGroupHeader(const SharedCommentNode *scn)
3677{
3678 out() << headingStart.arg(getClassAttr(scn, "fn qml-member qml-property-group"_L1),
3679 refForNode(scn))
3680 << "<b>" << scn->name() << " group</b>"
3681 << headingEnd;
3682}
3683
3684/*!
3685 Outputs the html detailed documentation for a section
3686 on a QML element reference page.
3687 */
3688void HtmlGenerator::generateDetailedQmlMember(Node *node, const Aggregate *relative,
3689 CodeMarker *marker)
3690{
3691 generateExtractionMark(node, MemberMark);
3692
3693 auto generateQmlProperty = [&](Node *n, bool isGroupItem = false) {
3694 const auto cssClasses = isGroupItem ? "fn qml-member qml-property fngroupitem"_L1 : "fn qml-member qml-property"_L1;
3695 out() << headingStart.arg(getClassAttr(n, cssClasses), refForNode(n));
3696 generateQmlItem(n, relative, marker, false);
3697 generateSourceLink(n);
3698 out() << headingEnd;
3699 };
3700
3701 auto generateQmlMethod = [&](Node *n, bool isGroupItem = false) {
3702 const auto cssClasses = isGroupItem ? "fn qml-member qml-method fngroupitem"_L1 : "fn qml-member qml-method"_L1;
3703 out() << headingStart.arg(getClassAttr(n, cssClasses), refForNode(n));
3704 generateSynopsis(n, relative, marker, Section::Details, false);
3705 generateSourceLink(n);
3706 out() << headingEnd;
3707 };
3708
3709 if (node->isSharedCommentNode()) {
3710 auto *scn = static_cast<const SharedCommentNode *>(node);
3711 const auto shared = scn->collective();
3712
3713 if (scn->isPropertyGroup() && !scn->name().isEmpty())
3714 emitGroupHeader(scn);
3715
3716 const bool isGroup = shared.size() > 1;
3717
3718 if (isGroup)
3719 out() << "<div class=\"fngroup\">\n"_L1;
3720
3721 for (auto *child : std::as_const(shared)) {
3722 if (child->isQmlProperty())
3723 generateQmlProperty(child, isGroup);
3724 else
3725 generateQmlMethod(child, isGroup);
3726 }
3727
3728 if (isGroup)
3729 out() << "</div>"_L1;
3730 out() << '\n';
3731 } else if (node->isQmlProperty()) {
3732 generateQmlProperty(node);
3733 } else { // assume the node is a method/signal handler
3734 generateQmlMethod(node);
3735 }
3736
3737 generateStatus(node, marker);
3738 generateBody(node, marker);
3739 generateThreadSafeness(node, marker);
3740 generateSince(node, marker);
3741 generateAlsoList(node, marker);
3742 generateExtractionMark(node, EndMark);
3743}
3744
3745void HtmlGenerator::generateExtractionMark(const Node *node, ExtractionMarkType markType)
3746{
3747 if (markType != EndMark) {
3748 out() << "<!-- $$$" + node->name();
3749 if (markType == MemberMark) {
3750 if (node->isFunction()) {
3751 const auto *func = static_cast<const FunctionNode *>(node);
3752 if (!func->hasAssociatedProperties()) {
3753 if (func->overloadNumber() == 0)
3754 out() << "[overload1]";
3755 out() << "$$$" + func->name() + func->parameters().rawSignature().remove(' ');
3756 }
3757 } else if (node->isProperty()) {
3758 out() << "-prop";
3759 const auto *prop = static_cast<const PropertyNode *>(node);
3760 const NodeList &list = prop->functions();
3761 for (const auto *propFuncNode : list) {
3762 if (propFuncNode->isFunction()) {
3763 const auto *func = static_cast<const FunctionNode *>(propFuncNode);
3764 out() << "$$$" + func->name()
3765 + func->parameters().rawSignature().remove(' ');
3766 }
3767 }
3768 } else if (node->isEnumType()) {
3769 const auto *enumNode = static_cast<const EnumNode *>(node);
3770 const auto &items = enumNode->items();
3771 for (const auto &item : items)
3772 out() << "$$$" + item.name();
3773 }
3774 } else if (markType == BriefMark) {
3775 out() << "-brief";
3776 } else if (markType == DetailedDescriptionMark) {
3777 out() << "-description";
3778 }
3779 out() << " -->\n";
3780 } else {
3781 out() << "<!-- @@@" + node->name() + " -->\n";
3782 }
3783}
3784
3785QT_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:32
const Location & location() const
Returns the starting location of a qdoc comment.
Definition doc.cpp:90
const Text & title() const
Definition doc.cpp:121
bool hasTableOfContents() const
Definition doc.cpp:286
static void quoteFromFile(const Location &location, Quoter &quoter, ResolvedFile resolved_file, CodeMarker *marker=nullptr)
Definition doc.cpp:462
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() const 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:54
void appendMembers(const NodeVector &nv)
Definition sections.h:55
const Aggregate * aggregate() const
Definition sections.h:56
const ClassNodesList & classNodesList() const
Definition sections.h:53
@ 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:46
Style style() const
Definition sections.h:41
bool isEmpty() const
Definition sections.h:35
A class for creating vectors of collections for documentation.
Definition sections.h:80
Aggregate * aggregate() const
Definition sections.h:160
Sections(Aggregate *aggregate)
This constructor builds the section vectors based on the type of the aggregate node.
Definition sections.cpp:371
SectionVector & summarySections()
Definition sections.h:150
SectionVector & sinceSections()
Definition sections.h:153
Sections(const NodeMultiMap &nsmap)
This constructor builds the since sections from the since node map, nsmap.
Definition sections.cpp:409
bool hasObsoleteMembers(SectionPtrVector *summary_spv, SectionPtrVector *details_spv) const
Returns true if any sections in this object contain obsolete members.
Definition sections.cpp:963
SectionVector & detailsSections()
Definition sections.h:151
const Section & allMembersSection() const
Definition sections.h:157
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:448
#define CONFIG_CPPCLASSESTITLE
Definition config.h:374
#define CONFIG_HOMETITLE
Definition config.h:394
#define CONFIG_HOMEPAGE
Definition config.h:393
#define CONFIG_URL
Definition config.h:447
#define CONFIG_CODEPREFIX
Definition config.h:371
#define CONFIG_QMLTYPESPAGE
Definition config.h:453
#define CONFIG_ENDHEADER
Definition config.h:380
#define CONFIG_HEADERSCRIPTS
Definition config.h:391
#define CONFIG_DESCRIPTION
Definition config.h:377
#define CONFIG_PROJECT
Definition config.h:425
#define CONFIG_CODEINDENT
Definition config.h:369
#define CONFIG_TRADEMARKSPAGE
Definition config.h:446
#define CONFIG_CPPCLASSESPAGE
Definition config.h:373
#define CONFIG_NATURALLANGUAGE
Definition config.h:416
#define CONFIG_PRODUCTNAME
Definition config.h:424
#define CONFIG_NAVIGATION
Definition config.h:417
#define CONFIG_BUILDVERSION
Definition config.h:368
#define CONFIG_LANDINGPAGE
Definition config.h:406
#define CONFIG_TAGFILE
Definition config.h:443
#define CONFIG_CODESUFFIX
Definition config.h:372
#define CONFIG_LANDINGTITLE
Definition config.h:407
#define CONFIG_HEADERSTYLES
Definition config.h:392
#define CONFIG_QMLTYPESTITLE
Definition config.h:454
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
QList< const Section * > SectionPtrVector
Definition sections.h:77
QList< Section > SectionVector
Definition sections.h:76
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.