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