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