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