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