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