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