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