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