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