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