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