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
docbookgenerator.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 Thibaut Cuvelier
2// Copyright (C) 2021 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
4
6
7#include "access.h"
8#include "aggregate.h"
9#include "classnode.h"
10#include "codemarker.h"
11#include "collectionnode.h"
13#include "config.h"
14#include "enumnode.h"
15#include "examplenode.h"
16#include "functionnode.h"
17#include "generator.h"
18#include "genustypes.h"
20#include "node.h"
21#include "nodecontext.h"
22#include "propertynode.h"
23#include "quoter.h"
24#include "qdocdatabase.h"
28#include "typedefnode.h"
29#include "utilities.h"
30#include "textutils.h"
31#include "variablenode.h"
32
33#include <QtCore/qlist.h>
34#include <QtCore/qmap.h>
35#include <QtCore/quuid.h>
36#include <QtCore/qurl.h>
37#include <QtCore/qregularexpression.h>
38#include <QtCore/qversionnumber.h>
39
40#include <cctype>
41
43
44using namespace Qt::StringLiterals;
45
46static const char dbNamespace[] = "http://docbook.org/ns/docbook";
47static const char xlinkNamespace[] = "http://www.w3.org/1999/xlink";
48static const char itsNamespace[] = "http://www.w3.org/2005/11/its";
49
51
52inline void DocBookGenerator::newLine()
53{
54 m_writer->writeCharacters("\n");
55}
56
57void DocBookGenerator::writeXmlId(const QString &id)
58{
59 if (id.isEmpty())
60 return;
61
62 m_writer->writeAttribute("xml:id", registerRef(id, true));
63}
64
65void DocBookGenerator::writeXmlId(const Node *node)
66{
67 if (!node)
68 return;
69
70 // Specifically for nodes, do not use the same code path as for QString
71 // inputs, as refForNode calls registerRef in all cases. Calling
72 // registerRef a second time adds a character to "disambiguate" the two IDs
73 // (the one returned by refForNode, then the one that is written as
74 // xml:id).
75 QString id = Generator::cleanRef(refForNode(node), true);
76 if (!id.isEmpty())
77 m_writer->writeAttribute("xml:id", id);
78}
79
80void DocBookGenerator::startSectionBegin(const QString &id)
81{
82 m_hasSection = true;
83
84 m_writer->writeStartElement(dbNamespace, "section");
85 writeXmlId(id);
86 newLine();
87 m_writer->writeStartElement(dbNamespace, "title");
88}
89
90void DocBookGenerator::startSectionBegin(const Node *node)
91{
92 m_writer->writeStartElement(dbNamespace, "section");
93 writeXmlId(node);
94 newLine();
95 m_writer->writeStartElement(dbNamespace, "title");
96}
97
98void DocBookGenerator::startSectionEnd()
99{
100 m_writer->writeEndElement(); // title
101 newLine();
102}
103
104void DocBookGenerator::startSection(const QString &id, const QString &title)
105{
106 startSectionBegin(id);
107 m_writer->writeCharacters(title);
108 startSectionEnd();
109}
110
111void DocBookGenerator::startSection(const Node *node, const QString &title)
112{
113 startSectionBegin(node);
114 m_writer->writeCharacters(title);
115 startSectionEnd();
116}
117
118void DocBookGenerator::startSection(const QString &title)
119{
120 // No xml:id given: down the calls, "" is interpreted as "no ID".
121 startSection("", title);
122}
123
124void DocBookGenerator::endSection()
125{
126 m_writer->writeEndElement(); // section
127 newLine();
128}
129
130void DocBookGenerator::writeAnchor(const QString &id)
131{
132 if (id.isEmpty())
133 return;
134
135 m_writer->writeEmptyElement(dbNamespace, "anchor");
136 writeXmlId(id);
137 newLine();
138}
139
140/*!
141 Initializes the DocBook output generator's data structures
142 from the configuration (Config).
143 */
145{
146 // Excerpts from HtmlGenerator::initializeGenerator.
148 m_config = &Config::instance();
149
150 m_project = m_config->get(CONFIG_PROJECT).asString();
151 m_productName = m_config->get(CONFIG_PRODUCTNAME).asString();
152
153 m_projectDescription = m_config->get(CONFIG_DESCRIPTION).asString();
154 if (m_projectDescription.isEmpty() && !m_project.isEmpty())
155 m_projectDescription = m_project + QLatin1String(" Reference Documentation");
156
157 m_naturalLanguage = m_config->get(CONFIG_NATURALLANGUAGE).asString();
158 if (m_naturalLanguage.isEmpty())
159 m_naturalLanguage = QLatin1String("en");
160
161 m_buildVersion = m_config->get(CONFIG_BUILDVERSION).asString();
162 m_useDocBook52 = m_config->get(CONFIG_DOCBOOKEXTENSIONS).asBool() ||
163 m_config->get(format() + Config::dot + "usedocbookextensions").asBool();
164 m_useITS = m_config->get(format() + Config::dot + "its").asBool();
165}
166
168{
169 return "DocBook";
170}
171
172/*!
173 Returns "xml" for this subclass of Generator.
174 */
176{
177 return "xml";
178}
179
180/*!
181 Generate the documentation for \a relative. i.e. \a relative
182 is the node that represents the entity where a qdoc comment
183 was found, and \a text represents the qdoc comment.
184 */
185bool DocBookGenerator::generateText(const Text &text, const Node *relative)
186{
187 // From Generator::generateText.
188 if (!text.firstAtom())
189 return false;
190
191 int numAtoms = 0;
193 generateAtomList(text.firstAtom(), relative, nullptr, true, numAtoms);
195 return true;
196}
197
198QString removeCodeMarkers(const QString& code) {
199 QString rewritten = code;
200 static const QRegularExpression re("(<@[^>&]*>)|(<\\/@[^&>]*>)");
201 rewritten.replace(re, "");
202 return rewritten;
203}
204
205/*!
206 Generate DocBook from an instance of Atom.
207 */
209{
210 Q_ASSERT(m_writer);
211 // From HtmlGenerator::generateAtom, without warning generation.
212 qsizetype idx = 0;
213 qsizetype skipAhead = 0;
214 Genus genus = Genus::DontCare;
215
216 switch (atom->type()) {
217 case Atom::AutoLink:
218 // Allow auto-linking to nodes in API reference
219 genus = Genus::API;
220 Q_FALLTHROUGH();
222 if (!m_inLink && !m_inContents && !m_inSectionHeading) {
223 const Node *node = nullptr;
224 QString link = getAutoLink(atom, relative, &node, genus);
225 if (!link.isEmpty() && node && node->isDeprecated()
226 && relative->parent() != node && !relative->isDeprecated()) {
227 link.clear();
228 }
229 if (link.isEmpty()) {
230 m_writer->writeCharacters(atom->string());
231 } else {
232 beginLink(link, node, relative);
233 generateLink(atom);
234 endLink();
235 }
236 } else {
237 m_writer->writeCharacters(atom->string());
238 }
239 break;
240 case Atom::BaseName:
241 break;
242 case Atom::BriefLeft:
243 if (!hasBrief(relative)) {
244 skipAhead = skipAtoms(atom, Atom::BriefRight);
245 break;
246 }
247 m_writer->writeStartElement(dbNamespace, "para");
248 m_inPara = true;
249 rewritePropertyBrief(atom, relative);
250 break;
251 case Atom::BriefRight:
252 if (hasBrief(relative)) {
253 m_writer->writeEndElement(); // para
254 m_inPara = false;
255 newLine();
256 }
257 break;
258 case Atom::C:
259 // This may at one time have been used to mark up C++ code but it is
260 // now widely used to write teletype text. As a result, text marked
261 // with the \c command is not passed to a code marker.
262 if (m_inTeletype)
263 m_writer->writeCharacters(plainCode(atom->string()));
264 else
265 m_writer->writeTextElement(dbNamespace, "code", plainCode(atom->string()));
266 break;
267 case Atom::CaptionLeft:
268 m_writer->writeStartElement(dbNamespace, "title");
269 break;
271 endLink();
272 m_writer->writeEndElement(); // title
273 newLine();
274 break;
275 case Atom::Qml:
276 m_writer->writeStartElement(dbNamespace, "programlisting");
277 m_writer->writeAttribute("language", "qml");
278 if (m_useITS)
279 m_writer->writeAttribute(itsNamespace, "translate", "no");
280 m_writer->writeCharacters(plainCode(removeCodeMarkers(atom->string())));
281 m_writer->writeEndElement(); // programlisting
282 newLine();
283 break;
284 case Atom::Code:
285 m_writer->writeStartElement(dbNamespace, "programlisting");
286 // Recover an additional string containing the code language, if present.
287 if (atom->strings().count() == 2)
288 m_writer->writeAttribute("language", atom->string(1));
289 else
290 m_writer->writeAttribute("language", "cpp");
291 if (m_useITS)
292 m_writer->writeAttribute(itsNamespace, "translate", "no");
293 m_writer->writeCharacters(plainCode(removeCodeMarkers(atom->string())));
294 m_writer->writeEndElement(); // programlisting
295 newLine();
296 break;
297 case Atom::CodeBad:
298 m_writer->writeStartElement(dbNamespace, "programlisting");
299 m_writer->writeAttribute("language", "cpp");
300 m_writer->writeAttribute("role", "bad");
301 if (m_useITS)
302 m_writer->writeAttribute(itsNamespace, "translate", "no");
303 m_writer->writeCharacters(plainCode(removeCodeMarkers(atom->string())));
304 m_writer->writeEndElement(); // programlisting
305 newLine();
306 break;
309 break;
310 case Atom::DetailsSummaryLeft: { // Ignore/skip \details summary
311 qsizetype offset{ 0 };
312 std::ignore = atom->find(Atom::DetailsSummaryRight, &offset);
313 skipAhead += offset;
314 } break;
316 break;
317 case Atom::DivLeft:
318 case Atom::DivRight:
319 break;
320 case Atom::FootnoteLeft:
321 m_writer->writeStartElement(dbNamespace, "footnote");
322 newLine();
323 m_writer->writeStartElement(dbNamespace, "para");
324 m_inPara = true;
325 break;
326 case Atom::FootnoteRight:
327 m_writer->writeEndElement(); // para
328 m_inPara = false;
329 newLine();
330 m_writer->writeEndElement(); // footnote
331 break;
332 case Atom::FormatElse:
334 case Atom::FormatIf:
335 break;
337 if (atom->string() == ATOM_FORMATTING_BOLD) {
338 m_writer->writeStartElement(dbNamespace, "emphasis");
339 m_writer->writeAttribute("role", "bold");
340 } else if (atom->string() == ATOM_FORMATTING_ITALIC) {
341 m_writer->writeStartElement(dbNamespace, "emphasis");
342 } else if (atom->string() == ATOM_FORMATTING_UNDERLINE) {
343 m_writer->writeStartElement(dbNamespace, "emphasis");
344 m_writer->writeAttribute("role", "underline");
345 } else if (atom->string() == ATOM_FORMATTING_SUBSCRIPT) {
346 m_writer->writeStartElement(dbNamespace, "subscript");
347 } else if (atom->string() == ATOM_FORMATTING_SUPERSCRIPT) {
348 m_writer->writeStartElement(dbNamespace, "superscript");
349 } else if (atom->string() == ATOM_FORMATTING_TELETYPE
350 || atom->string() == ATOM_FORMATTING_PARAMETER) {
351 m_writer->writeStartElement(dbNamespace, "code");
352 if (m_useITS)
353 m_writer->writeAttribute(itsNamespace, "translate", "no");
354
355 if (atom->string() == ATOM_FORMATTING_PARAMETER)
356 m_writer->writeAttribute("role", "parameter");
357 else // atom->string() == ATOM_FORMATTING_TELETYPE
358 m_inTeletype = true;
359 } else if (atom->string() == ATOM_FORMATTING_UICONTROL) {
360 m_writer->writeStartElement(dbNamespace, "guilabel");
361 if (m_useITS)
362 m_writer->writeAttribute(itsNamespace, "translate", "no");
363 } else if (atom->string() == ATOM_FORMATTING_TRADEMARK) {
364 m_writer->writeStartElement(dbNamespace,
365 appendTrademark(atom->find(Atom::FormattingRight)) ?
366 "trademark" : "phrase");
367 if (m_useITS)
368 m_writer->writeAttribute(itsNamespace, "translate", "no");
369 } else if (atom->string() == ATOM_FORMATTING_NOTRANSLATE) {
370 m_writer->writeStartElement(dbNamespace, "phrase");
371 if (m_useITS)
372 m_writer->writeAttribute(itsNamespace, "translate", "no");
373 } else {
374 relative->location().warning(QStringLiteral("Unsupported formatting: %1").arg(atom->string()));
375 }
376 break;
378 if (atom->string() == ATOM_FORMATTING_BOLD || atom->string() == ATOM_FORMATTING_ITALIC
379 || atom->string() == ATOM_FORMATTING_UNDERLINE
380 || atom->string() == ATOM_FORMATTING_SUBSCRIPT
381 || atom->string() == ATOM_FORMATTING_SUPERSCRIPT
382 || atom->string() == ATOM_FORMATTING_TELETYPE
383 || atom->string() == ATOM_FORMATTING_PARAMETER
384 || atom->string() == ATOM_FORMATTING_UICONTROL
385 || atom->string() == ATOM_FORMATTING_TRADEMARK
386 || atom->string() == ATOM_FORMATTING_NOTRANSLATE) {
387 m_writer->writeEndElement();
388 } else if (atom->string() == ATOM_FORMATTING_LINK) {
389 if (atom->string() == ATOM_FORMATTING_TELETYPE)
390 m_inTeletype = false;
391 endLink();
392 } else {
393 relative->location().warning(QStringLiteral("Unsupported formatting: %1").arg(atom->string()));
394 }
395 break;
396 case Atom::AnnotatedList: {
397 if (const CollectionNode *cn = m_qdb->getCollectionNode(atom->string(), NodeType::Group))
398 generateList(cn, atom->string(), Generator::sortOrder(atom->strings().last()));
399 } break;
400 case Atom::GeneratedList: {
401 const auto sortOrder{Generator::sortOrder(atom->strings().last())};
402 bool hasGeneratedSomething = false;
403 if (atom->string() == QLatin1String("annotatedclasses")
404 || atom->string() == QLatin1String("attributions")
405 || atom->string() == QLatin1String("namespaces")) {
406 const NodeMultiMap things = atom->string() == QLatin1String("annotatedclasses")
407 ? m_qdb->getCppClasses()
408 : atom->string() == QLatin1String("attributions") ? m_qdb->getAttributions()
409 : m_qdb->getNamespaces();
410 generateAnnotatedList(relative, things.values(), atom->string(), Auto, sortOrder);
411 hasGeneratedSomething = !things.isEmpty();
412 } else if (atom->string() == QLatin1String("annotatedexamples")
413 || atom->string() == QLatin1String("annotatedattributions")) {
414 const NodeMultiMap things = atom->string() == QLatin1String("annotatedexamples")
415 ? m_qdb->getAttributions()
416 : m_qdb->getExamples();
417 generateAnnotatedLists(relative, things, atom->string());
418 hasGeneratedSomething = !things.isEmpty();
419 } else if (atom->string() == QLatin1String("classes")
420 || atom->string() == QLatin1String("qmlbasictypes") // deprecated!
421 || atom->string() == QLatin1String("qmlvaluetypes")
422 || atom->string() == QLatin1String("qmltypes")) {
423 const NodeMultiMap things = atom->string() == QLatin1String("classes")
424 ? m_qdb->getCppClasses()
425 : (atom->string() == QLatin1String("qmlvaluetypes")
426 || atom->string() == QLatin1String("qmlbasictypes"))
427 ? m_qdb->getQmlValueTypes()
428 : m_qdb->getQmlTypes();
429 generateCompactList(relative, things, true, QString(), atom->string());
430 hasGeneratedSomething = !things.isEmpty();
431 } else if (atom->string().contains("classes ")) {
432 QString rootName = atom->string().mid(atom->string().indexOf("classes") + 7).trimmed();
433 NodeMultiMap things = m_qdb->getCppClasses();
434
435 hasGeneratedSomething = !things.isEmpty();
436 generateCompactList(relative, things, true, rootName, atom->string());
437 } else if ((idx = atom->string().indexOf(QStringLiteral("bymodule"))) != -1) {
438 QString moduleName = atom->string().mid(idx + 8).trimmed();
439 NodeType moduleType = typeFromString(atom);
441 if (const CollectionNode *cn = qdb->getCollectionNode(moduleName, moduleType)) {
442 NodeMap map;
443 switch (moduleType) {
444 case NodeType::Module:
445 // classesbymodule <module_name>
446 map = cn->getMembers([](const Node *n){ return n->isClassNode(); });
447 break;
449 if (atom->string().contains(QLatin1String("qmlvaluetypes")))
450 map = cn->getMembers(NodeType::QmlValueType); // qmlvaluetypesbymodule <module_name>
451 else
452 map = cn->getMembers(NodeType::QmlType); // qmltypesbymodule <module_name>
453 break;
454 default: // fall back to generating all members
455 generateAnnotatedList(relative, cn->members(), atom->string(), Auto, sortOrder);
456 hasGeneratedSomething = !cn->members().isEmpty();
457 break;
458 }
459 if (!map.isEmpty()) {
460 generateAnnotatedList(relative, map.values(), atom->string(), Auto, sortOrder);
461 hasGeneratedSomething = true;
462 }
463 }
464 } else if (atom->string() == QLatin1String("classhierarchy")) {
465 generateClassHierarchy(relative, m_qdb->getCppClasses());
466 hasGeneratedSomething = !m_qdb->getCppClasses().isEmpty();
467 } else if (atom->string().startsWith("obsolete")) {
468 QString prefix = atom->string().contains("cpp") ? QStringLiteral("Q") : QString();
469 const NodeMultiMap &things = atom->string() == QLatin1String("obsoleteclasses")
470 ? m_qdb->getObsoleteClasses()
471 : atom->string() == QLatin1String("obsoleteqmltypes")
472 ? m_qdb->getObsoleteQmlTypes()
473 : atom->string() == QLatin1String("obsoletecppmembers")
474 ? m_qdb->getClassesWithObsoleteMembers()
475 : m_qdb->getQmlTypesWithObsoleteMembers();
476 generateCompactList(relative, things, false, prefix, atom->string());
477 hasGeneratedSomething = !things.isEmpty();
478 } else if (atom->string() == QLatin1String("functionindex")) {
479 generateFunctionIndex(relative);
480 hasGeneratedSomething = !m_qdb->getFunctionIndex().isEmpty();
481 } else if (atom->string() == QLatin1String("legalese")) {
482 generateLegaleseList(relative);
483 hasGeneratedSomething = !m_qdb->getLegaleseTexts().isEmpty();
484 } else if (atom->string() == QLatin1String("overviews")
485 || atom->string() == QLatin1String("cpp-modules")
486 || atom->string() == QLatin1String("qml-modules")
487 || atom->string() == QLatin1String("related")) {
488 generateList(relative, atom->string());
489 hasGeneratedSomething = true; // Approximation, because there is
490 // some nontrivial logic in generateList.
491 } else if (const auto *cn = m_qdb->getCollectionNode(atom->string(), NodeType::Group); cn) {
492 generateAnnotatedList(cn, cn->members(), atom->string(), ItemizedList, sortOrder);
493 hasGeneratedSomething = true; // Approximation
494 }
495
496 // There must still be some content generated for the DocBook document
497 // to be valid (except if already in a paragraph).
498 if (!hasGeneratedSomething && !m_inPara) {
499 m_writer->writeEmptyElement(dbNamespace, "para");
500 newLine();
501 }
502 }
503 break;
504 case Atom::SinceList:
505 // Table of contents, should automatically be generated by the DocBook processor.
506 Q_FALLTHROUGH();
507 case Atom::LineBreak:
508 case Atom::BR:
509 case Atom::HR:
510 // Not supported in DocBook.
511 break;
512 case Atom::Image: // mediaobject
513 // An Image atom is always followed by an ImageText atom,
514 // containing the alternative text.
515 // If no caption is present, we just output a <db:mediaobject>,
516 // avoiding the wrapper as it is not required.
517 // For bordered images, there is another atom before the
518 // caption, DivRight (the corresponding DivLeft being just
519 // before the image).
520
523 // If there is a caption, there must be a <db:figure>
524 // wrapper starting with the caption.
525 Q_ASSERT(atom->next());
526 Q_ASSERT(atom->next()->next());
527 Q_ASSERT(atom->next()->next()->next());
530
531 m_writer->writeStartElement(dbNamespace, "figure");
532 newLine();
533
534 const Atom *current = atom->next()->next()->next();
535 skipAhead += 2;
536
537 Q_ASSERT(current->type() == Atom::CaptionLeft);
538 generateAtom(current, relative, nullptr);
539 current = current->next();
540 ++skipAhead;
541
542 while (current->type() != Atom::CaptionRight) { // The actual caption.
543 generateAtom(current, relative, nullptr);
544 current = current->next();
545 ++skipAhead;
546 }
547
548 Q_ASSERT(current->type() == Atom::CaptionRight);
549 generateAtom(current, relative, nullptr);
550 current = current->next();
551 ++skipAhead;
552
553 m_closeFigureWrapper = true;
554 }
555
557 // If there is a caption, there must be a <db:figure>
558 // wrapper starting with the caption.
559 Q_ASSERT(atom->next());
560 Q_ASSERT(atom->next()->next());
561 Q_ASSERT(atom->next()->next()->next());
563
564 m_writer->writeStartElement(dbNamespace, "figure");
565 newLine();
566
567 const Atom *current = atom->next()->next();
568 ++skipAhead;
569
570 Q_ASSERT(current->type() == Atom::CaptionLeft);
571 generateAtom(current, relative, nullptr);
572 current = current->next();
573 ++skipAhead;
574
575 while (current->type() != Atom::CaptionRight) { // The actual caption.
576 generateAtom(current, relative, nullptr);
577 current = current->next();
578 ++skipAhead;
579 }
580
581 Q_ASSERT(current->type() == Atom::CaptionRight);
582 generateAtom(current, relative, nullptr);
583 current = current->next();
584 ++skipAhead;
585
586 m_closeFigureWrapper = true;
587 }
588
589 Q_FALLTHROUGH();
590 case Atom::InlineImage: { // inlinemediaobject
591 // TODO: [generator-insufficient-structural-abstraction]
592 // The structure of the computations for this part of the
593 // docbook generation and the same parts in other format
594 // generators is the same.
595 //
596 // The difference, instead, lies in what the generated output
597 // is like. A correct abstraction for a generator would take
598 // this structural equivalence into account and encapsulate it
599 // into a driver for the format generators.
600 //
601 // This would avoid the replication of content, and the
602 // subsequent friction for changes and desynchronization
603 // between generators.
604 //
605 // Review all the generators routines and find the actual
606 // skeleton that is shared between them, then consider it when
607 // extracting the logic for the generation phase.
608 QString tag = atom->type() == Atom::Image ? "mediaobject" : "inlinemediaobject";
609 m_writer->writeStartElement(dbNamespace, tag);
610 newLine();
611
612 auto maybe_resolved_file{file_resolver.resolve(atom->string())};
613 if (!maybe_resolved_file) {
614 // TODO: [uncetnralized-admonition][failed-resolve-file]
615 relative->location().warning(QStringLiteral("Missing image: %1").arg(atom->string()));
616
617 m_writer->writeStartElement(dbNamespace, "textobject");
618 newLine();
619 m_writer->writeStartElement(dbNamespace, "para");
620 m_writer->writeTextElement(dbNamespace, "emphasis",
621 "[Missing image " + atom->string() + "]");
622 m_writer->writeEndElement(); // para
623 newLine();
624 m_writer->writeEndElement(); // textobject
625 newLine();
626 } else {
627 ResolvedFile file{*maybe_resolved_file};
628 QString file_name{QFileInfo{file.get_path()}.fileName()};
629
630 // TODO: [uncentralized-output-directory-structure]
631 Config::copyFile(relative->doc().location(), file.get_path(), file_name,
632 "%1/%2"_L1.arg(outputDir(), imagesOutputDir()));
633
634 if (atom->next() && !atom->next()->string().isEmpty()
636 m_writer->writeTextElement(dbNamespace, "alt", atom->next()->string());
637 newLine();
638 }
639
640 m_writer->writeStartElement(dbNamespace, "imageobject");
641 newLine();
642 m_writer->writeEmptyElement(dbNamespace, "imagedata");
643 const auto &imgPath = "%1/%2"_L1.arg(imagesOutputDir(), file_name);
644 // TODO: [uncentralized-output-directory-structure]
645 m_writer->writeAttribute("fileref", imgPath);
646 newLine();
647 m_writer->writeEndElement(); // imageobject
648 newLine();
649
650 // TODO: [uncentralized-output-directory-structure]
651 setImageFileName(relative, imgPath);
652 }
653
654 m_writer->writeEndElement(); // [inline]mediaobject
655 if (atom->type() == Atom::Image)
656 newLine();
657
658 if (m_closeFigureWrapper) {
659 m_writer->writeEndElement(); // figure
660 newLine();
661 m_closeFigureWrapper = false;
662 }
663 } break;
664 case Atom::ImageText:
665 break;
667 case Atom::NoteLeft:
668 case Atom::WarningLeft: {
669 QString admonType = atom->typeString().toLower();
670 // Remove 'Left' to get the admonition type
671 admonType.chop(4);
672 m_writer->writeStartElement(dbNamespace, admonType);
673 newLine();
674 m_writer->writeStartElement(dbNamespace, "para");
675 m_inPara = true;
676 } break;
677 case Atom::ImportantRight:
678 case Atom::NoteRight:
679 case Atom::WarningRight:
680 m_writer->writeEndElement(); // para
681 m_inPara = false;
682 newLine();
683 m_writer->writeEndElement(); // note/important
684 newLine();
685 break;
688 break;
689 case Atom::Link:
690 case Atom::NavLink: {
691 const Node *node = nullptr;
692 QString link = getLink(atom, relative, &node);
693 beginLink(link, node, relative); // Ended at Atom::FormattingRight
694 skipAhead = 1;
695 } break;
696 case Atom::LinkNode: {
697 const Node *node = static_cast<const Node*>(Utilities::nodeForString(atom->string()));
698 beginLink(linkForNode(node, relative), node, relative);
699 skipAhead = 1;
700 } break;
701 case Atom::ListLeft:
702 if (m_inPara) {
703 // The variable m_inPara is not set in a very smart way, because
704 // it ignores nesting. This might in theory create false positives
705 // here. A better solution would be to track the depth of
706 // paragraphs the generator is in, but determining the right check
707 // for this condition is far from trivial (think of nested lists).
708 m_writer->writeEndElement(); // para
709 newLine();
710 m_inPara = false;
711 }
712
713 if (atom->string() == ATOM_LIST_BULLET) {
714 m_writer->writeStartElement(dbNamespace, "itemizedlist");
715 newLine();
716 } else if (atom->string() == ATOM_LIST_TAG) {
717 m_writer->writeStartElement(dbNamespace, "variablelist");
718 newLine();
719 } else if (atom->string() == ATOM_LIST_VALUE) {
720 m_writer->writeStartElement(dbNamespace, "informaltable");
721 newLine();
722 m_writer->writeStartElement(dbNamespace, "thead");
723 newLine();
724 m_writer->writeStartElement(dbNamespace, "tr");
725 newLine();
726 m_writer->writeTextElement(dbNamespace, "th", "Constant");
727 newLine();
728
729 m_threeColumnEnumValueTable = isThreeColumnEnumValueTable(atom);
730 if (m_threeColumnEnumValueTable && relative->isEnumType(Genus::CPP)) {
731 // With three columns, if not in \enum topic, skip the value column
732 m_writer->writeTextElement(dbNamespace, "th", "Value");
733 newLine();
734 }
735
736 if (!isOneColumnValueTable(atom)) {
737 m_writer->writeTextElement(dbNamespace, "th", "Description");
738 newLine();
739 }
740
741 m_writer->writeEndElement(); // tr
742 newLine();
743 m_writer->writeEndElement(); // thead
744 newLine();
745 } else { // No recognized list type.
746 m_writer->writeStartElement(dbNamespace, "orderedlist");
747
748 if (atom->next() != nullptr && atom->next()->string().toInt() != 1)
749 m_writer->writeAttribute("startingnumber", atom->next()->string());
750
751 if (atom->string() == ATOM_LIST_UPPERALPHA)
752 m_writer->writeAttribute("numeration", "upperalpha");
753 else if (atom->string() == ATOM_LIST_LOWERALPHA)
754 m_writer->writeAttribute("numeration", "loweralpha");
755 else if (atom->string() == ATOM_LIST_UPPERROMAN)
756 m_writer->writeAttribute("numeration", "upperroman");
757 else if (atom->string() == ATOM_LIST_LOWERROMAN)
758 m_writer->writeAttribute("numeration", "lowerroman");
759 else // (atom->string() == ATOM_LIST_NUMERIC)
760 m_writer->writeAttribute("numeration", "arabic");
761
762 newLine();
763 }
764 m_inList++;
765 break;
767 break;
769 if (atom->string() == ATOM_LIST_TAG) {
770 m_writer->writeStartElement(dbNamespace, "varlistentry");
771 newLine();
772 m_writer->writeStartElement(dbNamespace, "item");
773 } else { // (atom->string() == ATOM_LIST_VALUE)
774 std::pair<QString, int> pair = getAtomListValue(atom);
775 skipAhead = pair.second;
776
777 m_writer->writeStartElement(dbNamespace, "tr");
778 newLine();
779 m_writer->writeStartElement(dbNamespace, "td");
780 newLine();
781 m_writer->writeStartElement(dbNamespace, "para");
782 if (m_useITS)
783 m_writer->writeAttribute(itsNamespace, "translate", "no");
784 generateEnumValue(pair.first, relative);
785 m_writer->writeEndElement(); // para
786 newLine();
787 m_writer->writeEndElement(); // td
788 newLine();
789
790 if (relative->isEnumType(Genus::CPP)) {
791 const auto enume = static_cast<const EnumNode *>(relative);
792 QString itemValue = enume->itemValue(atom->next()->string());
793
794 m_writer->writeStartElement(dbNamespace, "td");
795 if (itemValue.isEmpty())
796 m_writer->writeCharacters("?");
797 else {
798 m_writer->writeStartElement(dbNamespace, "code");
799 if (m_useITS)
800 m_writer->writeAttribute(itsNamespace, "translate", "no");
801 m_writer->writeCharacters(itemValue);
802 m_writer->writeEndElement(); // code
803 }
804 m_writer->writeEndElement(); // td
805 newLine();
806 }
807 }
808 m_inList++;
809 break;
811 if (atom->string() == ATOM_LIST_TAG) {
812 m_writer->writeEndElement(); // item
813 newLine();
814 }
815 break;
817 if (m_inList > 0 && atom->string() == ATOM_LIST_TAG) {
818 m_writer->writeEndElement(); // item
819 newLine();
820 m_inList = false;
821 }
822 break;
824 if (m_inList > 0) {
825 m_inListItemLineOpen = false;
826 if (atom->string() == ATOM_LIST_TAG) {
827 m_writer->writeStartElement(dbNamespace, "listitem");
828 newLine();
829 m_writer->writeStartElement(dbNamespace, "para");
830 m_inPara = true;
831 } else if (atom->string() == ATOM_LIST_VALUE) {
832 if (m_threeColumnEnumValueTable) {
834 m_writer->writeEmptyElement(dbNamespace, "td");
835 newLine();
836 m_inListItemLineOpen = false;
837 } else {
838 m_writer->writeStartElement(dbNamespace, "td");
839 newLine();
840 m_inListItemLineOpen = true;
841 }
842 }
843 } else {
844 m_writer->writeStartElement(dbNamespace, "listitem");
845 newLine();
846 }
847 // Don't skip a paragraph, DocBook requires them within list items.
848 }
849 break;
851 if (m_inList > 0) {
852 if (atom->string() == ATOM_LIST_TAG) {
853 m_writer->writeEndElement(); // para
854 m_inPara = false;
855 newLine();
856 m_writer->writeEndElement(); // listitem
857 newLine();
858 m_writer->writeEndElement(); // varlistentry
859 newLine();
860 } else if (atom->string() == ATOM_LIST_VALUE) {
861 if (m_inListItemLineOpen) {
862 m_writer->writeEndElement(); // td
863 newLine();
864 m_inListItemLineOpen = false;
865 }
866 m_writer->writeEndElement(); // tr
867 newLine();
868 } else {
869 m_writer->writeEndElement(); // listitem
870 newLine();
871 }
872 }
873 break;
874 case Atom::ListRight:
875 // Depending on atom->string(), closing a different item:
876 // - ATOM_LIST_BULLET: itemizedlist
877 // - ATOM_LIST_TAG: variablelist
878 // - ATOM_LIST_VALUE: informaltable
879 // - ATOM_LIST_NUMERIC: orderedlist
880 m_writer->writeEndElement();
881 newLine();
882 m_inList--;
883 break;
884 case Atom::Nop:
885 break;
886 case Atom::ParaLeft:
887 m_writer->writeStartElement(dbNamespace, "para");
888 m_inPara = true;
889 break;
890 case Atom::ParaRight:
891 endLink();
892 if (m_inPara) {
893 m_writer->writeEndElement(); // para
894 newLine();
895 m_inPara = false;
896 }
897 break;
898 case Atom::QuotationLeft:
899 m_writer->writeStartElement(dbNamespace, "blockquote");
900 m_inBlockquote = true;
901 break;
902 case Atom::QuotationRight:
903 m_writer->writeEndElement(); // blockquote
904 newLine();
905 m_inBlockquote = false;
906 break;
907 case Atom::RawString: {
908 m_writer->device()->write(atom->string().toUtf8());
909 }
910 break;
912 m_hasSection = true;
913
914 currentSectionLevel = atom->string().toInt() + hOffset(relative);
915 // Level 1 is dealt with at the header level (info tag).
916 if (currentSectionLevel > 1) {
917 // Unfortunately, SectionRight corresponds to the end of any section,
918 // i.e. going to a new section, even deeper.
919 while (!sectionLevels.empty() && sectionLevels.top() >= currentSectionLevel) {
920 sectionLevels.pop();
921 m_writer->writeEndElement(); // section
922 newLine();
923 }
924
925 sectionLevels.push(currentSectionLevel);
926
927 m_writer->writeStartElement(dbNamespace, "section");
928 writeXmlId(Tree::refForAtom(atom));
929 newLine();
930 // Unlike startSectionBegin, don't start a title here.
931 }
932
938 // A lonely section at the end of the document indicates that a
939 // generated list of some sort should be within this section.
940 // Close this section later on, in generateFooter().
941 generateAtom(atom->next(), relative, nullptr);
942 generateAtom(atom->next()->next(), relative, nullptr);
943 generateAtom(atom->next()->next()->next(), relative, nullptr);
944
945 m_closeSectionAfterGeneratedList = true;
946 skipAhead += 4;
947 sectionLevels.pop();
948 }
949
951 // No section title afterwards, make one up. This likely indicates a problem in the original documentation.
952 m_writer->writeTextElement(dbNamespace, "title", "");
953 }
954 break;
956 // All the logic about closing sections is done in the SectionLeft case
957 // and generateFooter() for the end of the page.
958 break;
960 // Level 1 is dealt with at the header level (info tag).
961 if (currentSectionLevel > 1) {
962 m_writer->writeStartElement(dbNamespace, "title");
963 m_inSectionHeading = true;
964 }
965 break;
967 // Level 1 is dealt with at the header level (info tag).
968 if (currentSectionLevel > 1) {
969 m_writer->writeEndElement(); // title
970 newLine();
971 m_inSectionHeading = false;
972 }
973 break;
974 case Atom::SidebarLeft:
975 m_writer->writeStartElement(dbNamespace, "sidebar");
976 break;
977 case Atom::SidebarRight:
978 m_writer->writeEndElement(); // sidebar
979 newLine();
980 break;
981 case Atom::String:
982 if (m_inLink && !m_inContents && !m_inSectionHeading)
983 generateLink(atom);
984 else
985 m_writer->writeCharacters(atom->string());
986 break;
987 case Atom::TableLeft: {
988 std::pair<QString, QString> pair = getTableWidthAttr(atom);
989 QString attr = pair.second;
990 QString width = pair.first;
991
992 if (m_inPara) {
993 m_writer->writeEndElement(); // para or blockquote
994 newLine();
995 m_inPara = false;
996 }
997
998 m_tableHeaderAlreadyOutput = false;
999
1000 m_writer->writeStartElement(dbNamespace, "informaltable");
1001 m_writer->writeAttribute("style", attr);
1002 if (!width.isEmpty())
1003 m_writer->writeAttribute("width", width);
1004 newLine();
1005 } break;
1006 case Atom::TableRight:
1007 m_tableWidthAttr = {"", ""};
1008 m_writer->writeEndElement(); // table
1009 newLine();
1010 break;
1011 case Atom::TableHeaderLeft: {
1013 ++skipAhead;
1014 break;
1015 }
1016
1017 if (m_tableHeaderAlreadyOutput) {
1018 // Headers are only allowed at the beginning of the table: close
1019 // the table and reopen one.
1020 m_writer->writeEndElement(); // table
1021 newLine();
1022
1023 const QString &attr = m_tableWidthAttr.second;
1024 const QString &width = m_tableWidthAttr.first;
1025
1026 m_writer->writeStartElement(dbNamespace, "informaltable");
1027 m_writer->writeAttribute("style", attr);
1028 if (!width.isEmpty())
1029 m_writer->writeAttribute("width", width);
1030 newLine();
1031 } else {
1032 m_tableHeaderAlreadyOutput = true;
1033 }
1034
1035 const Atom *next = atom->next();
1036 QString id{""};
1037 if (matchAhead(atom, Atom::Target)) {
1038 id = TextUtils::asAsciiPrintable(next->string());
1039 next = next->next();
1040 ++skipAhead;
1041 }
1042
1043 m_writer->writeStartElement(dbNamespace, "thead");
1044 newLine();
1045 m_writer->writeStartElement(dbNamespace, "tr");
1046 writeXmlId(id);
1047 newLine();
1048 m_inTableHeader = true;
1049
1051 m_closeTableCell = true;
1052 m_writer->writeStartElement(dbNamespace, "td");
1053 newLine();
1054 }
1055 }
1056 break;
1058 if (m_closeTableCell) {
1059 m_closeTableCell = false;
1060 m_writer->writeEndElement(); // td
1061 newLine();
1062 }
1063
1064 m_writer->writeEndElement(); // tr
1065 newLine();
1067 skipAhead = 1;
1068 m_writer->writeStartElement(dbNamespace, "tr");
1069 newLine();
1070 } else {
1071 m_writer->writeEndElement(); // thead
1072 newLine();
1073 m_inTableHeader = false;
1074 }
1075 break;
1076 case Atom::TableRowLeft: {
1078 skipAhead = 1;
1079 break;
1080 }
1081
1082 QString id{""};
1083 bool hasTarget {false};
1084 if (matchAhead(atom, Atom::Target)) {
1085 id = TextUtils::asAsciiPrintable(atom->next()->string());
1086 ++skipAhead;
1087 hasTarget = true;
1088 }
1089
1090 m_writer->writeStartElement(dbNamespace, "tr");
1091 writeXmlId(id);
1092
1093 if (atom->string().isEmpty()) {
1094 m_writer->writeAttribute("valign", "top");
1095 } else {
1096 // Basic parsing of attributes, should be enough. The input string (atom->string())
1097 // looks like:
1098 // arg1="val1" arg2="val2"
1099 QStringList args = atom->string().split("\"", Qt::SkipEmptyParts);
1100 // arg1=, val1, arg2=, val2,
1101 // \-- 1st --/ \-- 2nd --/ \-- remainder
1102 const int nArgs = args.size();
1103
1104 if (nArgs % 2) {
1105 // Problem...
1106 relative->doc().location().warning(
1107 QStringLiteral("Error when parsing attributes for the table: got \"%1\"")
1108 .arg(atom->string()));
1109 }
1110 for (int i = 0; i + 1 < nArgs; i += 2) {
1111 // args.at(i): name of the attribute being set.
1112 // args.at(i + 1): value of the said attribute.
1113 const QString &attr = args.at(i).chopped(1);
1114 if (attr == "id") { // Too bad if there is an anchor later on
1115 // (currently never happens).
1116 writeXmlId(args.at(i + 1));
1117 } else {
1118 m_writer->writeAttribute(attr, args.at(i + 1));
1119 }
1120 }
1121 }
1122 newLine();
1123
1124 // If there is nothing in this row, close it right now. There might be keywords before the row contents.
1125 bool isRowEmpty = hasTarget ? !matchAhead(atom->next(), Atom::TableItemLeft) : !matchAhead(atom, Atom::TableItemLeft);
1126 if (isRowEmpty && matchAhead(atom, Atom::Keyword)) {
1127 const Atom* next = atom->next();
1128 while (matchAhead(next, Atom::Keyword))
1129 next = next->next();
1130 isRowEmpty = !matchAhead(next, Atom::TableItemLeft);
1131 }
1132
1133 if (isRowEmpty) {
1134 m_closeTableRow = true;
1135 m_writer->writeEndElement(); // td
1136 newLine();
1137 }
1138 }
1139 break;
1141 if (m_closeTableRow) {
1142 m_closeTableRow = false;
1143 m_writer->writeEndElement(); // td
1144 newLine();
1145 }
1146
1147 m_writer->writeEndElement(); // tr
1148 newLine();
1149 break;
1150 case Atom::TableItemLeft:
1151 m_writer->writeStartElement(dbNamespace, m_inTableHeader ? "th" : "td");
1152
1153 for (int i = 0; i < atom->count(); ++i) {
1154 const QString &p = atom->string(i);
1155 if (p.contains('=')) {
1156 QStringList lp = p.split(QLatin1Char('='));
1157 m_writer->writeAttribute(lp.at(0), lp.at(1));
1158 } else {
1159 QStringList spans = p.split(QLatin1Char(','));
1160 if (spans.size() == 2) {
1161 if (spans.at(0) != "1")
1162 m_writer->writeAttribute("colspan", spans.at(0).trimmed());
1163 if (spans.at(1) != "1")
1164 m_writer->writeAttribute("rowspan", spans.at(1).trimmed());
1165 }
1166 }
1167 }
1168 newLine();
1169 // No skipahead, as opposed to HTML: in DocBook, the text must be wrapped in paragraphs.
1170 break;
1171 case Atom::TableItemRight:
1172 m_writer->writeEndElement(); // th if m_inTableHeader, otherwise td
1173 newLine();
1174 break;
1176 // Skip \toc .. \endtoc content, handled separately by TOCWriter
1177 std::ignore = atom->find(Atom::TableOfContentsRight, &skipAhead);
1178 break;
1179 case Atom::Keyword:
1180 break;
1181 case Atom::Target:
1182 // Sometimes, there is a \target just before a section title with the same ID. Only output one xml:id.
1184 QString nextId = TextUtils::asAsciiPrintable(
1186 QString ownId = TextUtils::asAsciiPrintable(atom->string());
1187 if (nextId == ownId)
1188 break;
1189 }
1190
1191 writeAnchor(TextUtils::asAsciiPrintable(atom->string()));
1192 break;
1193 case Atom::UnhandledFormat:
1194 m_writer->writeStartElement(dbNamespace, "emphasis");
1195 m_writer->writeAttribute("role", "bold");
1196 m_writer->writeCharacters("<Missing DocBook>");
1197 m_writer->writeEndElement(); // emphasis
1198 break;
1199 case Atom::UnknownCommand:
1200 m_writer->writeStartElement(dbNamespace, "emphasis");
1201 m_writer->writeAttribute("role", "bold");
1202 if (m_useITS)
1203 m_writer->writeAttribute(itsNamespace, "translate", "no");
1204 m_writer->writeCharacters("<Unknown command>");
1205 m_writer->writeStartElement(dbNamespace, "code");
1206 m_writer->writeCharacters(atom->string());
1207 m_writer->writeEndElement(); // code
1208 m_writer->writeEndElement(); // emphasis
1209 break;
1212 case Atom::ComparesLeft:
1217 // No output (ignore).
1218 break;
1219 default:
1220 unknownAtom(atom);
1221 }
1222 return skipAhead;
1223}
1224
1225void DocBookGenerator::generateClassHierarchy(const Node *relative, NodeMultiMap &classMap)
1226{
1227 // From HtmlGenerator::generateClassHierarchy.
1228 if (classMap.isEmpty())
1229 return;
1230
1231 std::function<void(ClassNode *)> generateClassAndChildren
1232 = [this, &relative, &generateClassAndChildren](ClassNode * classe) {
1233 m_writer->writeStartElement(dbNamespace, "listitem");
1234 newLine();
1235
1236 // This class.
1237 m_writer->writeStartElement(dbNamespace, "para");
1238 generateFullName(classe, relative);
1239 m_writer->writeEndElement(); // para
1240 newLine();
1241
1242 // Children, if any.
1243 bool hasChild = false;
1244 for (const RelatedClass &relatedClass : classe->derivedClasses()) {
1245 if (relatedClass.m_node && relatedClass.m_node->isInAPI()) {
1246 hasChild = true;
1247 break;
1248 }
1249 }
1250
1251 if (hasChild) {
1252 m_writer->writeStartElement(dbNamespace, "itemizedlist");
1253 newLine();
1254
1255 for (const RelatedClass &relatedClass: classe->derivedClasses()) {
1256 if (relatedClass.m_node && relatedClass.m_node->isInAPI()) {
1257 generateClassAndChildren(relatedClass.m_node);
1258 }
1259 }
1260
1261 m_writer->writeEndElement(); // itemizedlist
1262 newLine();
1263 }
1264
1265 // End this class.
1266 m_writer->writeEndElement(); // listitem
1267 newLine();
1268 };
1269
1270 m_writer->writeStartElement(dbNamespace, "itemizedlist");
1271 newLine();
1272
1273 for (const auto &it : classMap) {
1274 auto *classe = static_cast<ClassNode *>(it);
1275 if (classe->baseClasses().isEmpty())
1276 generateClassAndChildren(classe);
1277 }
1278
1279 m_writer->writeEndElement(); // itemizedlist
1280 newLine();
1281}
1282
1283void DocBookGenerator::generateLink(const Atom *atom)
1284{
1285 Q_ASSERT(m_inLink);
1286
1287 // From HtmlGenerator::generateLink.
1288 if (m_linkNode && m_linkNode->isFunction()) {
1289 auto match = XmlGenerator::m_funcLeftParen.match(atom->string());
1290 if (match.hasMatch()) {
1291 // C++: move () outside of link
1292 qsizetype leftParenLoc = match.capturedStart(1);
1293 m_writer->writeCharacters(atom->string().left(leftParenLoc));
1294 endLink();
1295 m_writer->writeCharacters(atom->string().mid(leftParenLoc));
1296 return;
1297 }
1298 }
1299 m_writer->writeCharacters(atom->string());
1300}
1301
1302/*!
1303 This version of the function is called when the \a link is known
1304 to be correct.
1305 */
1306void DocBookGenerator::beginLink(const QString &link, const Node *node, const Node *relative)
1307{
1308 // From HtmlGenerator::beginLink.
1309 m_writer->writeStartElement(dbNamespace, "link");
1310 m_writer->writeAttribute(xlinkNamespace, "href", link);
1311 if (node && !(relative && node->status() == relative->status())
1312 && node->isDeprecated())
1313 m_writer->writeAttribute("role", "deprecated");
1314 m_inLink = true;
1315 m_linkNode = node;
1316}
1317
1318void DocBookGenerator::endLink()
1319{
1320 // From HtmlGenerator::endLink.
1321 if (m_inLink)
1322 m_writer->writeEndElement(); // link
1323 m_inLink = false;
1324 m_linkNode = nullptr;
1325}
1326
1327void DocBookGenerator::generateList(const Node *relative, const QString &selector,
1328 Qt::SortOrder sortOrder)
1329{
1330 // From HtmlGenerator::generateList, without warnings, changing prototype.
1331 CNMap cnm;
1333 if (selector == QLatin1String("overviews"))
1334 type = NodeType::Group;
1335 else if (selector == QLatin1String("cpp-modules"))
1336 type = NodeType::Module;
1337 else if (selector == QLatin1String("qml-modules"))
1338 type = NodeType::QmlModule;
1339
1340 if (type != NodeType::NoType) {
1341 NodeList nodeList;
1342 m_qdb->mergeCollections(type, cnm, relative);
1343 const QList<CollectionNode *> collectionList = cnm.values();
1344 nodeList.reserve(collectionList.size());
1345 for (auto *collectionNode : collectionList)
1346 nodeList.append(collectionNode);
1347 generateAnnotatedList(relative, nodeList, selector, Auto, sortOrder);
1348 } else {
1349 /*
1350 \generatelist {selector} is only allowed in a comment where
1351 the topic is \group, \module, or \qmlmodule.
1352 */
1353 Node *n = const_cast<Node *>(relative);
1354 auto *cn = static_cast<CollectionNode *>(n);
1355 m_qdb->mergeCollections(cn);
1356 generateAnnotatedList(cn, cn->members(), selector, Auto, sortOrder);
1357 }
1358}
1359
1360/*!
1361 Outputs an annotated list of the nodes in \a nodeList.
1362 A two-column table is output.
1363 */
1364void DocBookGenerator::generateAnnotatedList(const Node *relative, const NodeList &nodeList,
1365 const QString &selector, GeneratedListType type,
1366 Qt::SortOrder sortOrder)
1367{
1368 if (nodeList.isEmpty())
1369 return;
1370
1371 // Do nothing if all items are internal or obsolete.
1372 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
1373 if (std::all_of(nodeList.cbegin(), nodeList.cend(), [&policy](const Node *n) {
1374 const NodeContext context = n->createContext();
1375 return !InclusionFilter::isIncluded(policy, context) || n->isDeprecated();
1376 })) {
1377 return;
1378 }
1379
1380 // Detect if there is a need for a variablelist (i.e. titles mapped to
1381 // descriptions) or a regular itemizedlist (only titles).
1382 bool noItemsHaveTitle =
1383 type == ItemizedList || std::all_of(nodeList.begin(), nodeList.end(),
1384 [](const Node* node) {
1385 return node->doc().briefText().toString().isEmpty();
1386 });
1387
1388 // Wrap the list in a section if needed.
1389 if (type == AutoSection && m_hasSection)
1390 startSection("", "Contents");
1391
1392 // From WebXMLGenerator::generateAnnotatedList.
1393 if (!nodeList.isEmpty()) {
1394 m_writer->writeStartElement(dbNamespace, noItemsHaveTitle ? "itemizedlist" : "variablelist");
1395 m_writer->writeAttribute("role", selector);
1396 newLine();
1397
1398 NodeList members{nodeList};
1399 if (sortOrder == Qt::DescendingOrder)
1400 std::sort(members.rbegin(), members.rend(), Node::nodeSortKeyOrNameLessThan);
1401 else
1402 std::sort(members.begin(), members.end(), Node::nodeSortKeyOrNameLessThan);
1403 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
1404 for (const auto &node : std::as_const(members)) {
1405 const NodeContext context = node->createContext();
1406 if (!InclusionFilter::isIncluded(policy, context) || node->isDeprecated())
1407 continue;
1408
1409 if (noItemsHaveTitle) {
1410 m_writer->writeStartElement(dbNamespace, "listitem");
1411 newLine();
1412 m_writer->writeStartElement(dbNamespace, "para");
1413 } else {
1414 m_writer->writeStartElement(dbNamespace, "varlistentry");
1415 newLine();
1416 m_writer->writeStartElement(dbNamespace, "term");
1417 }
1418 generateFullName(node, relative);
1419 if (noItemsHaveTitle) {
1420 m_writer->writeEndElement(); // para
1421 newLine();
1422 m_writer->writeEndElement(); // listitem
1423 } else {
1424 m_writer->writeEndElement(); // term
1425 newLine();
1426 m_writer->writeStartElement(dbNamespace, "listitem");
1427 newLine();
1428 m_writer->writeStartElement(dbNamespace, "para");
1429 m_writer->writeCharacters(node->doc().briefText().toString());
1430 m_writer->writeEndElement(); // para
1431 newLine();
1432 m_writer->writeEndElement(); // listitem
1433 newLine();
1434 m_writer->writeEndElement(); // varlistentry
1435 }
1436 newLine();
1437 }
1438
1439 m_writer->writeEndElement(); // itemizedlist or variablelist
1440 newLine();
1441 }
1442
1443 if (type == AutoSection && m_hasSection)
1444 endSection();
1445}
1446
1447/*!
1448 Outputs a series of annotated lists from the nodes in \a nmm,
1449 divided into sections based by the key names in the multimap.
1450 */
1451void DocBookGenerator::generateAnnotatedLists(const Node *relative, const NodeMultiMap &nmm,
1452 const QString &selector)
1453{
1454 // From HtmlGenerator::generateAnnotatedLists.
1455 for (const QString &name : nmm.uniqueKeys()) {
1456 if (!name.isEmpty())
1457 startSection(name.toLower(), name);
1458 generateAnnotatedList(relative, nmm.values(name), selector);
1459 if (!name.isEmpty())
1460 endSection();
1461 }
1462}
1463
1464/*!
1465 This function finds the common prefix of the names of all
1466 the classes in the class map \a nmm and then generates a
1467 compact list of the class names alphabetized on the part
1468 of the name not including the common prefix. You can tell
1469 the function to use \a comonPrefix as the common prefix,
1470 but normally you let it figure it out itself by looking at
1471 the name of the first and last classes in the class map
1472 \a nmm.
1473 */
1474void DocBookGenerator::generateCompactList(const Node *relative, const NodeMultiMap &nmm,
1475 bool includeAlphabet, const QString &commonPrefix,
1476 const QString &selector)
1477{
1478 // From HtmlGenerator::generateCompactList. No more "includeAlphabet", this should be handled by
1479 // the DocBook toolchain afterwards.
1480 // TODO: In DocBook, probably no need for this method: this is purely presentational, i.e. to be
1481 // fully handled by the DocBook toolchain.
1482
1483 if (nmm.isEmpty())
1484 return;
1485
1486 const int NumParagraphs = 37; // '0' to '9', 'A' to 'Z', '_'
1487 qsizetype commonPrefixLen = commonPrefix.size();
1488
1489 /*
1490 Divide the data into 37 paragraphs: 0, ..., 9, A, ..., Z,
1491 underscore (_). QAccel will fall in paragraph 10 (A) and
1492 QXtWidget in paragraph 33 (X). This is the only place where we
1493 assume that NumParagraphs is 37. Each paragraph is a NodeMultiMap.
1494 */
1495 NodeMultiMap paragraph[NumParagraphs + 1];
1496 QString paragraphName[NumParagraphs + 1];
1497 QSet<char> usedParagraphNames;
1498
1499 for (auto c = nmm.constBegin(); c != nmm.constEnd(); ++c) {
1500 QStringList pieces = c.key().split("::");
1501 int idx = commonPrefixLen;
1502 if (idx > 0 && !pieces.last().startsWith(commonPrefix, Qt::CaseInsensitive))
1503 idx = 0;
1504 QString last = pieces.last().toLower();
1505 QString key = last.mid(idx);
1506
1507 int paragraphNr = NumParagraphs - 1;
1508
1509 if (key[0].digitValue() != -1) {
1510 paragraphNr = key[0].digitValue();
1511 } else if (key[0] >= QLatin1Char('a') && key[0] <= QLatin1Char('z')) {
1512 paragraphNr = 10 + key[0].unicode() - 'a';
1513 }
1514
1515 paragraphName[paragraphNr] = key[0].toUpper();
1516 usedParagraphNames.insert(key[0].toLower().cell());
1517 paragraph[paragraphNr].insert(last, c.value());
1518 }
1519
1520 /*
1521 Each paragraph j has a size: paragraph[j].count(). In the
1522 discussion, we will assume paragraphs 0 to 5 will have sizes
1523 3, 1, 4, 1, 5, 9.
1524
1525 We now want to compute the paragraph offset. Paragraphs 0 to 6
1526 start at offsets 0, 3, 4, 8, 9, 14, 23.
1527 */
1528 int paragraphOffset[NumParagraphs + 1]; // 37 + 1
1529 paragraphOffset[0] = 0;
1530 for (int i = 0; i < NumParagraphs; i++) // i = 0..36
1531 paragraphOffset[i + 1] = paragraphOffset[i] + paragraph[i].size();
1532
1533 // Output the alphabet as a row of links.
1534 if (includeAlphabet && !usedParagraphNames.isEmpty()) {
1535 m_writer->writeStartElement(dbNamespace, "simplelist");
1536 newLine();
1537
1538 for (int i = 0; i < 26; i++) {
1539 QChar ch('a' + i);
1540 if (usedParagraphNames.contains(char('a' + i))) {
1541 m_writer->writeStartElement(dbNamespace, "member");
1542 generateSimpleLink(ch, ch.toUpper());
1543 m_writer->writeEndElement(); // member
1544 newLine();
1545 }
1546 }
1547
1548 m_writer->writeEndElement(); // simplelist
1549 newLine();
1550 }
1551
1552 // Build a map of all duplicate names across the entire list
1553 QHash<QString, int> nameOccurrences;
1554 for (const auto &[key, node] : nmm.asKeyValueRange()) {
1555 QStringList pieces{node->fullName(relative).split("::"_L1)};
1556 const QString &name{pieces.last()};
1557 nameOccurrences[name]++;
1558 }
1559
1560 // Actual output.
1561 int curParNr = 0;
1562 int curParOffset = 0;
1563
1564 m_writer->writeStartElement(dbNamespace, "variablelist");
1565 m_writer->writeAttribute("role", selector);
1566 newLine();
1567
1568 for (int i = 0; i < nmm.size(); i++) {
1569 while ((curParNr < NumParagraphs) && (curParOffset == paragraph[curParNr].size())) {
1570
1571 ++curParNr;
1572 curParOffset = 0;
1573 }
1574
1575 // Starting a new paragraph means starting a new varlistentry.
1576 if (curParOffset == 0) {
1577 if (i > 0) {
1578 m_writer->writeEndElement(); // itemizedlist
1579 newLine();
1580 m_writer->writeEndElement(); // listitem
1581 newLine();
1582 m_writer->writeEndElement(); // varlistentry
1583 newLine();
1584 }
1585
1586 m_writer->writeStartElement(dbNamespace, "varlistentry");
1587 if (includeAlphabet)
1588 writeXmlId(paragraphName[curParNr][0].toLower());
1589 newLine();
1590
1591 m_writer->writeStartElement(dbNamespace, "term");
1592 m_writer->writeStartElement(dbNamespace, "emphasis");
1593 m_writer->writeAttribute("role", "bold");
1594 m_writer->writeCharacters(paragraphName[curParNr]);
1595 m_writer->writeEndElement(); // emphasis
1596 m_writer->writeEndElement(); // term
1597 newLine();
1598
1599 m_writer->writeStartElement(dbNamespace, "listitem");
1600 newLine();
1601 m_writer->writeStartElement(dbNamespace, "itemizedlist");
1602 newLine();
1603 }
1604
1605 // Output a listitem for the current offset in the current paragraph.
1606 m_writer->writeStartElement(dbNamespace, "listitem");
1607 newLine();
1608 m_writer->writeStartElement(dbNamespace, "para");
1609
1610 if ((curParNr < NumParagraphs) && !paragraphName[curParNr].isEmpty()) {
1611 NodeMultiMap::Iterator it;
1612 NodeMultiMap::Iterator next;
1613 it = paragraph[curParNr].begin();
1614 for (int j = 0; j < curParOffset; j++)
1615 ++it;
1616
1617 // Cut the name into pieces to determine whether it is simple (one piece) or complex
1618 // (more than one piece).
1619 QStringList pieces{it.value()->fullName(relative).split("::"_L1)};
1620 const auto &name{pieces.last()};
1621
1622 // Add module disambiguation if there are multiple types with the same name
1623 if (nameOccurrences[name] > 1) {
1624 const QString moduleName = it.value()->isQmlNode() ? it.value()->logicalModuleName()
1625 : it.value()->tree()->camelCaseModuleName();
1626 pieces.last().append(": %1"_L1.arg(moduleName));
1627 }
1628
1629 // Write the link to the element, which is identical if the element is obsolete or not.
1630 m_writer->writeStartElement(dbNamespace, "link");
1631 m_writer->writeAttribute(xlinkNamespace, "href", linkForNode(*it, relative));
1632 if (const QString type = targetType(it.value()); !type.isEmpty())
1633 m_writer->writeAttribute("role", type);
1634 m_writer->writeCharacters(pieces.last());
1635 m_writer->writeEndElement(); // link
1636
1637 // Outside the link, give the full name of the node if it is complex.
1638 if (pieces.size() > 1) {
1639 m_writer->writeCharacters(" (");
1640 generateFullName(it.value()->parent(), relative);
1641 m_writer->writeCharacters(")");
1642 }
1643 }
1644
1645 m_writer->writeEndElement(); // para
1646 newLine();
1647 m_writer->writeEndElement(); // listitem
1648 newLine();
1649
1650 curParOffset++;
1651 }
1652 m_writer->writeEndElement(); // itemizedlist
1653 newLine();
1654 m_writer->writeEndElement(); // listitem
1655 newLine();
1656 m_writer->writeEndElement(); // varlistentry
1657 newLine();
1658
1659 m_writer->writeEndElement(); // variablelist
1660 newLine();
1661}
1662
1663void DocBookGenerator::generateFunctionIndex(const Node *relative)
1664{
1665 // From HtmlGenerator::generateFunctionIndex.
1666
1667 // First list: links to parts of the second list, one item per letter.
1668 m_writer->writeStartElement(dbNamespace, "simplelist");
1669 m_writer->writeAttribute("role", "functionIndex");
1670 newLine();
1671 for (int i = 0; i < 26; i++) {
1672 QChar ch('a' + i);
1673 m_writer->writeStartElement(dbNamespace, "member");
1674 m_writer->writeAttribute(xlinkNamespace, "href", QString("#") + ch);
1675 m_writer->writeCharacters(ch.toUpper());
1676 m_writer->writeEndElement(); // member
1677 newLine();
1678 }
1679 m_writer->writeEndElement(); // simplelist
1680 newLine();
1681
1682 // Second list: the actual list of functions, sorted by alphabetical
1683 // order. One entry of the list per letter.
1684 if (m_qdb->getFunctionIndex().isEmpty())
1685 return;
1686 char nextLetter = 'a';
1687 char currentLetter;
1688
1689 m_writer->writeStartElement(dbNamespace, "itemizedlist");
1690 newLine();
1691
1692 NodeMapMap &funcIndex = m_qdb->getFunctionIndex();
1693 QMap<QString, NodeMap>::ConstIterator f = funcIndex.constBegin();
1694 while (f != funcIndex.constEnd()) {
1695 m_writer->writeStartElement(dbNamespace, "listitem");
1696 newLine();
1697 m_writer->writeStartElement(dbNamespace, "para");
1698 m_writer->writeCharacters(f.key() + ": ");
1699
1700 currentLetter = f.key()[0].unicode();
1701 while (islower(currentLetter) && currentLetter >= nextLetter) {
1702 writeAnchor(QString(nextLetter));
1703 nextLetter++;
1704 }
1705
1706 NodeMap::ConstIterator s = (*f).constBegin();
1707 while (s != (*f).constEnd()) {
1708 m_writer->writeCharacters(" ");
1709 generateFullName((*s)->parent(), relative);
1710 ++s;
1711 }
1712
1713 m_writer->writeEndElement(); // para
1714 newLine();
1715 m_writer->writeEndElement(); // listitem
1716 newLine();
1717 ++f;
1718 }
1719 m_writer->writeEndElement(); // itemizedlist
1720 newLine();
1721}
1722
1723void DocBookGenerator::generateLegaleseList(const Node *relative)
1724{
1725 // From HtmlGenerator::generateLegaleseList.
1726 TextToNodeMap &legaleseTexts = m_qdb->getLegaleseTexts();
1727 for (auto it = legaleseTexts.cbegin(), end = legaleseTexts.cend(); it != end; ++it) {
1728 Text text = it.key();
1729 generateText(text, relative);
1730 m_writer->writeStartElement(dbNamespace, "itemizedlist");
1731 newLine();
1732 do {
1733 m_writer->writeStartElement(dbNamespace, "listitem");
1734 newLine();
1735 m_writer->writeStartElement(dbNamespace, "para");
1736 generateFullName(it.value(), relative);
1737 m_writer->writeEndElement(); // para
1738 newLine();
1739 m_writer->writeEndElement(); // listitem
1740 newLine();
1741 ++it;
1742 } while (it != legaleseTexts.constEnd() && it.key() == text);
1743 m_writer->writeEndElement(); // itemizedlist
1744 newLine();
1745 }
1746}
1747
1748void DocBookGenerator::generateBrief(const Node *node)
1749{
1750 // From HtmlGenerator::generateBrief. Also see generateHeader, which is specifically dealing
1751 // with the DocBook header (and thus wraps the brief in an abstract).
1752 Text brief = node->doc().briefText();
1753
1754 if (!brief.isEmpty()) {
1755 if (!brief.lastAtom()->string().endsWith('.'))
1756 brief << Atom(Atom::String, ".");
1757
1758 m_writer->writeStartElement(dbNamespace, "para");
1759 generateText(brief, node);
1760 m_writer->writeEndElement(); // para
1761 newLine();
1762 }
1763}
1764
1766{
1767 // From Generator::generateSince.
1768 if (!node->since().isEmpty()) {
1769 m_writer->writeStartElement(dbNamespace, "para");
1770 if (node->isSharedCommentNode()) {
1771 const auto &collective = static_cast<const SharedCommentNode *>(node)->collective();
1772 QString typeStr = collective.size() > 1 ? typeString(collective.first()) + "s" : typeString(node);
1773 m_writer->writeCharacters("These " + typeStr + " were introduced in ");
1774 } else {
1775 m_writer->writeCharacters("This " + typeString(node) + " was introduced in ");
1776 }
1777 m_writer->writeCharacters(formatSince(node) + ".");
1778 m_writer->writeEndElement(); // para
1779 newLine();
1780
1781 return true;
1782 }
1783
1784 return false;
1785}
1786
1787/*!
1788 Generate the DocBook header for the file, including the abstract.
1789 Equivalent to calling generateTitle and generateBrief in HTML.
1790*/
1791void DocBookGenerator::generateHeader(const Text &title, const QString &subTitle,
1792 const Node *node)
1793{
1794 generateHeader(title, node);
1795
1796 if (!subTitle.isEmpty()) {
1797 m_writer->writeStartElement(dbNamespace, "subtitle");
1798 if (isApiGenus(node->genus()) && m_useITS)
1799 m_writer->writeAttribute(itsNamespace, "translate", "no");
1800 m_writer->writeCharacters(subTitle);
1801 m_writer->writeEndElement(); // subtitle
1802 newLine();
1803 }
1804
1805 finishHeader(node);
1806}
1807
1808/*!
1809 Generates the opening portion of the DocBook header — \c{<db:info>}
1810 and the title element. The caller emits any subtitle (text-only or
1811 element-rich) directly into the writer afterwards, then calls
1812 finishHeader() to close \c{<db:info>} and write the abstract.
1813
1814 Class pages with template declarations use this two-step form so the
1815 subtitle element can carry \c{<db:link>} elements for concept
1816 references — content that wouldn't survive the plain-text subtitle
1817 path the 3-argument overload uses.
1818*/
1819void DocBookGenerator::generateHeader(const Text &title, const Node *node)
1820{
1821 refMap.clear();
1822
1823 // Output the DocBook header.
1824 m_writer->writeStartElement(dbNamespace, "info");
1825 newLine();
1826 m_writer->writeStartElement(dbNamespace, "title");
1827 if (isApiGenus(node->genus()) && m_useITS)
1828 m_writer->writeAttribute(itsNamespace, "translate", "no");
1829 generateText(title, node);
1830 m_writer->writeEndElement(); // title
1831 newLine();
1832}
1833
1834/*!
1835 Emits the tail of the DocBook header — product, edition, titleabbrev,
1836 navigation links, and abstract — then closes \c{<db:info>}. Used as
1837 the companion to the two-argument \c{generateHeader} overload that
1838 leaves the info element open for the caller to emit a rich subtitle.
1839*/
1841{
1842 if (!m_productName.isEmpty() || !m_project.isEmpty()) {
1843 m_writer->writeTextElement(dbNamespace, "productname", m_productName.isEmpty() ?
1844 m_project : m_productName);
1845 newLine();
1846 }
1847
1848 if (!m_buildVersion.isEmpty()) {
1849 m_writer->writeTextElement(dbNamespace, "edition", m_buildVersion);
1850 newLine();
1851 }
1852
1853 if (!m_projectDescription.isEmpty()) {
1854 m_writer->writeTextElement(dbNamespace, "titleabbrev", m_projectDescription);
1855 newLine();
1856 }
1857
1858 // Deal with links.
1859 // Adapted from HtmlGenerator::generateHeader (output part: no need to update a navigationLinks
1860 // or useSeparator field, as this content is only output in the info tag, not in the main
1861 // content).
1862 if (node && !node->links().empty()) {
1863 std::pair<QString, QString> linkPair;
1864 std::pair<QString, QString> anchorPair;
1865 const Node *linkNode;
1866
1867 if (node->links().contains(Node::PreviousLink)) {
1868 linkPair = node->links()[Node::PreviousLink];
1869 linkNode = m_qdb->findNodeForTarget(linkPair.first, node);
1870 if (!linkNode || linkNode == node)
1871 anchorPair = linkPair;
1872 else
1873 anchorPair = anchorForNode(linkNode);
1874
1875 m_writer->writeStartElement(dbNamespace, "extendedlink");
1876 m_writer->writeAttribute(xlinkNamespace, "type", "extended");
1877 m_writer->writeEmptyElement(dbNamespace, "link");
1878 m_writer->writeAttribute(xlinkNamespace, "to", anchorPair.first);
1879 m_writer->writeAttribute(xlinkNamespace, "type", "arc");
1880 m_writer->writeAttribute(xlinkNamespace, "arcrole", "prev");
1881 if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty())
1882 m_writer->writeAttribute(xlinkNamespace, "title", anchorPair.second);
1883 else
1884 m_writer->writeAttribute(xlinkNamespace, "title", linkPair.second);
1885 m_writer->writeEndElement(); // extendedlink
1886 newLine();
1887 }
1888 if (node->links().contains(Node::NextLink)) {
1889 linkPair = node->links()[Node::NextLink];
1890 linkNode = m_qdb->findNodeForTarget(linkPair.first, node);
1891 if (!linkNode || linkNode == node)
1892 anchorPair = linkPair;
1893 else
1894 anchorPair = anchorForNode(linkNode);
1895
1896 m_writer->writeStartElement(dbNamespace, "extendedlink");
1897 m_writer->writeAttribute(xlinkNamespace, "type", "extended");
1898 m_writer->writeEmptyElement(dbNamespace, "link");
1899 m_writer->writeAttribute(xlinkNamespace, "to", anchorPair.first);
1900 m_writer->writeAttribute(xlinkNamespace, "type", "arc");
1901 m_writer->writeAttribute(xlinkNamespace, "arcrole", "next");
1902 if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty())
1903 m_writer->writeAttribute(xlinkNamespace, "title", anchorPair.second);
1904 else
1905 m_writer->writeAttribute(xlinkNamespace, "title", linkPair.second);
1906 m_writer->writeEndElement(); // extendedlink
1907 newLine();
1908 }
1909 if (node->links().contains(Node::StartLink)) {
1910 linkPair = node->links()[Node::StartLink];
1911 linkNode = m_qdb->findNodeForTarget(linkPair.first, node);
1912 if (!linkNode || linkNode == node)
1913 anchorPair = linkPair;
1914 else
1915 anchorPair = anchorForNode(linkNode);
1916
1917 m_writer->writeStartElement(dbNamespace, "extendedlink");
1918 m_writer->writeAttribute(xlinkNamespace, "type", "extended");
1919 m_writer->writeEmptyElement(dbNamespace, "link");
1920 m_writer->writeAttribute(xlinkNamespace, "to", anchorPair.first);
1921 m_writer->writeAttribute(xlinkNamespace, "type", "arc");
1922 m_writer->writeAttribute(xlinkNamespace, "arcrole", "start");
1923 if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty())
1924 m_writer->writeAttribute(xlinkNamespace, "title", anchorPair.second);
1925 else
1926 m_writer->writeAttribute(xlinkNamespace, "title", linkPair.second);
1927 m_writer->writeEndElement(); // extendedlink
1928 newLine();
1929 }
1930 }
1931
1932 // Deal with the abstract (what qdoc calls brief).
1933 if (node) {
1934 // Adapted from HtmlGenerator::generateBrief, without extraction marks. The parameter
1935 // addLink is always false. Factoring this function out is not as easy as in HtmlGenerator:
1936 // abstracts only happen in the header (info tag), slightly different tags must be used at
1937 // other places. Also includes code from HtmlGenerator::generateCppReferencePage to handle
1938 // the name spaces.
1939 m_writer->writeStartElement(dbNamespace, "abstract");
1940 newLine();
1941
1942 bool generatedSomething = false;
1943
1944 Text brief;
1945 const NamespaceNode *ns =
1946 node->isNamespace() ? static_cast<const NamespaceNode *>(node) : nullptr;
1947 if (ns && !ns->hasDoc() && ns->docNode()) {
1948 NamespaceNode *NS = ns->docNode();
1949 brief << "The " << ns->name()
1950 << " namespace includes the following elements from module "
1951 << ns->tree()->camelCaseModuleName() << ". The full namespace is "
1952 << "documented in module " << NS->tree()->camelCaseModuleName();
1953 addNodeLink(brief, fullDocumentLocation(NS), " here.");
1954 } else {
1955 brief = node->doc().briefText();
1956 }
1957
1958 if (!brief.isEmpty()) {
1959 if (!brief.lastAtom()->string().endsWith('.'))
1960 brief << Atom(Atom::String, ".");
1961
1962 m_writer->writeStartElement(dbNamespace, "para");
1963 generateText(brief, node);
1964 m_writer->writeEndElement(); // para
1965 newLine();
1966
1967 generatedSomething = true;
1968 }
1969
1970 // Generate other paragraphs that should go into the abstract.
1971 generatedSomething |= generateStatus(node);
1972 generatedSomething |= generateSince(node);
1973 generatedSomething |= generateThreadSafeness(node);
1974 generatedSomething |= generateComparisonTable(node);
1975
1976 // An abstract cannot be empty, hence use the project description.
1977 if (!generatedSomething)
1978 m_writer->writeTextElement(dbNamespace, "para", m_projectDescription + ".");
1979
1980 m_writer->writeEndElement(); // abstract
1981 newLine();
1982 }
1983
1984 // End of the DocBook header.
1985 m_writer->writeEndElement(); // info
1986 newLine();
1987}
1988
1990{
1991 while (!sectionLevels.isEmpty()) {
1992 sectionLevels.pop();
1993 endSection();
1994 }
1995}
1996
1998{
1999 if (m_closeSectionAfterGeneratedList) {
2000 m_closeSectionAfterGeneratedList = false;
2001 endSection();
2002 }
2003 if (m_closeSectionAfterRawTitle) {
2004 m_closeSectionAfterRawTitle = false;
2005 endSection();
2006 }
2007
2009 m_writer->writeEndElement(); // article
2010}
2011
2012void DocBookGenerator::generateSimpleLink(const QString &href, const QString &text)
2013{
2014 m_writer->writeStartElement(dbNamespace, "link");
2015 m_writer->writeAttribute(xlinkNamespace, "href", href);
2016 m_writer->writeCharacters(text);
2017 m_writer->writeEndElement(); // link
2018}
2019
2020/*!
2021 Writes the extra synopsis string \a extra, processing any embedded
2022 <@extref> tags and converting them to DocBook links.
2023*/
2024void DocBookGenerator::generateExtraSynopsis(const QString &extra)
2025{
2026 static const QHash<QString, QString> extrefUrls = {
2027 {u"cpp-explicitly-defaulted"_s,
2028 u"https://en.cppreference.com/w/cpp/language/function#Defaulted_functions"_s},
2029 {u"cpp-deleted-functions"_s,
2030 u"https://en.cppreference.com/w/cpp/language/function#Deleted_functions"_s},
2031 };
2032
2033 static const QRegularExpression extrefRegex(
2034 u"<@extref target=\"([^\"]+)\">([^<]*)</@extref>"_s);
2035
2036 qsizetype pos = 0;
2037 auto it = extrefRegex.globalMatch(extra);
2038 while (it.hasNext()) {
2039 auto match = it.next();
2040 if (match.capturedStart() > pos)
2041 m_writer->writeCharacters(extra.mid(pos, match.capturedStart() - pos));
2042
2043 QString target = match.captured(1);
2044 QString text = match.captured(2);
2045 QString url = extrefUrls.value(target);
2046 if (!url.isEmpty())
2047 generateSimpleLink(url, text);
2048 else
2049 m_writer->writeCharacters(text);
2050
2051 pos = match.capturedEnd();
2052 }
2053 if (pos < extra.size())
2054 m_writer->writeCharacters(extra.mid(pos));
2055}
2056
2057void DocBookGenerator::generateObsoleteMembers(const Sections &sections)
2058{
2059 // From HtmlGenerator::generateObsoleteMembersFile.
2060 SectionPtrVector summary_spv; // Summaries are ignored in DocBook (table of contents).
2061 SectionPtrVector details_spv;
2062 if (!sections.hasObsoleteMembers(&summary_spv, &details_spv))
2063 return;
2064
2065 const Aggregate *aggregate = sections.aggregate();
2066 startSection("obsolete", "Obsolete Members for " + aggregate->plainFullName());
2067
2068 m_writer->writeStartElement(dbNamespace, "para");
2069 m_writer->writeStartElement(dbNamespace, "emphasis");
2070 m_writer->writeAttribute("role", "bold");
2071 m_writer->writeCharacters("The following members of class ");
2072 generateSimpleLink(linkForNode(aggregate, nullptr), aggregate->name());
2073 m_writer->writeCharacters(" are deprecated.");
2074 m_writer->writeEndElement(); // emphasis bold
2075 m_writer->writeCharacters(" We strongly advise against using them in new code.");
2076 m_writer->writeEndElement(); // para
2077 newLine();
2078
2079 for (const Section *section : details_spv) {
2080 const QString &title = "Obsolete " + section->title();
2081 startSection(title.toLower(), title);
2082
2083 const NodeVector &members = section->obsoleteMembers();
2084 NodeVector::ConstIterator m = members.constBegin();
2085 while (m != members.constEnd()) {
2086 if ((*m)->access() != Access::Private)
2087 generateDetailedMember(*m, aggregate);
2088 ++m;
2089 }
2090
2091 endSection();
2092 }
2093
2094 endSection();
2095}
2096
2097/*!
2098 Generates a separate section where obsolete members of the QML
2099 type \a qcn are listed. The \a marker is used to generate
2100 the section lists, which are then traversed and output here.
2101
2102 Note that this function currently only handles correctly the
2103 case where \a status is \c {Section::Deprecated}.
2104 */
2105void DocBookGenerator::generateObsoleteQmlMembers(const Sections &sections)
2106{
2107 // From HtmlGenerator::generateObsoleteQmlMembersFile.
2108 SectionPtrVector summary_spv; // Summaries are not useful in DocBook.
2109 SectionPtrVector details_spv;
2110 if (!sections.hasObsoleteMembers(&summary_spv, &details_spv))
2111 return;
2112
2113 const Aggregate *aggregate = sections.aggregate();
2114 startSection("obsolete", "Obsolete Members for " + aggregate->name());
2115
2116 m_writer->writeStartElement(dbNamespace, "para");
2117 m_writer->writeStartElement(dbNamespace, "emphasis");
2118 m_writer->writeAttribute("role", "bold");
2119 m_writer->writeCharacters("The following members of QML type ");
2120 generateSimpleLink(linkForNode(aggregate, nullptr), aggregate->name());
2121 m_writer->writeCharacters(" are deprecated.");
2122 m_writer->writeEndElement(); // emphasis bold
2123 m_writer->writeCharacters(" We strongly advise against using them in new code.");
2124 m_writer->writeEndElement(); // para
2125 newLine();
2126
2127 for (const auto *section : details_spv) {
2128 const QString &title = "Obsolete " + section->title();
2129 startSection(title.toLower(), title);
2130
2131 const NodeVector &members = section->obsoleteMembers();
2132 NodeVector::ConstIterator m = members.constBegin();
2133 while (m != members.constEnd()) {
2134 if ((*m)->access() != Access::Private)
2135 generateDetailedQmlMember(*m, aggregate);
2136 ++m;
2137 }
2138
2139 endSection();
2140 }
2141
2142 endSection();
2143}
2144
2145static QString nodeToSynopsisTag(const Node *node)
2146{
2147 // Order from Node::nodeTypeString.
2148 if (node->isClass() || node->isQmlType())
2149 return QStringLiteral("classsynopsis");
2150 if (node->isNamespace())
2151 return QStringLiteral("packagesynopsis");
2152 if (node->isPageNode()) {
2153 node->doc().location().warning("Unexpected document node in nodeToSynopsisTag");
2154 return QString();
2155 }
2156 if (node->isEnumType())
2157 return QStringLiteral("enumsynopsis");
2158 if (node->isTypedef())
2159 return QStringLiteral("typedefsynopsis");
2160 if (node->isFunction()) {
2161 // Signals are also encoded as functions (including QML ones).
2162 const auto fn = static_cast<const FunctionNode *>(node);
2163 if (fn->isCtor() || fn->isCCtor() || fn->isMCtor())
2164 return QStringLiteral("constructorsynopsis");
2165 if (fn->isDtor())
2166 return QStringLiteral("destructorsynopsis");
2167 return QStringLiteral("methodsynopsis");
2168 }
2169 if (node->isProperty() || node->isVariable() || node->isQmlProperty())
2170 return QStringLiteral("fieldsynopsis");
2171
2172 node->doc().location().warning(QString("Unknown node tag %1").arg(node->nodeTypeString()));
2173 return QStringLiteral("synopsis");
2174}
2175
2176void DocBookGenerator::generateStartRequisite(const QString &description)
2177{
2178 m_writer->writeStartElement(dbNamespace, "varlistentry");
2179 newLine();
2180 m_writer->writeTextElement(dbNamespace, "term", description);
2181 newLine();
2182 m_writer->writeStartElement(dbNamespace, "listitem");
2183 newLine();
2184 m_writer->writeStartElement(dbNamespace, "para");
2185 m_inPara = true;
2186}
2187
2188void DocBookGenerator::generateEndRequisite()
2189{
2190 m_writer->writeEndElement(); // para
2191 m_inPara = false;
2192 newLine();
2193 m_writer->writeEndElement(); // listitem
2194 newLine();
2195 m_writer->writeEndElement(); // varlistentry
2196 newLine();
2197}
2198
2199void DocBookGenerator::generateRequisite(const QString &description, const QString &value)
2200{
2201 generateStartRequisite(description);
2202 m_writer->writeCharacters(value);
2203 generateEndRequisite();
2204}
2205
2206/*!
2207 * \internal
2208 * Generates the CMake (\a description) requisites
2209 */
2210void DocBookGenerator::generateCMakeRequisite(const QString &findPackage, const QString &linkLibraries)
2211{
2212 const QString description("CMake");
2213 generateStartRequisite(description);
2214 m_writer->writeCharacters(findPackage);
2215 m_writer->writeEndElement(); // para
2216 newLine();
2217
2218 m_writer->writeStartElement(dbNamespace, "para");
2219 m_writer->writeCharacters(linkLibraries);
2220 generateEndRequisite();
2221}
2222
2223void DocBookGenerator::generateSortedNames(const ClassNode *cn, const QList<RelatedClass> &rc)
2224{
2225 // From Generator::appendSortedNames.
2226 QMap<QString, ClassNode *> classMap;
2227 QList<RelatedClass>::ConstIterator r = rc.constBegin();
2228 while (r != rc.constEnd()) {
2229 ClassNode *rcn = (*r).m_node;
2230 if (rcn && rcn->access() == Access::Public && !rcn->isInternal()
2231 && !rcn->doc().isEmpty()) {
2232 classMap[rcn->plainFullName(cn).toLower()] = rcn;
2233 }
2234 ++r;
2235 }
2236
2237 QStringList classNames = classMap.keys();
2238 classNames.sort();
2239
2240 int index = 0;
2241 for (const QString &className : classNames) {
2242 generateFullName(classMap.value(className), cn);
2243 m_writer->writeCharacters(TextUtils::comma(index++, classNames.size()));
2244 }
2245}
2246
2247void DocBookGenerator::generateSortedQmlNames(const Node *base, const QStringList &knownTypes,
2248 const NodeList &subs)
2249{
2250 // From Generator::appendSortedQmlNames.
2251 QMap<QString, Node *> classMap;
2252 QStringList typeNames(knownTypes);
2253 for (const auto sub : subs)
2254 typeNames << sub->name();
2255
2256 for (auto sub : subs) {
2257 QString key{sub->plainFullName(base).toLower()};
2258 // Disambiguate with '(<QML module name>)' if there are clashing type names
2259 if (typeNames.count(sub->name()) > 1)
2260 key.append(": (%1)"_L1.arg(sub->logicalModuleName()));
2261 classMap[key] = sub;
2262 }
2263
2264 QStringList names = classMap.keys();
2265 names.sort();
2266
2267 int index = 0;
2268 for (const QString &name : names) {
2269 generateFullName(classMap.value(name), base);
2270 if (name.contains(':'))
2271 m_writer->writeCharacters(name.section(':', 1));
2272 m_writer->writeCharacters(TextUtils::comma(index++, names.size()));
2273 }
2274}
2275
2276/*!
2277 Lists the required imports and includes.
2278*/
2280{
2281 // Adapted from HtmlGenerator::generateRequisites, but simplified: no need to store all the
2282 // elements, they can be produced one by one.
2283
2284 // Generate the requisites first separately: if some of them are generated, output them in a wrapper.
2285 // This complexity is required to ensure the DocBook file is valid: an empty list is not valid. It is not easy
2286 // to write a truly comprehensive condition.
2287 QXmlStreamWriter* oldWriter = m_writer;
2288 QString output;
2289 m_writer = new QXmlStreamWriter(&output);
2290
2291 // Includes.
2292 if (aggregate->includeFile()) generateRequisite("Header", *aggregate->includeFile());
2293
2294 // Since and project.
2295 if (!aggregate->since().isEmpty())
2296 generateRequisite("Since", formatSince(aggregate));
2297
2298 if (aggregate->isClassNode() || aggregate->isNamespace()) {
2299 // CMake and QT variable.
2300 const CollectionNode *cn =
2301 m_qdb->getCollectionNode(aggregate->physicalModuleName(), NodeType::Module);
2302
2303 if (const auto result = cmakeRequisite(cn)) {
2304 generateCMakeRequisite(result->first, result->second);
2305 }
2306
2307 if (cn && !cn->qtVariable().isEmpty())
2308 generateRequisite("qmake", "QT += " + cn->qtVariable());
2309 }
2310
2311 if (aggregate->nodeType() == NodeType::Class) {
2312 // Native type information.
2313 auto *classe = const_cast<ClassNode *>(static_cast<const ClassNode *>(aggregate));
2314 if (classe && classe->isQmlNativeType() && !classe->isInternal()) {
2315 generateStartRequisite("In QML");
2316
2317 qsizetype idx{0};
2318 QList<QmlTypeNode *> nativeTypes { classe->qmlNativeTypes().cbegin(), classe->qmlNativeTypes().cend()};
2319 std::sort(nativeTypes.begin(), nativeTypes.end(), Node::nodeNameLessThan);
2320
2321 for (const auto &item : std::as_const(nativeTypes)) {
2322 generateFullName(item, classe);
2323 m_writer->writeCharacters(
2324 TextUtils::comma(idx++, nativeTypes.size()));
2325 }
2326 generateEndRequisite();
2327 }
2328
2329 // Inherits.
2330 QList<RelatedClass>::ConstIterator r;
2331 const auto *metaTags = classe ? classe->doc().metaTagMap() : nullptr;
2332 bool suppressInherits = metaTags && metaTags->contains(u"qdoc-suppress-inheritance"_s);
2333 if (classe && !suppressInherits && !classe->baseClasses().isEmpty()) {
2334 generateStartRequisite("Inherits");
2335
2336 r = classe->baseClasses().constBegin();
2337 int index = 0;
2338 while (r != classe->baseClasses().constEnd()) {
2339 if ((*r).m_node) {
2340 generateFullName((*r).m_node, classe);
2341
2342 if ((*r).m_access == Access::Protected)
2343 m_writer->writeCharacters(" (protected)");
2344 else if ((*r).m_access == Access::Private)
2345 m_writer->writeCharacters(" (private)");
2346 m_writer->writeCharacters(
2347 TextUtils::comma(index++, classe->baseClasses().size()));
2348 }
2349 ++r;
2350 }
2351
2352 generateEndRequisite();
2353 }
2354
2355 // Inherited by.
2356 if (!classe->derivedClasses().isEmpty()) {
2357 generateStartRequisite("Inherited By");
2358 generateSortedNames(classe, classe->derivedClasses());
2359 generateEndRequisite();
2360 }
2361 }
2362
2363 // Group.
2364 if (!aggregate->groupNames().empty()) {
2365 generateStartRequisite("Group");
2366 generateGroupReferenceText(aggregate);
2367 generateEndRequisite();
2368 }
2369
2370 // Status.
2371 if (auto status = formatStatus(aggregate, m_qdb); status)
2372 generateRequisite("Status", status.value());
2373
2374 // Write the elements as a list if not empty.
2375 delete m_writer;
2376 m_writer = oldWriter;
2377
2378 if (!output.isEmpty()) {
2379 // Namespaces are mangled in this output, because QXmlStreamWriter doesn't know about them. (Letting it know
2380 // would imply generating the xmlns declaration one more time.)
2381 static const QRegularExpression xmlTag(R"(<(/?)n\d+:)"); // Only for DocBook tags.
2382 static const QRegularExpression xmlnsDocBookDefinition(R"( xmlns:n\d+=")" + QString{dbNamespace} + "\"");
2383 static const QRegularExpression xmlnsXLinkDefinition(R"( xmlns:n\d+=")" + QString{xlinkNamespace} + "\"");
2384 static const QRegularExpression xmlAttr(R"( n\d+:)"); // Only for XLink attributes.
2385 // Space at the beginning!
2386 const QString cleanOutput = output.replace(xmlTag, R"(<\1db:)")
2387 .replace(xmlnsDocBookDefinition, "")
2388 .replace(xmlnsXLinkDefinition, "")
2389 .replace(xmlAttr, " xlink:");
2390
2391 m_writer->writeStartElement(dbNamespace, "variablelist");
2392 if (m_useITS)
2393 m_writer->writeAttribute(itsNamespace, "translate", "no");
2394 newLine();
2395
2396 m_writer->device()->write(cleanOutput.toUtf8());
2397
2398 m_writer->writeEndElement(); // variablelist
2399 newLine();
2400 }
2401}
2402
2403/*!
2404 Lists the required imports and includes.
2405*/
2407{
2408 // From HtmlGenerator::generateQmlRequisites, but simplified: no need to store all the elements,
2409 // they can be produced one by one and still keep the right order.
2410 if (!qcn)
2411 return;
2412
2413 const QString importText = "Import Statement";
2414 const QString sinceText = "Since";
2415 const QString inheritedByText = "Inherited By";
2416 const QString inheritsText = "Inherits";
2417 const QString nativeTypeText = "In C++";
2418 const QString groupText = "Group";
2419 const QString statusText = "Status";
2420
2421 const CollectionNode *collection = qcn->logicalModule();
2422
2423 NodeList subs;
2424 QmlTypeNode::subclasses(qcn, subs);
2425
2426 QmlTypeNode *base = qcn->qmlBaseNode();
2427 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
2428 while (base) {
2429 const NodeContext context = base->createContext();
2430 if (InclusionFilter::isIncluded(policy, context))
2431 break;
2432 base = base->qmlBaseNode();
2433 }
2434
2435 // Skip import statement for \internal collections
2436 bool generate_import_statement = !qcn->logicalModuleName().isEmpty();
2437 if (generate_import_statement && collection) {
2438 const NodeContext context = collection->createContext();
2439 generate_import_statement = InclusionFilter::isIncluded(policy, context);
2440 }
2441 // Detect if anything is generated in this method. If not, exit early to avoid having an empty list.
2442 const bool generates_something = generate_import_statement || !qcn->since().isEmpty() || !subs.isEmpty() || base;
2443
2444 if (!generates_something)
2445 return;
2446
2447 QStringList knownTypeNames{qcn->name()};
2448 if (base)
2449 knownTypeNames << base->name();
2450
2451 // Start writing the elements as a list.
2452 m_writer->writeStartElement(dbNamespace, "variablelist");
2453 if (m_useITS)
2454 m_writer->writeAttribute(itsNamespace, "translate", "no");
2455 newLine();
2456
2457 if (generate_import_statement) {
2458 QStringList parts = QStringList() << "import" << qcn->logicalModuleName() << qcn->logicalModuleVersion();
2459 generateRequisite(importText, parts.join(' ').trimmed());
2460 }
2461
2462 // Since and project.
2463 if (!qcn->since().isEmpty())
2464 generateRequisite(sinceText, formatSince(qcn));
2465
2466 // Native type information.
2467 ClassNode *cn = (const_cast<QmlTypeNode *>(qcn))->classNode();
2468 if (cn && cn->isQmlNativeType() && !cn->isInternal()) {
2469 generateStartRequisite(nativeTypeText);
2470 generateSimpleLink(fullDocumentLocation(cn), cn->name());
2471 generateEndRequisite();
2472 }
2473
2474 // Inherits.
2475 if (base) {
2476 generateStartRequisite(inheritsText);
2477 generateSimpleLink(fullDocumentLocation(base), base->name());
2478 // Disambiguate with '(<QML module name>)' if there are clashing type names
2479 for (const auto sub : std::as_const(subs)) {
2480 if (knownTypeNames.contains(sub->name())) {
2481 m_writer->writeCharacters(" (%1)"_L1.arg(base->logicalModuleName()));
2482 break;
2483 }
2484 }
2485 generateEndRequisite();
2486 }
2487
2488 // Inherited by.
2489 if (!subs.isEmpty()) {
2490 generateStartRequisite(inheritedByText);
2491 generateSortedQmlNames(qcn, knownTypeNames, subs);
2492 generateEndRequisite();
2493 }
2494
2495 // Group.
2496 if (!qcn->groupNames().empty()) {
2497 generateStartRequisite(groupText);
2499 generateEndRequisite();
2500 }
2501
2502 // Status.
2503 if (auto status = formatStatus(qcn, m_qdb); status)
2504 generateRequisite(statusText, status.value());
2505
2506 m_writer->writeEndElement(); // variablelist
2507 newLine();
2508}
2509
2511{
2512 // From Generator::generateStatus.
2513 switch (node->status()) {
2514 case Status::Active:
2515 // Output the module 'state' description if set.
2516 if (node->isModule() || node->isQmlModule()) {
2517 const QString &state = static_cast<const CollectionNode*>(node)->state();
2518 if (!state.isEmpty()) {
2519 m_writer->writeStartElement(dbNamespace, "para");
2520 m_writer->writeCharacters("This " + typeString(node) + " is in ");
2521 m_writer->writeStartElement(dbNamespace, "emphasis");
2522 m_writer->writeCharacters(state);
2523 m_writer->writeEndElement(); // emphasis
2524 m_writer->writeCharacters(" state.");
2525 m_writer->writeEndElement(); // para
2526 newLine();
2527 return true;
2528 }
2529 }
2530 if (const auto &version = node->deprecatedSince(); !version.isEmpty()) {
2531 m_writer->writeStartElement(dbNamespace, "para");
2532 m_writer->writeCharacters("This " + typeString(node)
2533 + " is scheduled for deprecation in version "
2534 + version + ".");
2535 m_writer->writeEndElement(); // para
2536 newLine();
2537 return true;
2538 }
2539 return false;
2540 case Status::Preliminary:
2541 m_writer->writeStartElement(dbNamespace, "para");
2542 m_writer->writeStartElement(dbNamespace, "emphasis");
2543 m_writer->writeAttribute("role", "bold");
2544 m_writer->writeCharacters(
2545 Config::instance()
2546 .get(CONFIG_PRELIMINARY + Config::dot + CONFIG_DESCRIPTION)
2547 .asString()
2548 .replace('\1'_L1, typeString(node)));
2549 m_writer->writeEndElement(); // emphasis
2550 m_writer->writeEndElement(); // para
2551 newLine();
2552 return true;
2553 case Status::Deprecated:
2554 m_writer->writeStartElement(dbNamespace, "para");
2555 if (node->isAggregate()) {
2556 m_writer->writeStartElement(dbNamespace, "emphasis");
2557 m_writer->writeAttribute("role", "bold");
2558 }
2559 m_writer->writeCharacters("This " + typeString(node) + " is deprecated");
2560 if (const QString &version = node->deprecatedSince(); !version.isEmpty()) {
2561 m_writer->writeCharacters(" since ");
2562 if (node->isQmlNode() && !node->logicalModuleName().isEmpty())
2563 m_writer->writeCharacters(node->logicalModuleName() + " ");
2564 m_writer->writeCharacters(version);
2565 }
2566 m_writer->writeCharacters(". We strongly advise against using it in new code.");
2567 if (node->isAggregate())
2568 m_writer->writeEndElement(); // emphasis
2569 m_writer->writeEndElement(); // para
2570 newLine();
2571 return true;
2572 case Status::Internal:
2574 default:
2575 return false;
2576 }
2577}
2578
2579/*!
2580 Generate a list of function signatures. The function nodes
2581 are in \a nodes.
2582 */
2583void DocBookGenerator::generateSignatureList(const NodeList &nodes)
2584{
2585 // From Generator::signatureList and Generator::appendSignature.
2586 m_writer->writeStartElement(dbNamespace, "itemizedlist");
2587 newLine();
2588
2589 NodeList::ConstIterator n = nodes.constBegin();
2590 while (n != nodes.constEnd()) {
2591 m_writer->writeStartElement(dbNamespace, "listitem");
2592 newLine();
2593 m_writer->writeStartElement(dbNamespace, "para");
2594
2595 generateSimpleLink(currentGenerator()->fullDocumentLocation(*n),
2596 (*n)->signature(Node::SignaturePlain));
2597
2598 m_writer->writeEndElement(); // para
2599 newLine();
2600 m_writer->writeEndElement(); // itemizedlist
2601 newLine();
2602 ++n;
2603 }
2604
2605 m_writer->writeEndElement(); // itemizedlist
2606 newLine();
2607}
2608
2609/*!
2610 * Return a string representing a text that exposes information about
2611 * the groups that the \a node is part of.
2612 */
2614{
2615 // From HtmlGenerator::groupReferenceText
2616
2617 if (!node->isAggregate())
2618 return;
2619 const auto aggregate = static_cast<const Aggregate *>(node);
2620
2621 const QStringList &groups_names{aggregate->groupNames()};
2622 if (!groups_names.empty()) {
2623 m_writer->writeCharacters(aggregate->name() + " is part of ");
2624 m_writer->writeStartElement(dbNamespace, "simplelist");
2625
2626 for (qsizetype index{0}; index < groups_names.size(); ++index) {
2627 CollectionNode* group{m_qdb->groups()[groups_names[index]]};
2628 m_qdb->mergeCollections(group);
2629
2630 m_writer->writeStartElement(dbNamespace, "member");
2631 if (QString target{linkForNode(group, nullptr)}; !target.isEmpty())
2632 generateSimpleLink(target, group->fullTitle());
2633 else
2634 m_writer->writeCharacters(group->name());
2635 m_writer->writeEndElement(); // member
2636 }
2637
2638 m_writer->writeEndElement(); // simplelist
2639 newLine();
2640 }
2641}
2642
2643/*!
2644 Generates text that explains how threadsafe and/or reentrant
2645 \a node is.
2646 */
2648{
2649 // From Generator::generateThreadSafeness
2651
2652 const Node *reentrantNode;
2653 Atom reentrantAtom = Atom(Atom::Link, "reentrant");
2654 QString linkReentrant = getAutoLink(&reentrantAtom, node, &reentrantNode);
2655 const Node *threadSafeNode;
2656 Atom threadSafeAtom = Atom(Atom::Link, "thread-safe");
2657 QString linkThreadSafe = getAutoLink(&threadSafeAtom, node, &threadSafeNode);
2658
2659 if (ts == Node::NonReentrant) {
2660 m_writer->writeStartElement(dbNamespace, "warning");
2661 newLine();
2662 m_writer->writeStartElement(dbNamespace, "para");
2663 m_writer->writeCharacters("This " + typeString(node) + " is not ");
2664 generateSimpleLink(linkReentrant, "reentrant");
2665 m_writer->writeCharacters(".");
2666 m_writer->writeEndElement(); // para
2667 newLine();
2668 m_writer->writeEndElement(); // warning
2669
2670 return true;
2671 } else if (ts == Node::Reentrant || ts == Node::ThreadSafe) {
2672 m_writer->writeStartElement(dbNamespace, "note");
2673 newLine();
2674 m_writer->writeStartElement(dbNamespace, "para");
2675
2676 if (node->isAggregate()) {
2677 m_writer->writeCharacters("All functions in this " + typeString(node) + " are ");
2678 if (ts == Node::ThreadSafe)
2679 generateSimpleLink(linkThreadSafe, "thread-safe");
2680 else
2681 generateSimpleLink(linkReentrant, "reentrant");
2682
2683 NodeList reentrant;
2684 NodeList threadsafe;
2685 NodeList nonreentrant;
2686 bool exceptions = hasExceptions(node, reentrant, threadsafe, nonreentrant);
2687 if (!exceptions || (ts == Node::Reentrant && !threadsafe.isEmpty())) {
2688 m_writer->writeCharacters(".");
2689 m_writer->writeEndElement(); // para
2690 newLine();
2691 } else {
2692 m_writer->writeCharacters(" with the following exceptions:");
2693 m_writer->writeEndElement(); // para
2694 newLine();
2695 m_writer->writeStartElement(dbNamespace, "para");
2696
2697 if (ts == Node::Reentrant) {
2698 if (!nonreentrant.isEmpty()) {
2699 m_writer->writeCharacters("These functions are not ");
2700 generateSimpleLink(linkReentrant, "reentrant");
2701 m_writer->writeCharacters(":");
2702 m_writer->writeEndElement(); // para
2703 newLine();
2704 generateSignatureList(nonreentrant);
2705 }
2706 if (!threadsafe.isEmpty()) {
2707 m_writer->writeCharacters("These functions are also ");
2708 generateSimpleLink(linkThreadSafe, "thread-safe");
2709 m_writer->writeCharacters(":");
2710 m_writer->writeEndElement(); // para
2711 newLine();
2712 generateSignatureList(threadsafe);
2713 }
2714 } else { // thread-safe
2715 if (!reentrant.isEmpty()) {
2716 m_writer->writeCharacters("These functions are only ");
2717 generateSimpleLink(linkReentrant, "reentrant");
2718 m_writer->writeCharacters(":");
2719 m_writer->writeEndElement(); // para
2720 newLine();
2721 generateSignatureList(reentrant);
2722 }
2723 if (!nonreentrant.isEmpty()) {
2724 m_writer->writeCharacters("These functions are not ");
2725 generateSimpleLink(linkReentrant, "reentrant");
2726 m_writer->writeCharacters(":");
2727 m_writer->writeEndElement(); // para
2728 newLine();
2729 generateSignatureList(nonreentrant);
2730 }
2731 }
2732 }
2733 } else {
2734 m_writer->writeCharacters("This " + typeString(node) + " is ");
2735 if (ts == Node::ThreadSafe)
2736 generateSimpleLink(linkThreadSafe, "thread-safe");
2737 else
2738 generateSimpleLink(linkReentrant, "reentrant");
2739 m_writer->writeCharacters(".");
2740 m_writer->writeEndElement(); // para
2741 newLine();
2742 }
2743 m_writer->writeEndElement(); // note
2744 newLine();
2745
2746 return true;
2747 }
2748
2749 return false;
2750}
2751
2752/*!
2753 Generate the body of the documentation from the qdoc comment
2754 found with the entity represented by the \a node.
2755 */
2757{
2758 // From Generator::generateBody, without warnings.
2759 const FunctionNode *fn = node->isFunction() ? static_cast<const FunctionNode *>(node) : nullptr;
2760
2761 if (!node->hasDoc()) {
2762 /*
2763 Test for special function, like a destructor or copy constructor,
2764 that has no documentation.
2765 */
2766 if (fn) {
2767 QString t;
2768 if (fn->isDtor()) {
2769 t = "Destroys the instance of " + fn->parent()->name() + ".";
2770 if (fn->isVirtual())
2771 t += " The destructor is virtual.";
2772 } else if (fn->isCtor()) {
2773 t = "Default constructs an instance of " + fn->parent()->name() + ".";
2774 } else if (fn->isCCtor()) {
2775 t = "Copy constructor.";
2776 } else if (fn->isMCtor()) {
2777 t = "Move-copy constructor.";
2778 } else if (fn->isCAssign()) {
2779 t = "Copy-assignment constructor.";
2780 } else if (fn->isMAssign()) {
2781 t = "Move-assignment constructor.";
2782 }
2783
2784 if (!t.isEmpty())
2785 m_writer->writeTextElement(dbNamespace, "para", t);
2786 }
2787 } else if (!node->isSharingComment()) {
2788 // Reimplements clause and type alias info precede body text
2789 if (fn && !fn->overridesThis().isEmpty())
2790 generateReimplementsClause(fn);
2791 else if (node->isProperty()) {
2792 if (static_cast<const PropertyNode *>(node)->propertyType() != PropertyNode::PropertyType::StandardProperty)
2794 }
2795
2796 // Generate the body.
2797 if (!generateText(node->doc().body(), node)) {
2798 if (node->isMarkedReimp())
2799 return;
2800 }
2801
2802 // Output what is after the main body.
2803 if (fn) {
2804 if (fn->isQmlSignal())
2806 if (fn->isPrivateSignal())
2808 if (fn->isInvokable())
2812 if (fn->hasOverloads() && fn->doc().hasOverloadCommand()
2813 && !fn->isSignal() && !fn->isSlot())
2815 }
2816
2817 // Warning generation skipped with respect to Generator::generateBody.
2818 }
2819
2820 generateEnumValuesForQmlReference(node, nullptr);
2821 generateRequiredLinks(node);
2822}
2823
2824/*!
2825 Generates either a link to the project folder for example \a node, or a list
2826 of links files/images if 'url.examples config' variable is not defined.
2827
2828 Does nothing for non-example nodes.
2829*/
2830void DocBookGenerator::generateRequiredLinks(const Node *node)
2831{
2832 // From Generator::generateRequiredLinks.
2833 if (!node->isExample())
2834 return;
2835
2836 const auto en = static_cast<const ExampleNode *>(node);
2837 QString exampleUrl{Config::instance().get(CONFIG_URL + Config::dot + CONFIG_EXAMPLES).asString()};
2838
2839 if (exampleUrl.isEmpty()) {
2840 if (!en->noAutoList()) {
2841 generateFileList(en, false); // files
2842 generateFileList(en, true); // images
2843 }
2844 } else {
2845 generateLinkToExample(en, exampleUrl);
2846 }
2847}
2848
2849/*!
2850 The path to the example replaces a placeholder '\1' character if
2851 one is found in the \a baseUrl string. If no such placeholder is found,
2852 the path is appended to \a baseUrl, after a '/' character if \a baseUrl did
2853 not already end in one.
2854*/
2855void DocBookGenerator::generateLinkToExample(const ExampleNode *en, const QString &baseUrl)
2856{
2857 // From Generator::generateLinkToExample.
2858 QString exampleUrl(baseUrl);
2859 QString link;
2860#ifndef QT_BOOTSTRAPPED
2861 link = QUrl(exampleUrl).host();
2862#endif
2863 if (!link.isEmpty())
2864 link.prepend(" @ ");
2865 link.prepend("Example project");
2866
2867 const QLatin1Char separator('/');
2868 const QLatin1Char placeholder('\1');
2869 if (!exampleUrl.contains(placeholder)) {
2870 if (!exampleUrl.endsWith(separator))
2871 exampleUrl += separator;
2872 exampleUrl += placeholder;
2873 }
2874
2875 // Construct a path to the example; <install path>/<example name>
2876 QStringList path = QStringList()
2877 << Config::instance().get(CONFIG_EXAMPLESINSTALLPATH).asString() << en->name();
2878 path.removeAll(QString());
2879
2880 // Write the link to the example. Typically, this link comes after sections, hence
2881 // wrap it in a section too.
2882 startSection("Example project");
2883
2884 m_writer->writeStartElement(dbNamespace, "para");
2885 generateSimpleLink(exampleUrl.replace(placeholder, path.join(separator)), link);
2886 m_writer->writeEndElement(); // para
2887 newLine();
2888
2889 endSection();
2890}
2891
2892// TODO: [multi-purpose-function-with-flag][generate-file-list]
2893
2894/*!
2895 This function is called when the documentation for an example is
2896 being formatted. It outputs a list of files for the example, which
2897 can be the example's source files or the list of images used by the
2898 example. The images are copied into a subtree of
2899 \c{...doc/html/images/used-in-examples/...}
2900*/
2901void DocBookGenerator::generateFileList(const ExampleNode *en, bool images)
2902{
2903 // TODO: [possibly-stale-duplicate-code][generator-insufficient-structural-abstraction]
2904 // Review and compare this code with
2905 // Generator::generateFileList.
2906 // Some subtle changes that might be semantically equivalent are
2907 // present between the two.
2908 // Supposedly, this version is to be considered stale compared to
2909 // Generator's one and it might be possible to remove it in favor
2910 // of that as long as the difference in output are taken into consideration.
2911
2912 // From Generator::generateFileList
2913 QString tag;
2914 QStringList paths;
2915 if (images) {
2916 paths = en->images();
2917 tag = "Images:";
2918 } else { // files
2919 paths = en->files();
2920 tag = "Files:";
2921 }
2922 std::sort(paths.begin(), paths.end(), Generator::comparePaths);
2923
2924 if (paths.isEmpty())
2925 return;
2926
2927 startSection("", "List of Files");
2928
2929 m_writer->writeStartElement(dbNamespace, "para");
2930 m_writer->writeCharacters(tag);
2931 m_writer->writeEndElement(); // para
2932 newLine();
2933
2934 startSection("List of Files");
2935
2936 m_writer->writeStartElement(dbNamespace, "itemizedlist");
2937 newLine();
2938
2939 for (const auto &path : std::as_const(paths)) {
2940 auto maybe_resolved_file{file_resolver.resolve(path)};
2941 if (!maybe_resolved_file) {
2942 // TODO: [uncentralized-admonition][failed-resolve-file]
2943 QString details = std::transform_reduce(
2944 file_resolver.get_search_directories().cbegin(),
2945 file_resolver.get_search_directories().cend(),
2946 u"Searched directories:"_s,
2947 std::plus(),
2948 [](const DirectoryPath &directory_path) -> QString { return u' ' + directory_path.value(); }
2949 );
2950
2951 en->location().warning(u"Cannot find file to quote from: %1"_s.arg(path), details);
2952
2953 continue;
2954 }
2955
2956 const auto &file{*maybe_resolved_file};
2957 if (images) addImageToCopy(en, file);
2958 else generateExampleFilePage(en, file);
2959
2960 m_writer->writeStartElement(dbNamespace, "listitem");
2961 newLine();
2962 m_writer->writeStartElement(dbNamespace, "para");
2963 generateSimpleLink(file.get_query(), file.get_query());
2964 m_writer->writeEndElement(); // para
2965 m_writer->writeEndElement(); // listitem
2966 newLine();
2967 }
2968
2969 m_writer->writeEndElement(); // itemizedlist
2970 newLine();
2971
2972 endSection();
2973}
2974
2975/*!
2976 Generate a file with the contents of a C++ or QML source file.
2977 */
2979{
2980 // TODO: [generator-insufficient-structural-abstraction]
2981
2982 // From HtmlGenerator::generateExampleFilePage.
2983 if (!node->isExample())
2984 return;
2985
2986 // TODO: Understand if this is safe.
2987 const auto en = static_cast<const ExampleNode *>(node);
2988
2989 // Store current (active) writer
2990 QXmlStreamWriter *currentWriter = m_writer;
2991 m_writer = startDocument(en, resolved_file.get_query());
2992 generateHeader(en->doc().title(), en->subtitle(), en);
2993
2994 Text text;
2995 Quoter quoter;
2996 Doc::quoteFromFile(en->doc().location(), quoter, resolved_file);
2997 QString code = quoter.quoteTo(en->location(), QString(), QString());
2998 CodeMarker *codeMarker = CodeMarker::markerForFileName(resolved_file.get_path());
2999 text << Atom(codeMarker->atomType(), code);
3000 Atom a(codeMarker->atomType(), code);
3001 generateText(text, en);
3002
3003 endDocument(); // Delete m_writer.
3004 m_writer = currentWriter; // Restore writer.
3005}
3006
3007void DocBookGenerator::generateReimplementsClause(const FunctionNode *fn)
3008{
3009 // From Generator::generateReimplementsClause, without warning generation.
3010 if (fn->overridesThis().isEmpty() || !fn->parent()->isClassNode())
3011 return;
3012
3013 auto cn = static_cast<ClassNode *>(fn->parent());
3014
3015 if (const FunctionNode *overrides = cn->findOverriddenFunction(fn);
3016 overrides && !overrides->isPrivate() && !overrides->parent()->isPrivate()) {
3017 if (overrides->hasDoc()) {
3018 m_writer->writeStartElement(dbNamespace, "para");
3019 m_writer->writeCharacters("Reimplements: ");
3020 QString fullName =
3021 overrides->parent()->name() + "::" + overrides->signature(Node::SignaturePlain);
3022 generateFullName(overrides->parent(), fullName, overrides);
3023 m_writer->writeCharacters(".");
3024 m_writer->writeEndElement(); // para
3025 newLine();
3026 return;
3027 }
3028 }
3029
3030 if (const PropertyNode *sameName = cn->findOverriddenProperty(fn); sameName && sameName->hasDoc()) {
3031 m_writer->writeStartElement(dbNamespace, "para");
3032 m_writer->writeCharacters("Reimplements an access function for property: ");
3033 QString fullName = sameName->parent()->name() + "::" + sameName->name();
3034 generateFullName(sameName->parent(), fullName, sameName);
3035 m_writer->writeCharacters(".");
3036 m_writer->writeEndElement(); // para
3037 newLine();
3038 return;
3039 }
3040}
3041
3043{
3044 // From Generator::generateAlsoList.
3045 QList<Text> alsoList = node->doc().alsoList();
3046 supplementAlsoList(node, alsoList);
3047
3048 if (!alsoList.isEmpty()) {
3049 startSection("See Also");
3050
3051 m_writer->writeStartElement(dbNamespace, "para");
3052 m_writer->writeStartElement(dbNamespace, "emphasis");
3053 m_writer->writeCharacters("See also ");
3054 m_writer->writeEndElement(); // emphasis
3055 newLine();
3056
3057 m_writer->writeStartElement(dbNamespace, "simplelist");
3058 m_writer->writeAttribute("type", "vert");
3059 m_writer->writeAttribute("role", "see-also");
3060 newLine();
3061
3062 for (const Text &text : alsoList) {
3063 m_writer->writeStartElement(dbNamespace, "member");
3064 generateText(text, node);
3065 m_writer->writeEndElement(); // member
3066 newLine();
3067 }
3068
3069 m_writer->writeEndElement(); // simplelist
3070 newLine();
3071
3072 m_writer->writeEndElement(); // para
3073 newLine();
3074
3075 endSection();
3076 }
3077}
3078
3079/*!
3080 Open a new file to write XML contents, including the DocBook
3081 opening tag.
3082 */
3083QXmlStreamWriter *DocBookGenerator::startGenericDocument(const Node *node, const QString &fileName)
3084{
3085 Q_ASSERT(node->isPageNode());
3086 QFile *outFile = openSubPageFile(static_cast<const PageNode*>(node), fileName);
3087 m_writer = new QXmlStreamWriter(outFile);
3088 m_writer->setAutoFormatting(false); // We need a precise handling of line feeds.
3089
3090 m_writer->writeStartDocument();
3091 newLine();
3092 m_writer->writeNamespace(dbNamespace, "db");
3093 m_writer->writeNamespace(xlinkNamespace, "xlink");
3094 if (m_useITS)
3095 m_writer->writeNamespace(itsNamespace, "its");
3096 m_writer->writeStartElement(dbNamespace, "article");
3097 m_writer->writeAttribute("version", "5.2");
3098 if (!m_naturalLanguage.isEmpty())
3099 m_writer->writeAttribute("xml:lang", m_naturalLanguage);
3100 newLine();
3101
3102 // Reset the state for the new document.
3103 sectionLevels.resize(0);
3104 m_inPara = false;
3105 m_inList = 0;
3106
3107 return m_writer;
3108}
3109
3110QXmlStreamWriter *DocBookGenerator::startDocument(const Node *node)
3111{
3112 m_hasSection = false;
3113 refMap.clear();
3114
3115 QString fileName = Generator::fileName(node, fileExtension());
3116 return startGenericDocument(node, fileName);
3117}
3118
3119QXmlStreamWriter *DocBookGenerator::startDocument(const ExampleNode *en, const QString &file)
3120{
3121 m_hasSection = false;
3122
3123 QString fileName = linkForExampleFile(file);
3124 return startGenericDocument(en, fileName);
3125}
3126
3127void DocBookGenerator::endDocument()
3128{
3129 m_writer->writeEndElement(); // article
3130 m_writer->writeEndDocument();
3131
3132 m_writer->device()->close();
3133 delete m_writer->device();
3134 delete m_writer;
3135 m_writer = nullptr;
3136}
3137
3138/*!
3139 Generate a reference page for the C++ class, namespace, or
3140 header file documented in \a node.
3141 */
3143{
3144 // Based on HtmlGenerator::generateCppReferencePage.
3145 Q_ASSERT(node->isAggregate());
3146 const auto aggregate = static_cast<const Aggregate *>(node);
3147
3148 QString title;
3149 Text titleText;
3150 const QString typeWord{aggregate->typeWord(true)};
3151 if (aggregate->isNamespace()) {
3152 title = "%1 %2"_L1.arg(aggregate->plainFullName(), typeWord);
3153 } else if (aggregate->isClass()) {
3154 title = "%1 %2"_L1.arg(aggregate->plainFullName(), typeWord);
3155 } else if (aggregate->isHeader()) {
3156 title = aggregate->fullTitle();
3157 if (!aggregate->doc().title().isEmpty())
3158 titleText << aggregate->name() << " - "_L1 << aggregate->doc().title();
3159 }
3160
3161 // Start producing the DocBook file.
3162 m_writer = startDocument(node);
3163
3164 // Info container. Class pages with a template declaration emit the
3165 // subtitle in two steps so concept references in the template head
3166 // can surface as <db:link> elements inside the subtitle text rather
3167 // than being flattened to characters.
3168 if (aggregate->isClass()) {
3169 if (auto templateDecl = node->templateDecl()) {
3170 if (!titleText.isEmpty())
3171 generateHeader(titleText, aggregate);
3172 else
3173 generateHeader(Text() << title, aggregate);
3174
3175 m_writer->writeStartElement(dbNamespace, "subtitle");
3176 if (isApiGenus(aggregate->genus()) && m_useITS)
3177 m_writer->writeAttribute(itsNamespace, "translate", "no");
3178 generateTemplateDecl(&*templateDecl, aggregate);
3179 m_writer->writeCharacters(" "_L1 + aggregate->typeWord(false) + " "_L1
3180 + aggregate->plainFullName());
3181 m_writer->writeEndElement(); // subtitle
3182 newLine();
3183
3184 finishHeader(aggregate);
3185 } else {
3186 if (!titleText.isEmpty())
3187 generateHeader(titleText, QString(), aggregate);
3188 else
3189 generateHeader(title, QString(), aggregate);
3190 }
3191 } else {
3192 if (!titleText.isEmpty())
3193 generateHeader(titleText, QString(), aggregate);
3194 else
3195 generateHeader(title, QString(), aggregate);
3196 }
3197
3198 generateRequisites(aggregate);
3199 generateStatus(aggregate);
3200
3201 // Element synopsis.
3203
3204 // Actual content.
3205 if (!aggregate->doc().isEmpty()) {
3206 startSection("details", "Detailed Description");
3207
3208 generateBody(aggregate);
3209 generateAlsoList(aggregate);
3210
3211 endSection();
3212 }
3213
3214 Sections sections(aggregate);
3215 const SectionVector &sectionVector = sections.detailsSections();
3216 for (const Section &section : sectionVector) {
3217 if (section.members().isEmpty())
3218 continue;
3219
3220 startSection(section.title().toLower(), section.title());
3221
3222 for (const Node *member : section.members()) {
3223 if (member->nodeType() != NodeType::Class) {
3224 // This function starts its own section.
3225 generateDetailedMember(member, aggregate);
3226 } else {
3227 startSectionBegin();
3228 m_writer->writeCharacters("class ");
3229 generateFullName(member, aggregate);
3230 startSectionEnd();
3231
3232 generateBrief(member);
3233
3234 endSection();
3235 }
3236 }
3237
3238 endSection();
3239 }
3240
3241 generateObsoleteMembers(sections);
3242
3243 endDocument();
3244}
3245
3246void DocBookGenerator::generateSynopsisInfo(const QString &key, const QString &value)
3247{
3248 m_writer->writeStartElement(dbNamespace, "synopsisinfo");
3249 m_writer->writeAttribute("role", key);
3250 m_writer->writeCharacters(value);
3251 m_writer->writeEndElement(); // synopsisinfo
3252 newLine();
3253}
3254
3255void DocBookGenerator::generateModifier(const QString &value)
3256{
3257 m_writer->writeTextElement(dbNamespace, "modifier", value);
3258 newLine();
3259}
3260
3261/*!
3262 Generate the metadata for the given \a node in DocBook.
3263 */
3265{
3266 if (!node)
3267 return;
3268
3269 // From Generator::generateStatus, HtmlGenerator::generateRequisites,
3270 // Generator::generateThreadSafeness, QDocIndexFiles::generateIndexSection.
3271
3272 // This function is the major place where DocBook extensions are used.
3273 if (!m_useDocBook52)
3274 return;
3275
3276 // Nothing to export in some cases. Note that isSharedCommentNode() returns
3277 // true also for QML property groups.
3279 return;
3280
3281 // Cast the node to several subtypes (null pointer if the node is not of the required type).
3282 const Aggregate *aggregate =
3283 node->isAggregate() ? static_cast<const Aggregate *>(node) : nullptr;
3284 const ClassNode *classNode = node->isClass() ? static_cast<const ClassNode *>(node) : nullptr;
3285 const FunctionNode *functionNode =
3286 node->isFunction() ? static_cast<const FunctionNode *>(node) : nullptr;
3287 const PropertyNode *propertyNode =
3288 node->isProperty() ? static_cast<const PropertyNode *>(node) : nullptr;
3289 const VariableNode *variableNode =
3290 node->isVariable() ? static_cast<const VariableNode *>(node) : nullptr;
3291 const EnumNode *enumNode = node->isEnumType() ? static_cast<const EnumNode *>(node) : nullptr;
3292 const QmlPropertyNode *qpn =
3293 node->isQmlProperty() ? static_cast<const QmlPropertyNode *>(node) : nullptr;
3294 const QmlTypeNode *qcn = node->isQmlType() ? static_cast<const QmlTypeNode *>(node) : nullptr;
3295 // Typedefs are ignored, as they correspond to enums.
3296 // Groups and modules are ignored.
3297 // Documents are ignored, they have no interesting metadata.
3298
3299 // Start the synopsis tag.
3300 QString synopsisTag = nodeToSynopsisTag(node);
3301 m_writer->writeStartElement(dbNamespace, synopsisTag);
3302 newLine();
3303
3304 // Name and basic properties of each tag (like types and parameters).
3305 if (node->isClass()) {
3306 m_writer->writeStartElement(dbNamespace, "ooclass");
3307 m_writer->writeTextElement(dbNamespace, "classname", node->plainName());
3308 m_writer->writeEndElement(); // ooclass
3309 newLine();
3310 } else if (node->isNamespace()) {
3311 m_writer->writeTextElement(dbNamespace, "namespacename", node->plainName());
3312 newLine();
3313 } else if (node->isQmlType()) {
3314 m_writer->writeStartElement(dbNamespace, "ooclass");
3315 m_writer->writeTextElement(dbNamespace, "classname", node->plainName());
3316 m_writer->writeEndElement(); // ooclass
3317 newLine();
3318 if (!qcn->groupNames().isEmpty())
3319 m_writer->writeAttribute("groups", qcn->groupNames().join(QLatin1Char(',')));
3320 } else if (node->isProperty()) {
3321 m_writer->writeTextElement(dbNamespace, "modifier", "(Qt property)");
3322 newLine();
3323 m_writer->writeTextElement(dbNamespace, "type", propertyNode->dataType());
3324 newLine();
3325 m_writer->writeTextElement(dbNamespace, "varname", node->plainName());
3326 newLine();
3327 } else if (node->isVariable()) {
3328 if (variableNode->isStatic()) {
3329 m_writer->writeTextElement(dbNamespace, "modifier", "static");
3330 newLine();
3331 }
3332 m_writer->writeTextElement(dbNamespace, "type", variableNode->dataType());
3333 newLine();
3334 m_writer->writeTextElement(dbNamespace, "varname", node->plainName());
3335 newLine();
3336 } else if (node->isEnumType()) {
3337 if (!enumNode->isAnonymous()) {
3338 m_writer->writeTextElement(dbNamespace, "enumname", node->plainName());
3339 newLine();
3340 }
3341 } else if (node->isQmlProperty()) {
3342 QString name = node->name();
3343 if (qpn->isAttached())
3344 name.prepend(qpn->element() + QLatin1Char('.'));
3345
3346 m_writer->writeTextElement(dbNamespace, "type", qpn->dataType());
3347 newLine();
3348 m_writer->writeTextElement(dbNamespace, "varname", name);
3349 newLine();
3350
3351 const bool readOnly = qpn->isReadOnly();
3352 const bool required = qpn->isRequired();
3353
3354 // Semantic modifiers for DocBook (machine-readable)
3355 if (qpn->isAttached()) {
3356 m_writer->writeTextElement(dbNamespace, "modifier", "attached");
3357 newLine();
3358 }
3359 if (!readOnly) {
3360 m_writer->writeTextElement(dbNamespace, "modifier", "writable");
3361 newLine();
3362 }
3363 if (required) {
3364 m_writer->writeTextElement(dbNamespace, "modifier", "required");
3365 newLine();
3366 }
3367 // Presentation modifiers (human-readable)
3368 if (readOnly) {
3369 generateModifier("[read-only]");
3370 newLine();
3371 }
3372 if (qpn->isDefault()) {
3373 generateModifier("[default]");
3374 newLine();
3375 }
3376 } else if (node->isFunction()) {
3377 if (functionNode->virtualness() != "non")
3378 generateModifier("virtual");
3379 if (functionNode->isConst())
3380 generateModifier("const");
3381 if (functionNode->isStatic())
3382 generateModifier("static");
3383
3384 if (!functionNode->isMacro() && !functionNode->isCtor() &&
3385 !functionNode->isCCtor() && !functionNode->isMCtor()
3386 && !functionNode->isDtor()) {
3387 if (functionNode->returnType() == "void")
3388 m_writer->writeEmptyElement(dbNamespace, "void");
3389 else
3390 m_writer->writeTextElement(dbNamespace, "type", functionNode->returnTypeString());
3391 newLine();
3392 }
3393 // Remove two characters from the plain name to only get the name
3394 // of the method without parentheses (only for functions, not macros).
3395 QString name = node->plainName();
3396 if (name.endsWith("()"))
3397 name.chop(2);
3398 m_writer->writeTextElement(dbNamespace, "methodname", name);
3399 newLine();
3400
3401 if (functionNode->parameters().isEmpty()) {
3402 m_writer->writeEmptyElement(dbNamespace, "void");
3403 newLine();
3404 }
3405
3406 const Parameters &lp = functionNode->parameters();
3407 for (int i = 0; i < lp.count(); ++i) {
3408 const Parameter &parameter = lp.at(i);
3409 m_writer->writeStartElement(dbNamespace, "methodparam");
3410 newLine();
3411 m_writer->writeTextElement(dbNamespace, "type", parameter.type());
3412 newLine();
3413 m_writer->writeTextElement(dbNamespace, "parameter", parameter.name());
3414 newLine();
3415 if (!parameter.defaultValue().isEmpty()) {
3416 m_writer->writeTextElement(dbNamespace, "initializer", parameter.defaultValue());
3417 newLine();
3418 }
3419 m_writer->writeEndElement(); // methodparam
3420 newLine();
3421 }
3422
3423 if (functionNode->isImplicitlyGenerated())
3424 generateModifier("implicit");
3425 else if (functionNode->isExplicitlyDefaulted())
3426 generateModifier("default");
3427 else if (functionNode->isDeletedAsWritten())
3428 generateModifier("delete");
3429 if (functionNode->isFinal())
3430 generateModifier("final");
3431 if (functionNode->isOverride())
3432 generateModifier("override");
3433 } else if (node->isTypedef()) {
3434 m_writer->writeTextElement(dbNamespace, "typedefname", node->plainName());
3435 newLine();
3436 } else {
3437 node->doc().location().warning(
3438 QStringLiteral("Unexpected node type in generateDocBookSynopsis: %1")
3439 .arg(node->nodeTypeString()));
3440 newLine();
3441 }
3442
3443 // Enums and typedefs.
3444 if (enumNode) {
3445 for (const EnumItem &item : enumNode->items()) {
3446 m_writer->writeStartElement(dbNamespace, "enumitem");
3447 newLine();
3448 m_writer->writeTextElement(dbNamespace, "enumidentifier", item.name());
3449 newLine();
3450 m_writer->writeTextElement(dbNamespace, "enumvalue", item.value());
3451 newLine();
3452 m_writer->writeEndElement(); // enumitem
3453 newLine();
3454 }
3455
3456 if (enumNode->items().isEmpty()) {
3457 // If the enumeration is empty (really rare case), still produce
3458 // something for the DocBook document to be valid.
3459 m_writer->writeStartElement(dbNamespace, "enumitem");
3460 newLine();
3461 m_writer->writeEmptyElement(dbNamespace, "enumidentifier");
3462 newLine();
3463 m_writer->writeEndElement(); // enumitem
3464 newLine();
3465 }
3466 }
3467
3468 // Below: only synopsisinfo within synopsisTag. These elements must be at
3469 // the end of the tag, as per DocBook grammar.
3470
3471 // Information for functions that could not be output previously
3472 // (synopsisinfo).
3473 if (node->isFunction()) {
3474 generateSynopsisInfo("meta", functionNode->metanessString());
3475
3476 if (functionNode->isOverload()) {
3477 generateSynopsisInfo("overload", "overload");
3478 generateSynopsisInfo("overload-number",
3479 QString::number(functionNode->overloadNumber()));
3480 }
3481
3482 if (functionNode->isRef())
3483 generateSynopsisInfo("refness", QString::number(1));
3484 else if (functionNode->isRefRef())
3485 generateSynopsisInfo("refness", QString::number(2));
3486
3487 if (functionNode->hasAssociatedProperties()) {
3488 QStringList associatedProperties;
3489 const auto &nodes = functionNode->associatedProperties();
3490 for (const Node *n : nodes) {
3491 const auto pn = static_cast<const PropertyNode *>(n);
3492 associatedProperties << pn->name();
3493 }
3494 associatedProperties.sort();
3495 generateSynopsisInfo("associated-property",
3496 associatedProperties.join(QLatin1Char(',')));
3497 }
3498
3499 QString signature = functionNode->signature(Node::SignatureReturnType);
3500 // 'const' is already part of FunctionNode::signature()
3501 if (functionNode->isFinal())
3502 signature += " final";
3503 if (functionNode->isOverride())
3504 signature += " override";
3505 if (functionNode->isPureVirtual())
3506 signature += " = 0";
3507 else if (functionNode->isExplicitlyDefaulted())
3508 signature += " = default";
3509 else if (functionNode->isDeletedAsWritten())
3510 signature += " = delete";
3511 if (const auto &req = functionNode->trailingRequiresClause(); req && !req->isEmpty())
3512 signature += " requires " + *req;
3513 generateSynopsisInfo("signature", signature);
3514 }
3515
3516 // Accessibility status.
3517 if (!node->isPageNode() && !node->isCollectionNode()) {
3518 switch (node->access()) {
3519 case Access::Public:
3520 generateSynopsisInfo("access", "public");
3521 break;
3522 case Access::Protected:
3523 generateSynopsisInfo("access", "protected");
3524 break;
3525 case Access::Private:
3526 generateSynopsisInfo("access", "private");
3527 break;
3528 default:
3529 break;
3530 }
3531 if (node->isAbstract())
3532 generateSynopsisInfo("abstract", "true");
3533 }
3534
3535 // Status.
3536 switch (node->status()) {
3537 case Status::Active:
3538 generateSynopsisInfo("status", "active");
3539 break;
3540 case Status::Preliminary:
3541 generateSynopsisInfo("status",
3542 Config::instance().get(CONFIG_PRELIMINARY).asString().toLower());
3543 break;
3544 case Status::Deprecated:
3545 generateSynopsisInfo("status", "deprecated");
3546 break;
3547 case Status::Internal:
3548 case Status::InternalAuto:
3549 generateSynopsisInfo("status", "internal");
3550 break;
3551 default:
3552 generateSynopsisInfo("status", "main");
3553 break;
3554 }
3555
3556 // C++ classes and name spaces.
3557 if (aggregate) {
3558 // Includes.
3559 if (aggregate->includeFile()) generateSynopsisInfo("headers", *aggregate->includeFile());
3560
3561 // Since and project.
3562 if (!aggregate->since().isEmpty())
3563 generateSynopsisInfo("since", formatSince(aggregate));
3564
3565 if (aggregate->nodeType() == NodeType::Class || aggregate->nodeType() == NodeType::Namespace) {
3566 // CMake and QT variable.
3567 if (!aggregate->physicalModuleName().isEmpty()) {
3568 const CollectionNode *cn =
3569 m_qdb->getCollectionNode(aggregate->physicalModuleName(), NodeType::Module);
3570
3571 if (const auto result = cmakeRequisite(cn)) {
3572 generateSynopsisInfo("cmake-find-package", result->first);
3573 generateSynopsisInfo("cmake-target-link-libraries", result->second);
3574 }
3575
3576 if (cn && !cn->qtVariable().isEmpty())
3577 generateSynopsisInfo("qmake", "QT += " + cn->qtVariable());
3578 }
3579 }
3580
3581 if (aggregate->nodeType() == NodeType::Class) {
3582 // Native type
3583 auto *classe = const_cast<ClassNode *>(static_cast<const ClassNode *>(aggregate));
3584 if (classe && classe->isQmlNativeType() && !classe->isInternal()) {
3585 m_writer->writeStartElement(dbNamespace, "synopsisinfo");
3586 m_writer->writeAttribute("role", "nativeTypeFor");
3587
3588 QList<QmlTypeNode *> nativeTypes { classe->qmlNativeTypes().cbegin(), classe->qmlNativeTypes().cend()};
3589 std::sort(nativeTypes.begin(), nativeTypes.end(), Node::nodeNameLessThan);
3590
3591 for (auto item : std::as_const(nativeTypes)) {
3592 const Node *otherNode{nullptr};
3593 Atom a = Atom(Atom::LinkNode, Utilities::stringForNode(item));
3594 const QString &link = getAutoLink(&a, aggregate, &otherNode);
3595 generateSimpleLink(link, item->name());
3596 }
3597
3598 m_writer->writeEndElement(); // synopsisinfo
3599 }
3600
3601 // Inherits.
3602 QList<RelatedClass>::ConstIterator r;
3603 if (!classe->baseClasses().isEmpty()) {
3604 m_writer->writeStartElement(dbNamespace, "synopsisinfo");
3605 m_writer->writeAttribute("role", "inherits");
3606
3607 r = classe->baseClasses().constBegin();
3608 int index = 0;
3609 while (r != classe->baseClasses().constEnd()) {
3610 if ((*r).m_node) {
3611 generateFullName((*r).m_node, classe);
3612
3613 if ((*r).m_access == Access::Protected) {
3614 m_writer->writeCharacters(" (protected)");
3615 } else if ((*r).m_access == Access::Private) {
3616 m_writer->writeCharacters(" (private)");
3617 }
3618 m_writer->writeCharacters(
3619 TextUtils::comma(index++, classe->baseClasses().size()));
3620 }
3621 ++r;
3622 }
3623
3624 m_writer->writeEndElement(); // synopsisinfo
3625 newLine();
3626 }
3627
3628 // Inherited by.
3629 if (!classe->derivedClasses().isEmpty()) {
3630 m_writer->writeStartElement(dbNamespace, "synopsisinfo");
3631 m_writer->writeAttribute("role", "inheritedBy");
3632 generateSortedNames(classe, classe->derivedClasses());
3633 m_writer->writeEndElement(); // synopsisinfo
3634 newLine();
3635 }
3636 }
3637 }
3638
3639 // QML types.
3640 if (qcn) {
3641 // Module name and version (i.e. import).
3642 QString logicalModuleVersion;
3643 const CollectionNode *collection =
3644 m_qdb->getCollectionNode(qcn->logicalModuleName(), qcn->nodeType());
3645 if (collection)
3646 logicalModuleVersion = collection->logicalModuleVersion();
3647 else
3648 logicalModuleVersion = qcn->logicalModuleVersion();
3649
3650 QStringList importText;
3651 importText << "import " + qcn->logicalModuleName();
3652 if (!logicalModuleVersion.isEmpty())
3653 importText << logicalModuleVersion;
3654 generateSynopsisInfo("import", importText.join(' '));
3655
3656 // Since and project.
3657 if (!qcn->since().isEmpty())
3658 generateSynopsisInfo("since", formatSince(qcn));
3659
3660 QmlTypeNode *base = qcn->qmlBaseNode();
3661 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
3662 while (base) {
3663 const NodeContext context = base->createContext();
3664 if (InclusionFilter::isIncluded(policy, context))
3665 break;
3666 base = base->qmlBaseNode();
3667 }
3668
3669 QStringList knownTypeNames{qcn->name()};
3670 if (base)
3671 knownTypeNames << base->name();
3672
3673 // Inherited by.
3674 NodeList subs;
3675 QmlTypeNode::subclasses(qcn, subs);
3676 if (!subs.isEmpty()) {
3677 m_writer->writeTextElement(dbNamespace, "synopsisinfo");
3678 m_writer->writeAttribute("role", "inheritedBy");
3679 generateSortedQmlNames(qcn, knownTypeNames, subs);
3680 m_writer->writeEndElement(); // synopsisinfo
3681 newLine();
3682 }
3683
3684 // Inherits.
3685 if (base) {
3686 const Node *otherNode = nullptr;
3687 Atom a = Atom(Atom::LinkNode, Utilities::stringForNode(base));
3688 QString link = getAutoLink(&a, base, &otherNode);
3689
3690 m_writer->writeTextElement(dbNamespace, "synopsisinfo");
3691 m_writer->writeAttribute("role", "inherits");
3692 generateSimpleLink(link, base->name());
3693 // Disambiguate with '(<QML module name>)' if there are clashing type names
3694 for (const auto sub : std::as_const(subs)) {
3695 if (knownTypeNames.contains(sub->name())) {
3696 m_writer->writeCharacters(" (%1)"_L1.arg(base->logicalModuleName()));
3697 break;
3698 }
3699 }
3700 m_writer->writeEndElement(); // synopsisinfo
3701 newLine();
3702 }
3703
3704 // Native type
3705 ClassNode *cn = (const_cast<QmlTypeNode *>(qcn))->classNode();
3706
3707 if (cn && cn->isQmlNativeType() && !cn->isInternal()) {
3708 const Node *otherNode = nullptr;
3709 Atom a = Atom(Atom::LinkNode, Utilities::stringForNode(qcn));
3710 QString link = getAutoLink(&a, cn, &otherNode);
3711
3712 m_writer->writeTextElement(dbNamespace, "synopsisinfo");
3713 m_writer->writeAttribute("role", "nativeType");
3714 generateSimpleLink(link, cn->name());
3715 m_writer->writeEndElement(); // synopsisinfo
3716 newLine();
3717 }
3718 }
3719
3720 // Thread safeness.
3721 switch (node->threadSafeness()) {
3722 case Node::UnspecifiedSafeness:
3723 generateSynopsisInfo("threadsafeness", "unspecified");
3724 break;
3725 case Node::NonReentrant:
3726 generateSynopsisInfo("threadsafeness", "non-reentrant");
3727 break;
3728 case Node::Reentrant:
3729 generateSynopsisInfo("threadsafeness", "reentrant");
3730 break;
3731 case Node::ThreadSafe:
3732 generateSynopsisInfo("threadsafeness", "thread safe");
3733 break;
3734 default:
3735 generateSynopsisInfo("threadsafeness", "unspecified");
3736 break;
3737 }
3738
3739 // Module.
3740 if (!node->physicalModuleName().isEmpty())
3741 generateSynopsisInfo("module", node->physicalModuleName());
3742
3743 // Group.
3744 if (classNode && !classNode->groupNames().isEmpty()) {
3745 generateSynopsisInfo("groups", classNode->groupNames().join(QLatin1Char(',')));
3746 } else if (qcn && !qcn->groupNames().isEmpty()) {
3747 generateSynopsisInfo("groups", qcn->groupNames().join(QLatin1Char(',')));
3748 }
3749
3750 // Properties.
3751 if (propertyNode) {
3752 for (const Node *fnNode : propertyNode->getters()) {
3753 if (fnNode) {
3754 const auto funcNode = static_cast<const FunctionNode *>(fnNode);
3755 generateSynopsisInfo("getter", funcNode->name());
3756 }
3757 }
3758 for (const Node *fnNode : propertyNode->setters()) {
3759 if (fnNode) {
3760 const auto funcNode = static_cast<const FunctionNode *>(fnNode);
3761 generateSynopsisInfo("setter", funcNode->name());
3762 }
3763 }
3764 for (const Node *fnNode : propertyNode->resetters()) {
3765 if (fnNode) {
3766 const auto funcNode = static_cast<const FunctionNode *>(fnNode);
3767 generateSynopsisInfo("resetter", funcNode->name());
3768 }
3769 }
3770 for (const Node *fnNode : propertyNode->notifiers()) {
3771 if (fnNode) {
3772 const auto funcNode = static_cast<const FunctionNode *>(fnNode);
3773 generateSynopsisInfo("notifier", funcNode->name());
3774 }
3775 }
3776 }
3777
3778 m_writer->writeEndElement(); // nodeToSynopsisTag (like classsynopsis)
3779 newLine();
3780
3781 // The typedef associated to this enum. It is output *after* the main tag,
3782 // i.e. it must be after the synopsisinfo.
3783 if (enumNode && enumNode->flagsType()) {
3784 m_writer->writeStartElement(dbNamespace, "typedefsynopsis");
3785 newLine();
3786
3787 m_writer->writeTextElement(dbNamespace, "typedefname",
3788 enumNode->flagsType()->fullDocumentName());
3789 newLine();
3790
3791 m_writer->writeEndElement(); // typedefsynopsis
3792 newLine();
3793 }
3794}
3795
3797{
3798 // From CodeMarker::taggedNode, but without the tag part (i.e. only the QML specific case
3799 // remaining).
3800 // TODO: find a better name for this.
3801 if (node->nodeType() == NodeType::QmlType && node->name().startsWith(QLatin1String("QML:")))
3802 return node->name().mid(4);
3803 return node->name();
3804}
3805
3806/*!
3807 Parses a string with method/variable name and (return) type
3808 to include type tags.
3809 */
3810void DocBookGenerator::typified(const QString &string, const Node *relative, bool trailingSpace,
3811 bool generateType)
3812{
3813 // Adapted from CodeMarker::typified and HtmlGenerator::highlightedCode.
3814 QString result;
3815 QString pendingWord;
3816
3817 for (int i = 0; i <= string.size(); ++i) {
3818 QChar ch;
3819 if (i != string.size())
3820 ch = string.at(i);
3821
3822 QChar lower = ch.toLower();
3823 if ((lower >= QLatin1Char('a') && lower <= QLatin1Char('z')) || ch.digitValue() >= 0
3824 || ch == QLatin1Char('_') || ch == QLatin1Char(':')) {
3825 pendingWord += ch;
3826 } else {
3827 if (!pendingWord.isEmpty()) {
3828 bool isProbablyType = (pendingWord != QLatin1String("const"));
3829 if (generateType && isProbablyType) {
3830 // Flush the current buffer.
3831 m_writer->writeCharacters(result);
3832 result.truncate(0);
3833
3834 // Add the link, logic from HtmlGenerator::highlightedCode.
3835 const Node *n = m_qdb->findTypeNode(pendingWord, relative, Genus::DontCare);
3836 QString href;
3837 if (!(n && n->isQmlBasicType())
3838 || (relative
3839 && (relative->genus() == n->genus() || Genus::DontCare == n->genus()))) {
3840 href = linkForNode(n, relative);
3841 }
3842
3843 m_writer->writeStartElement(dbNamespace, "type");
3844 if (href.isEmpty())
3845 m_writer->writeCharacters(pendingWord);
3846 else
3847 generateSimpleLink(href, pendingWord);
3848 m_writer->writeEndElement(); // type
3849 } else {
3850 result += pendingWord;
3851 }
3852 }
3853 pendingWord.clear();
3854
3855 if (ch.unicode() != '\0')
3856 result += ch;
3857 }
3858 }
3859
3860 if (trailingSpace && string.size()) {
3861 if (!string.endsWith(QLatin1Char('*')) && !string.endsWith(QLatin1Char('&')))
3862 result += QLatin1Char(' ');
3863 }
3864
3865 m_writer->writeCharacters(result);
3866}
3867
3868/*!
3869 \internal
3870
3871 Emits the requires-clause \a text, wrapping each occurrence of a name
3872 in \a concepts in a \c{<db:link>} element pointing at the corresponding
3873 documented \\concept page. Concept identifiers consist of letters,
3874 digits, and underscores only, so word-boundary regex matches reliably
3875 isolate standalone concept tokens. The list is sorted longest first so
3876 prefix-sharing concept names (such as \c{Hashable} and
3877 \c{HashableContainer}) substitute correctly without overlap.
3878
3879 If a concept name doesn't resolve to a documented entity through
3880 \c{QDocDatabase::findConceptNode}, the bare text is written instead —
3881 matching the graceful-degradation behavior of the legacy HTML
3882 \c{<@concept>} resolver branch.
3883*/
3884void DocBookGenerator::generateRequiresClauseText(const QString &text,
3885 const QStringList &concepts,
3886 const Node *relative)
3887{
3888 if (concepts.isEmpty()) {
3889 m_writer->writeCharacters(text);
3890 return;
3891 }
3892
3893 QStringList sorted = concepts;
3894 std::sort(sorted.begin(), sorted.end(),
3895 [](const QString &a, const QString &b) { return a.size() > b.size(); });
3896
3897 // Match by the unqualified spelling that appears in the clause text, but
3898 // carry the fully-qualified name as the lookup target: concepts register
3899 // under their qualified name, so a namespaced reference such as
3900 // traits::Sortable resolves only through the qualified target.
3901 struct Match { qsizetype offset; qsizetype length; QString name; QString target; };
3902 QList<Match> matches;
3903 QList<bool> occupied(text.size(), false);
3904
3905 for (const QString &concept_name : sorted) {
3906 const QString unqualified = concept_name.section("::"_L1, -1);
3907 const QRegularExpression re("\\b"_L1
3908 + QRegularExpression::escape(unqualified) + "\\b"_L1);
3909 auto it = re.globalMatch(text);
3910 while (it.hasNext()) {
3911 const auto m = it.next();
3912 const qsizetype start = m.capturedStart();
3913 const qsizetype len = m.capturedLength();
3914 bool clear = true;
3915 for (qsizetype i = start; i < start + len; ++i) {
3916 if (occupied[i]) {
3917 clear = false;
3918 break;
3919 }
3920 }
3921 if (!clear)
3922 continue;
3923 for (qsizetype i = start; i < start + len; ++i)
3924 occupied[i] = true;
3925 matches.append({start, len, unqualified, concept_name});
3926 }
3927 }
3928
3929 std::sort(matches.begin(), matches.end(),
3930 [](const Match &a, const Match &b) { return a.offset < b.offset; });
3931
3932 qsizetype pos = 0;
3933 for (const auto &m : matches) {
3934 if (m.offset > pos)
3935 m_writer->writeCharacters(text.mid(pos, m.offset - pos));
3936 const Node *n = m_qdb->findConceptNode(m.target);
3937 if (n)
3938 generateSimpleLink(linkForNode(n, relative), m.name);
3939 else
3940 m_writer->writeCharacters(m.name);
3941 pos = m.offset + m.length;
3942 }
3943 if (pos < text.size())
3944 m_writer->writeCharacters(text.mid(pos));
3945}
3946
3947/*!
3948 \internal
3949
3950 Emits one template parameter into the current writer position. When the
3951 parameter carries a direct concept constraint (such as \c {Integral T}),
3952 the unqualified concept name is wrapped in a \c{<db:link>} pointing at
3953 the documented concept page; otherwise the plain \c{typename} keyword is
3954 written. The shape mirrors \c {CppCodeMarker::formatTemplateParameter},
3955 but writes DocBook XML directly rather than producing a marker string.
3956*/
3957void DocBookGenerator::generateTemplateParameter(const RelaxedTemplateParameter &param,
3958 const Node *relative)
3959{
3960 const auto &decl = param.valued_declaration;
3961
3962 if (param.template_declaration) {
3963 // Template-template parameter: emit the nested template declaration
3964 // recursively. The nested declaration is a TemplateDeclarationStorage,
3965 // not a RelaxedTemplateDeclaration, so it has no requires_clause or
3966 // referenced_concepts to consider.
3967 m_writer->writeCharacters("template <"_L1);
3968 bool first = true;
3969 for (const auto &sub : param.template_declaration->parameters) {
3970 if (sub.sfinae_constraint)
3971 continue;
3972 if (!first)
3973 m_writer->writeCharacters(", "_L1);
3974 generateTemplateParameter(sub, relative);
3975 first = false;
3976 }
3977 m_writer->writeCharacters("> "_L1);
3978 }
3979
3980 switch (param.kind) {
3982 if (param.concept_name) {
3983 const QString fq = QString::fromStdString(*param.concept_name);
3984 const QString unqualified = fq.section("::"_L1, -1);
3985 const Node *n = m_qdb->findConceptNode(fq);
3986 if (n)
3987 generateSimpleLink(linkForNode(n, relative), unqualified);
3988 else
3989 // No documented concept to link: keep the qualified spelling
3990 // the author wrote rather than silently dropping the namespace.
3991 m_writer->writeCharacters(fq);
3992 } else {
3993 m_writer->writeCharacters("typename"_L1);
3994 }
3995 break;
3996 case RelaxedTemplateParameter::Kind::NonTypeTemplateParameter:
3997 if (!decl.type.empty())
3998 typified(QString::fromStdString(decl.type), relative, false, true);
3999 break;
4000 case RelaxedTemplateParameter::Kind::TemplateTemplateParameter:
4001 m_writer->writeCharacters("typename"_L1);
4002 break;
4003 }
4004
4005 if (param.is_parameter_pack)
4006 m_writer->writeCharacters("..."_L1);
4007
4008 if (!decl.name.empty()) {
4009 m_writer->writeCharacters(" "_L1);
4010 m_writer->writeCharacters(QString::fromStdString(decl.name));
4011 }
4012
4013 if (!decl.initializer.empty()) {
4014 m_writer->writeCharacters(" = "_L1);
4017 typified(QString::fromStdString(decl.initializer), relative, false, true);
4018 } else {
4019 m_writer->writeCharacters(QString::fromStdString(decl.initializer));
4020 }
4021 }
4022}
4023
4024/*!
4025 \internal
4026
4027 Emits a full \c {template <...> requires ...} declaration element by
4028 element so concept references inside both the parameter list and the
4029 requires clause become \c{<db:link>} anchors. Multi-line layout kicks in
4030 when the visible parameter count exceeds
4031 \c{QDoc::MultilineTemplateParamThreshold}, mirroring
4032 \c {RelaxedTemplateDeclaration::to_qstring_multiline()}.
4033*/
4034void DocBookGenerator::generateTemplateDecl(const RelaxedTemplateDeclaration *templateDecl,
4035 const Node *relative)
4036{
4037 if (!templateDecl)
4038 return;
4039
4040 const bool multiline = templateDecl->visibleParameterCount()
4042
4043 m_writer->writeCharacters("template <"_L1);
4044 if (multiline)
4045 m_writer->writeCharacters("\n"_L1);
4046
4047 bool first = true;
4048 for (const auto &param : templateDecl->parameters) {
4049 if (param.sfinae_constraint)
4050 continue;
4051 if (!first)
4052 m_writer->writeCharacters(multiline ? ",\n"_L1 : ", "_L1);
4053 if (multiline)
4054 m_writer->writeCharacters(" "_L1);
4055 generateTemplateParameter(param, relative);
4056 first = false;
4057 }
4058
4059 if (multiline)
4060 m_writer->writeCharacters("\n"_L1);
4061 m_writer->writeCharacters(">"_L1);
4062
4063 if (templateDecl->requires_clause && !templateDecl->requires_clause->empty()) {
4064 m_writer->writeCharacters(" requires "_L1);
4065 QStringList concepts;
4066 concepts.reserve(int(templateDecl->referenced_concepts.size()));
4067 for (const auto &s : templateDecl->referenced_concepts)
4068 concepts.append(QString::fromStdString(s));
4069 generateRequiresClauseText(QString::fromStdString(*templateDecl->requires_clause),
4070 concepts, relative);
4071 }
4072}
4073
4074void DocBookGenerator::generateSynopsisName(const Node *node, const Node *relative,
4075 bool generateNameLink)
4076{
4077 // Implements the rewriting of <@link> from HtmlGenerator::highlightedCode, only due to calls to
4078 // CodeMarker::linkTag in CppCodeMarker::markedUpSynopsis.
4079 QString name = taggedNode(node);
4080
4081 if (!generateNameLink) {
4082 m_writer->writeCharacters(name);
4083 return;
4084 }
4085
4086 m_writer->writeStartElement(dbNamespace, "emphasis");
4087 m_writer->writeAttribute("role", "bold");
4088 generateSimpleLink(linkForNode(node, relative), name);
4089 m_writer->writeEndElement(); // emphasis
4090}
4091
4092void DocBookGenerator::generateParameter(const Parameter &parameter, const Node *relative,
4093 bool generateExtra, bool generateType)
4094{
4095 const QString &pname = parameter.name();
4096 const QString &ptype = parameter.type();
4097 QString paramName;
4098 qsizetype insertPos = !pname.isEmpty() ? parameter.nameInsertionPoint() : -1;
4099 if (!pname.isEmpty()) {
4100 if (insertPos >= 0) {
4101 // Inside-out declarator: name goes inside the type.
4102 typified(ptype.left(insertPos), relative, false, generateType);
4103 } else {
4104 typified(ptype, relative, true, generateType);
4105 }
4106 paramName = pname;
4107 } else {
4108 paramName = ptype;
4109 }
4110
4111 if (generateExtra || pname.isEmpty()) {
4112 m_writer->writeStartElement(dbNamespace, "emphasis");
4113 m_writer->writeCharacters(paramName);
4114 m_writer->writeEndElement(); // emphasis
4115 }
4116
4117 if (insertPos >= 0)
4118 typified(ptype.mid(insertPos), relative, false, generateType);
4119
4120 const QString &pvalue = parameter.defaultValue();
4121 if (generateExtra && !pvalue.isEmpty())
4122 m_writer->writeCharacters(" = " + pvalue);
4123}
4124
4125void DocBookGenerator::generateSynopsis(const Node *node, const Node *relative,
4126 Section::Style style)
4127{
4128 // From HtmlGenerator::generateSynopsis (conditions written as booleans).
4129 const bool generateExtra = style != Section::AllMembers;
4130 const bool generateType = style != Section::Details;
4131 const bool generateNameLink = style != Section::Details;
4132
4133 // From CppCodeMarker::markedUpSynopsis, reversed the generation of "extra" and "synopsis".
4134 const int MaxEnumValues = 6;
4135
4136 if (generateExtra) {
4137 if (auto extra = CodeMarker::extraSynopsis(node, style); !extra.isEmpty()) {
4138 generateExtraSynopsis(extra);
4139 m_writer->writeCharacters(u" "_s);
4140 }
4141 }
4142
4143 // Then generate the synopsis.
4144 QString namePrefix {};
4145 if (style == Section::Details) {
4146 if (!node->isRelatedNonmember() && !node->isProxyNode() && !node->parent()->name().isEmpty()
4147 && !node->parent()->isHeader() && !node->isProperty() && !node->isQmlNode()) {
4148 namePrefix = taggedNode(node->parent()) + "::";
4149 }
4150 }
4151
4152 switch (node->nodeType()) {
4153 case NodeType::Namespace:
4154 m_writer->writeCharacters("namespace ");
4155 m_writer->writeCharacters(namePrefix);
4156 generateSynopsisName(node, relative, generateNameLink);
4157 break;
4158 case NodeType::Class:
4159 m_writer->writeCharacters("class ");
4160 m_writer->writeCharacters(namePrefix);
4161 generateSynopsisName(node, relative, generateNameLink);
4162 break;
4163 case NodeType::Function: {
4164 const auto func = (const FunctionNode *)node;
4165
4166 if (style == Section::Details) {
4167 if (auto templateDecl = func->templateDecl()) {
4168 generateTemplateDecl(&*templateDecl, relative);
4169 if (templateDecl->visibleParameterCount() > QDoc::MultilineTemplateParamThreshold)
4170 m_writer->writeCharacters("\n"_L1);
4171 else
4172 m_writer->writeCharacters(" "_L1);
4173 }
4174 }
4175
4176 // First, the part coming before the name.
4177 if (style == Section::Summary || style == Section::Accessors) {
4178 if (!func->isNonvirtual())
4179 m_writer->writeCharacters(QStringLiteral("virtual "));
4180 }
4181
4182 // Name and parameters.
4183 if (style != Section::AllMembers && !func->returnType().isEmpty())
4184 typified(func->returnTypeString(), relative, true, generateType);
4185 m_writer->writeCharacters(namePrefix);
4186 generateSynopsisName(node, relative, generateNameLink);
4187
4188 if (!func->isMacroWithoutParams()) {
4189 m_writer->writeCharacters(QStringLiteral("("));
4190 if (!func->parameters().isEmpty()) {
4191 const Parameters &parameters = func->parameters();
4192 for (int i = 0; i < parameters.count(); i++) {
4193 if (i > 0)
4194 m_writer->writeCharacters(QStringLiteral(", "));
4195 generateParameter(parameters.at(i), relative, generateExtra, generateType);
4196 }
4197 }
4198 m_writer->writeCharacters(QStringLiteral(")"));
4199 }
4200
4201 if (func->isConst())
4202 m_writer->writeCharacters(QStringLiteral(" const"));
4203
4204 if (style == Section::Summary || style == Section::Accessors) {
4205 // virtual is prepended, if needed.
4206 QString synopsis;
4207 if (func->isFinal())
4208 synopsis += QStringLiteral(" final");
4209 if (func->isOverride())
4210 synopsis += QStringLiteral(" override");
4211 if (func->isPureVirtual())
4212 synopsis += QStringLiteral(" = 0");
4213 if (func->isRef())
4214 synopsis += QStringLiteral(" &");
4215 else if (func->isRefRef())
4216 synopsis += QStringLiteral(" &&");
4217 m_writer->writeCharacters(synopsis);
4218 } else if (style == Section::AllMembers) {
4219 if (!func->returnType().isEmpty() && func->returnType() != "void") {
4220 m_writer->writeCharacters(QStringLiteral(" : "));
4221 typified(func->returnTypeString(), relative, false, generateType);
4222 }
4223 } else {
4224 QString synopsis;
4225 if (func->isRef())
4226 synopsis += " &"_L1;
4227 else if (func->isRefRef())
4228 synopsis += " &&"_L1;
4229 m_writer->writeCharacters(synopsis);
4230
4231 if (const auto &req = func->trailingRequiresClause(); req && !req->isEmpty()) {
4232 m_writer->writeCharacters(" requires "_L1);
4233 generateRequiresClauseText(*req, func->referencedConcepts(), relative);
4234 }
4235 }
4236 } break;
4237 case NodeType::Enum: {
4238 const auto enume = static_cast<const EnumNode *>(node);
4239 if (!enume->isAnonymous()) {
4240 m_writer->writeCharacters("enum "_L1);
4241 m_writer->writeCharacters(namePrefix);
4242 generateSynopsisName(node, relative, generateNameLink);
4243 } else if (generateNameLink) {
4244 m_writer->writeStartElement(dbNamespace, "emphasis");
4245 m_writer->writeAttribute("role", "bold");
4246 generateSimpleLink(linkForNode(node, relative), "enum");
4247 m_writer->writeEndElement(); // emphasis
4248 } else {
4249 m_writer->writeCharacters("enum"_L1);
4250 }
4251
4252 QString synopsis;
4253 if (style == Section::Summary) {
4254 synopsis += " { ";
4255
4256 QStringList documentedItems = enume->doc().enumItemNames();
4257 if (documentedItems.isEmpty()) {
4258 const auto &enumItems = enume->items();
4259 for (const auto &item : enumItems)
4260 documentedItems << item.name();
4261 }
4262 const QStringList omitItems = enume->doc().omitEnumItemNames();
4263 for (const auto &item : omitItems)
4264 documentedItems.removeAll(item);
4265
4266 if (documentedItems.size() > MaxEnumValues) {
4267 // Take the last element and keep it safe, then elide the surplus.
4268 const QString last = documentedItems.last();
4269 documentedItems = documentedItems.mid(0, MaxEnumValues - 1);
4270 documentedItems += "&#x2026;"; // Ellipsis: in HTML, &hellip;.
4271 documentedItems += last;
4272 }
4273 synopsis += documentedItems.join(QLatin1String(", "));
4274
4275 if (!documentedItems.isEmpty())
4276 synopsis += QLatin1Char(' ');
4277 synopsis += QLatin1Char('}');
4278 }
4279 m_writer->writeCharacters(synopsis);
4280 } break;
4281 case NodeType::TypeAlias: {
4282 if (style == Section::Details) {
4283 if (auto templateDecl = node->templateDecl()) {
4284 generateTemplateDecl(&*templateDecl, relative);
4285 if (templateDecl->visibleParameterCount() > QDoc::MultilineTemplateParamThreshold)
4286 m_writer->writeCharacters("\n"_L1);
4287 else
4288 m_writer->writeCharacters(" "_L1);
4289 }
4290 }
4291 m_writer->writeCharacters(namePrefix);
4292 generateSynopsisName(node, relative, generateNameLink);
4293 } break;
4294 case NodeType::Typedef: {
4295 if (static_cast<const TypedefNode *>(node)->associatedEnum())
4296 m_writer->writeCharacters("flags ");
4297 m_writer->writeCharacters(namePrefix);
4298 generateSynopsisName(node, relative, generateNameLink);
4299 } break;
4300 case NodeType::Property: {
4301 const auto property = static_cast<const PropertyNode *>(node);
4302 m_writer->writeCharacters(namePrefix);
4303 generateSynopsisName(node, relative, generateNameLink);
4304 m_writer->writeCharacters(" : ");
4305 typified(property->qualifiedDataType(), relative, false, generateType);
4306 } break;
4307 case NodeType::Variable: {
4308 const auto variable = static_cast<const VariableNode *>(node);
4309 if (style == Section::AllMembers) {
4310 generateSynopsisName(node, relative, generateNameLink);
4311 m_writer->writeCharacters(" : ");
4312 typified(variable->dataType(), relative, false, generateType);
4313 } else {
4314 typified(variable->leftType(), relative, false, generateType);
4315 m_writer->writeCharacters(" ");
4316 m_writer->writeCharacters(namePrefix);
4317 generateSynopsisName(node, relative, generateNameLink);
4318 m_writer->writeCharacters(variable->rightType());
4319 }
4320 } break;
4321 default:
4322 m_writer->writeCharacters(namePrefix);
4323 generateSynopsisName(node, relative, generateNameLink);
4324 }
4325}
4326
4327void DocBookGenerator::generateEnumValue(const QString &enumValue, const Node *relative)
4328{
4329 // From CppCodeMarker::markedUpEnumValue, simplifications from Generator::plainCode (removing
4330 // <@op>). With respect to CppCodeMarker::markedUpEnumValue, the order of generation of parents
4331 // must be reversed so that they are processed in the order
4332 const auto *node = relative->parent();
4333
4334 const NativeEnum *nativeEnum{nullptr};
4335 if (auto *ne_if = dynamic_cast<const NativeEnumInterface *>(relative))
4336 nativeEnum = ne_if->nativeEnum();
4337
4338 if (nativeEnum && nativeEnum->enumNode() && !enumValue.startsWith("%1."_L1.arg(nativeEnum->prefix()))) {
4339 m_writer->writeCharacters("%1.%2"_L1.arg(nativeEnum->prefix(), enumValue));
4340 return;
4341 }
4342
4343 // Respect existing prefixes in \value arguments of \qmlenum topic
4344 if (!relative->isEnumType() || (relative->isEnumType(Genus::QML)
4345 && enumValue.section(' ', 0, 0).contains('.'_L1))) {
4346 m_writer->writeCharacters(enumValue);
4347 return;
4348 }
4349
4350 QList<const Node *> parents;
4351 while (!node->isHeader() && node->parent()) {
4352 parents.prepend(node);
4353 if (node->parent() == relative || node->parent()->name().isEmpty())
4354 break;
4355 node = node->parent();
4356 }
4357 if (static_cast<const EnumNode *>(relative)->isScoped())
4358 parents << relative;
4359
4360 m_writer->writeStartElement(dbNamespace, "code");
4361 for (auto parent : parents) {
4362 generateSynopsisName(parent, relative, true);
4363 m_writer->writeCharacters((relative->genus() == Genus::QML) ? "."_L1 : "::"_L1);
4364 }
4365
4366 m_writer->writeCharacters(enumValue);
4367 m_writer->writeEndElement(); // code
4368}
4369
4370/*!
4371 Generates an addendum note of type \a type for \a node. \a marker
4372 is unused in this generator.
4373*/
4375 AdmonitionPrefix prefix)
4376{
4377 Q_UNUSED(marker)
4378 Q_ASSERT(node && !node->name().isEmpty());
4379
4380 switch (prefix) {
4382 break;
4384 m_writer->writeStartElement(dbNamespace, "note");
4385 newLine();
4386 break;
4387 }
4388 }
4389 switch (type) {
4390 case Invokable:
4391 m_writer->writeStartElement(dbNamespace, "para");
4392 m_writer->writeCharacters(
4393 "This function can be invoked via the meta-object system and from QML. See ");
4394 generateSimpleLink(node->url(), "Q_INVOKABLE");
4395 m_writer->writeCharacters(".");
4396 m_writer->writeEndElement(); // para
4397 newLine();
4398 break;
4399 case PrivateSignal:
4400 m_writer->writeTextElement(
4401 dbNamespace, "para",
4402 "This is a private signal. It can be used in signal connections but "
4403 "cannot be emitted by the user.");
4404 break;
4405 case QmlSignalHandler:
4406 {
4407 QString handler(node->name());
4408 int prefixLocation = handler.lastIndexOf('.', -2) + 1;
4409 handler[prefixLocation] = handler[prefixLocation].toTitleCase();
4410 handler.insert(prefixLocation, QLatin1String("on"));
4411 m_writer->writeStartElement(dbNamespace, "para");
4412 m_writer->writeCharacters("The corresponding handler is ");
4413 m_writer->writeTextElement(dbNamespace, "code", handler);
4414 m_writer->writeCharacters(".");
4415 m_writer->writeEndElement(); // para
4416 newLine();
4417 break;
4418 }
4420 {
4421 if (!node->isFunction())
4422 return;
4423 const auto *fn = static_cast<const FunctionNode *>(node);
4424 auto nodes = fn->associatedProperties();
4425 if (nodes.isEmpty())
4426 return;
4427 std::sort(nodes.begin(), nodes.end(), Node::nodeNameLessThan);
4428
4429 // Group properties by their role for more concise output
4430 QMap<PropertyNode::FunctionRole, QList<const PropertyNode *>> roleGroups;
4431 for (const auto *n : std::as_const(nodes)) {
4432 const auto *pn = static_cast<const PropertyNode *>(n);
4433 PropertyNode::FunctionRole role = pn->role(fn);
4434 roleGroups[role].append(pn);
4435 }
4436
4437 // Generate text for each role group in an explicit order
4438 static constexpr PropertyNode::FunctionRole roleOrder[] = {
4444 };
4445
4446 for (auto role : roleOrder) {
4447 const auto it = roleGroups.constFind(role);
4448 if (it == roleGroups.cend())
4449 continue;
4450
4451 const auto &properties = it.value();
4452
4453 QString msg;
4454 switch (role) {
4456 msg = QStringLiteral("Getter function");
4457 break;
4459 msg = QStringLiteral("Setter function");
4460 break;
4462 msg = QStringLiteral("Resetter function");
4463 break;
4465 msg = QStringLiteral("Notifier signal");
4466 break;
4468 msg = QStringLiteral("Bindable function");
4469 break;
4470 default:
4471 continue;
4472 }
4473
4474 m_writer->writeStartElement(dbNamespace, "para");
4475 if (properties.size() == 1) {
4476 const auto *pn = properties.first();
4477 m_writer->writeCharacters(msg + " for property ");
4478 generateSimpleLink(linkForNode(pn, nullptr), pn->name());
4479 m_writer->writeCharacters(". ");
4480 } else {
4481 m_writer->writeCharacters(msg + " for properties ");
4482 for (qsizetype i = 0; i < properties.size(); ++i) {
4483 const auto *pn = properties.at(i);
4484 generateSimpleLink(linkForNode(pn, nullptr), pn->name());
4485 m_writer->writeCharacters(TextUtils::separator(i, properties.size()));
4486 }
4487 m_writer->writeCharacters(" ");
4488 }
4489 m_writer->writeEndElement(); // para
4490 newLine();
4491 }
4492 break;
4493 }
4494 case BindableProperty:
4495 {
4496 const Node *linkNode;
4497 Atom linkAtom = Atom(Atom::Link, "QProperty");
4498 QString link = getAutoLink(&linkAtom, node, &linkNode);
4499 m_writer->writeStartElement(dbNamespace, "para");
4500 m_writer->writeCharacters("This property supports ");
4501 generateSimpleLink(link, "QProperty");
4502 m_writer->writeCharacters(" bindings.");
4503 m_writer->writeEndElement(); // para
4504 newLine();
4505 break;
4506 }
4507 case OverloadNote: {
4508 const auto *func = static_cast<const FunctionNode *>(node);
4509
4510 // Primary overloads should not display any overload note text
4511 if (func->isPrimaryOverload())
4512 return;
4513
4514 m_writer->writeStartElement(dbNamespace, "para");
4515
4516 if (func->isSignal() || func->isSlot()) {
4517 auto writeDocBookLink = [&](const QString &target, const QString &label) {
4518 const Node *linkNode = nullptr;
4519 const Atom &linkAtom = Atom(Atom::AutoLink, target);
4520 const QString &link = getAutoLink(&linkAtom, node, &linkNode);
4521
4522 m_writer->writeStartElement(dbNamespace, "link");
4523 if (!link.isEmpty() && linkNode) {
4524 m_writer->writeAttribute(xlinkNamespace, "href", link);
4525 } else {
4526 m_writer->writeAttribute(dbNamespace, "linkend", target);
4527 }
4528 m_writer->writeCharacters(label);
4529 m_writer->writeEndElement(); // link
4530 };
4531
4532 const QString &functionType = func->isSignal() ? "signal" : "slot";
4533 const QString &configKey = func->isSignal() ? "overloadedsignalstarget" : "overloadedslotstarget";
4534 const QString &defaultTarget = func->isSignal() ? "connecting-overloaded-signals" : "connecting-overloaded-slots";
4535 const QString &linkTarget = Config::instance().get(configKey).asString(defaultTarget);
4536
4537 m_writer->writeCharacters("This " + functionType + " is overloaded. ");
4538
4539 QString snippet = generateOverloadSnippet(func);
4540 if (!snippet.isEmpty()) {
4541 m_writer->writeCharacters("To connect to this " + functionType + ":");
4542 m_writer->writeEndElement(); // para
4543 newLine();
4544 m_writer->writeStartElement(dbNamespace, "programlisting");
4545 m_writer->writeCharacters(snippet);
4546 m_writer->writeEndElement(); // programlisting
4547 newLine();
4548 // A new para element is needed here because the caller closes it.
4549 // When linkTarget is empty, this creates an empty para, which is
4550 // harmless but slightly verbose.
4551 m_writer->writeStartElement(dbNamespace, "para");
4552 if (!linkTarget.isEmpty()) {
4553 m_writer->writeCharacters("For more examples and approaches, see ");
4554 writeDocBookLink(linkTarget, "connecting to overloaded " + functionType + "s");
4555 m_writer->writeCharacters(".");
4556 }
4557 } else if (!linkTarget.isEmpty()) {
4558 m_writer->writeCharacters("For more examples and approaches, see ");
4559 writeDocBookLink(linkTarget, "connecting to overloaded " + functionType + "s");
4560 m_writer->writeCharacters(".");
4561 }
4562 } else {
4563 // Original behavior for regular overloaded functions
4564 const auto &args = node->doc().overloadList();
4565 if (args.first().first.isEmpty()) {
4566 m_writer->writeCharacters("This is an overloaded function.");
4567 } else {
4568 QString target = args.first().first;
4569 // If the target is not fully qualified and we have a parent class context,
4570 // attempt to qualify it to improve link resolution
4571 if (!target.contains("::")) {
4572 const auto *parent = node->parent();
4573 if (parent && (parent->isClassNode() || parent->isNamespace())) {
4574 target = parent->name() + "::" + target;
4575 }
4576 }
4577 m_writer->writeCharacters("This function overloads ");
4578 // Use the same approach as AutoLink resolution in other generators
4579 const Node *linkNode = nullptr;
4580 Atom linkAtom = Atom(Atom::AutoLink, target);
4581 QString link = getAutoLink(&linkAtom, node, &linkNode);
4582 if (!link.isEmpty() && linkNode)
4583 generateSimpleLink(link, target);
4584 else
4585 m_writer->writeCharacters(target);
4586 m_writer->writeCharacters(".");
4587 }
4588 }
4589
4590 m_writer->writeEndElement(); // para
4591 newLine();
4592 break;
4593 }
4594 default:
4595 break;
4596 }
4597
4598 if (prefix == AdmonitionPrefix::Note) {
4599 m_writer->writeEndElement(); // note
4600 newLine();
4601 }
4602}
4603
4604void DocBookGenerator::generateDetailedMember(const Node *node, const PageNode *relative)
4605{
4606 // From HtmlGenerator::generateDetailedMember.
4607 bool closeSupplementarySection = false;
4608
4609 if (node->isSharedCommentNode()) {
4610 const auto *scn = reinterpret_cast<const SharedCommentNode *>(node);
4611 const QList<Node *> &collective = scn->collective();
4612
4613 bool firstFunction = true;
4614 for (const auto *sharedNode : collective) {
4615 if (firstFunction) {
4616 startSectionBegin(sharedNode);
4617 } else {
4618 m_writer->writeStartElement(dbNamespace, "bridgehead");
4619 m_writer->writeAttribute("renderas", "sect2");
4620 writeXmlId(sharedNode);
4621 }
4622 if (m_useITS)
4623 m_writer->writeAttribute(itsNamespace, "translate", "no");
4624
4625 generateSynopsis(sharedNode, relative, Section::Details);
4626
4627 if (firstFunction) {
4628 startSectionEnd();
4629 firstFunction = false;
4630 } else {
4631 m_writer->writeEndElement(); // bridgehead
4632 newLine();
4633 }
4634 }
4635 } else {
4636 const EnumNode *etn;
4637 if (node->isEnumType(Genus::CPP) && (etn = static_cast<const EnumNode *>(node))->flagsType()) {
4638 startSectionBegin(node);
4639 if (m_useITS)
4640 m_writer->writeAttribute(itsNamespace, "translate", "no");
4641 generateSynopsis(etn, relative, Section::Details);
4642 startSectionEnd();
4643
4644 m_writer->writeStartElement(dbNamespace, "bridgehead");
4645 m_writer->writeAttribute("renderas", "sect2");
4646 generateSynopsis(etn->flagsType(), relative, Section::Details);
4647 m_writer->writeEndElement(); // bridgehead
4648 newLine();
4649 } else {
4650 startSectionBegin(node);
4651 if (m_useITS)
4652 m_writer->writeAttribute(itsNamespace, "translate", "no");
4653 generateSynopsis(node, relative, Section::Details);
4654 startSectionEnd();
4655 }
4656 }
4657 Q_ASSERT(m_hasSection);
4658
4660
4661 generateStatus(node);
4662 generateBody(node);
4663
4664 // If the body ends with a section, the rest of the description must be wrapped in a section too.
4666 closeSupplementarySection = true;
4667 startSection("", "Notes");
4668 }
4669
4670 if (node->isFunction()) {
4671 const auto *func = static_cast<const FunctionNode *>(node);
4672 if (func->hasOverloads() && (func->isSignal() || func->isSlot()))
4674 }
4677 generateSince(node);
4678
4679 if (node->isProperty()) {
4680 const auto property = static_cast<const PropertyNode *>(node);
4681 if (property->propertyType() == PropertyNode::PropertyType::StandardProperty) {
4682 Section section("", "", "", "", Section::Accessors);
4683
4684 section.appendMembers(property->getters().toVector());
4685 section.appendMembers(property->setters().toVector());
4686 section.appendMembers(property->resetters().toVector());
4687
4688 if (!section.members().isEmpty()) {
4689 m_writer->writeStartElement(dbNamespace, "para");
4690 newLine();
4691 m_writer->writeStartElement(dbNamespace, "emphasis");
4692 m_writer->writeAttribute("role", "bold");
4693 m_writer->writeCharacters("Access functions:");
4694 newLine();
4695 m_writer->writeEndElement(); // emphasis
4696 newLine();
4697 m_writer->writeEndElement(); // para
4698 newLine();
4699 generateSectionList(section, node);
4700 }
4701
4702 Section notifiers("", "", "", "", Section::Accessors);
4703 notifiers.appendMembers(property->notifiers().toVector());
4704
4705 if (!notifiers.members().isEmpty()) {
4706 m_writer->writeStartElement(dbNamespace, "para");
4707 newLine();
4708 m_writer->writeStartElement(dbNamespace, "emphasis");
4709 m_writer->writeAttribute("role", "bold");
4710 m_writer->writeCharacters("Notifier signal:");
4711 newLine();
4712 m_writer->writeEndElement(); // emphasis
4713 newLine();
4714 m_writer->writeEndElement(); // para
4715 newLine();
4716 generateSectionList(notifiers, node);
4717 }
4718 }
4719 } else if (node->isEnumType(Genus::CPP)) {
4720 const auto en = static_cast<const EnumNode *>(node);
4721
4722 if (m_qflagsHref.isEmpty()) {
4723 Node *qflags = m_qdb->findClassNode(QStringList("QFlags"));
4724 if (qflags)
4725 m_qflagsHref = linkForNode(qflags, nullptr);
4726 }
4727
4728 if (en->flagsType()) {
4729 m_writer->writeStartElement(dbNamespace, "para");
4730 m_writer->writeCharacters("The ");
4731 m_writer->writeStartElement(dbNamespace, "code");
4732 m_writer->writeCharacters(en->flagsType()->name());
4733 m_writer->writeEndElement(); // code
4734 m_writer->writeCharacters(" type is a typedef for ");
4735 m_writer->writeStartElement(dbNamespace, "code");
4736 generateSimpleLink(m_qflagsHref, "QFlags");
4737 m_writer->writeCharacters("<" + en->name() + ">. ");
4738 m_writer->writeEndElement(); // code
4739 m_writer->writeCharacters("It stores an OR combination of ");
4740 m_writer->writeStartElement(dbNamespace, "code");
4741 m_writer->writeCharacters(en->name());
4742 m_writer->writeEndElement(); // code
4743 m_writer->writeCharacters(" values.");
4744 m_writer->writeEndElement(); // para
4745 newLine();
4746 }
4747 }
4748
4749 if (closeSupplementarySection)
4750 endSection();
4751
4752 // The list of linked pages is always in its own section.
4754
4755 // Close the section for this member.
4756 endSection(); // section
4757}
4758
4759void DocBookGenerator::generateSectionList(const Section &section, const Node *relative,
4760 bool useObsoleteMembers)
4761{
4762 // From HtmlGenerator::generateSectionList, just generating a list (not tables).
4763 const NodeVector &members =
4764 (useObsoleteMembers ? section.obsoleteMembers() : section.members());
4765 if (!members.isEmpty()) {
4766 bool hasPrivateSignals = false;
4767 bool isInvokable = false;
4768
4769 m_writer->writeStartElement(dbNamespace, "itemizedlist");
4770 if (m_useITS)
4771 m_writer->writeAttribute(itsNamespace, "translate", "no");
4772 newLine();
4773
4774 NodeVector::ConstIterator m = members.constBegin();
4775 while (m != members.constEnd()) {
4776 if ((*m)->access() == Access::Private) {
4777 ++m;
4778 continue;
4779 }
4780
4781 m_writer->writeStartElement(dbNamespace, "listitem");
4782 newLine();
4783 m_writer->writeStartElement(dbNamespace, "para");
4784
4785 // prefix no more needed.
4786 generateSynopsis(*m, relative, section.style());
4787 if ((*m)->isFunction()) {
4788 const auto fn = static_cast<const FunctionNode *>(*m);
4789 if (fn->isPrivateSignal())
4790 hasPrivateSignals = true;
4791 else if (fn->isInvokable())
4792 isInvokable = true;
4793 }
4794
4795 m_writer->writeEndElement(); // para
4796 newLine();
4797 m_writer->writeEndElement(); // listitem
4798 newLine();
4799
4800 ++m;
4801 }
4802
4803 m_writer->writeEndElement(); // itemizedlist
4804 newLine();
4805
4806 if (hasPrivateSignals)
4808 if (isInvokable)
4810 }
4811
4812 if (!useObsoleteMembers && section.style() == Section::Summary
4813 && !section.inheritedMembers().isEmpty()) {
4814 m_writer->writeStartElement(dbNamespace, "itemizedlist");
4815 if (m_useITS)
4816 m_writer->writeAttribute(itsNamespace, "translate", "no");
4817 newLine();
4818
4819 generateSectionInheritedList(section, relative);
4820
4821 m_writer->writeEndElement(); // itemizedlist
4822 newLine();
4823 }
4824}
4825
4826void DocBookGenerator::generateSectionInheritedList(const Section &section, const Node *relative)
4827{
4828 // From HtmlGenerator::generateSectionInheritedList.
4829 QList<std::pair<const Aggregate *, int>>::ConstIterator p = section.inheritedMembers().constBegin();
4830 while (p != section.inheritedMembers().constEnd()) {
4831 m_writer->writeStartElement(dbNamespace, "listitem");
4832 m_writer->writeCharacters(QString::number((*p).second) + u' ');
4833 if ((*p).second == 1)
4834 m_writer->writeCharacters(section.singular());
4835 else
4836 m_writer->writeCharacters(section.plural());
4837 m_writer->writeCharacters(" inherited from ");
4838 generateSimpleLink(fileName((*p).first) + '#'
4839 + Generator::cleanRef(section.title().toLower()),
4840 (*p).first->plainFullName(relative));
4841 ++p;
4842 }
4843}
4844
4845/*!
4846 Generate the DocBook page for an entity that doesn't map
4847 to any underlying parsable C++ or QML element.
4848 */
4850{
4851 // From HtmlGenerator::generatePageNode, remove anything related to TOCs.
4852 Q_ASSERT(m_writer == nullptr);
4853 m_writer = startDocument(pn);
4854
4855 generateHeader(pn->doc().title(), pn->subtitle(), pn);
4856 generateBody(pn);
4857 generateAlsoList(pn);
4859
4860 endDocument();
4861}
4862
4863/*!
4864 Generate the DocBook page for a QML type. \qcn is the QML type.
4865 */
4867{
4868 // From HtmlGenerator::generateQmlTypePage.
4869 // Start producing the DocBook file.
4870 Q_ASSERT(m_writer == nullptr);
4871 m_writer = startDocument(qcn);
4872
4874 QString title = qcn->name();
4875 if (qcn->isQmlBasicType())
4876 title.append(" QML Value Type");
4877 else
4878 title.append(" QML Type");
4879
4880 if (qcn->isSingleton())
4881 title.append(" (Singleton)");
4882 else if (qcn->isUncreatable())
4883 title.append(" (Uncreatable)");
4884 // TODO: for ITS attribute, only apply translate="no" on qcn->fullTitle(),
4885 // not its suffix (which should be translated). generateHeader doesn't
4886 // allow this kind of input, the title isn't supposed to be structured.
4887 // Ideally, do the same in HTML.
4888
4889 generateHeader(title, qcn->subtitle(), qcn);
4891 generateStatus(qcn);
4892
4893 if (qcn->isSingleton() || qcn->isUncreatable()) {
4894 m_writer->writeStartElement(dbNamespace, "note");
4895 m_writer->writeStartElement(dbNamespace, "para");
4896 m_writer->writeStartElement(dbNamespace, "emphasis");
4897 m_writer->writeAttribute("role", "bold");
4898 m_writer->writeCharacters("Note: ");
4899 m_writer->writeEndElement(); // emphasis
4900 if (qcn->isSingleton())
4901 m_writer->writeCharacters("This type is a QML singleton. "
4902 "There is only one instance of this type in the QML engine.");
4903 else
4904 m_writer->writeCharacters("This is an uncreatable type. "
4905 "It cannot be instantiated in QML.");
4906 m_writer->writeEndElement(); // para
4907 m_writer->writeEndElement(); // note
4908 }
4909
4910 startSection("details", "Detailed Description");
4911 generateBody(qcn);
4912
4913 generateAlsoList(qcn);
4914
4915 endSection();
4916
4917 Sections sections(qcn);
4918 for (const auto &section : sections.detailsSections()) {
4919 if (!section.isEmpty()) {
4920 startSection(section.title().toLower(), section.title());
4921
4922 for (const auto &member : section.members())
4923 generateDetailedQmlMember(member, qcn);
4924
4925 endSection();
4926 }
4927 }
4928
4929 generateObsoleteQmlMembers(sections);
4930
4933
4934 endDocument();
4935}
4936
4937/*!
4938 Outputs the DocBook detailed documentation for a section
4939 on a QML element reference page.
4940 */
4941void DocBookGenerator::generateDetailedQmlMember(Node *node, const Aggregate *relative)
4942{
4943 // From HtmlGenerator::generateDetailedQmlMember, with elements from
4944 // CppCodeMarker::markedUpQmlItem and HtmlGenerator::generateQmlItem.
4945 auto getQmlPropertyTitle = [&](QmlPropertyNode *n) {
4946 QString title{CodeMarker::extraSynopsis(n, Section::Details)};
4947 if (!title.isEmpty())
4948 title += ' '_L1;
4949 // Finalise generation of name, as per CppCodeMarker::markedUpQmlItem.
4950 if (n->isAttached())
4951 title += n->element() + QLatin1Char('.');
4952 title += n->name() + " : " + n->dataType();
4953
4954 return title;
4955 };
4956
4957 auto generateQmlMethodTitle = [&](Node *node) {
4958 generateSynopsis(node, relative, Section::Details);
4959 };
4960
4961 if (node->isPropertyGroup()) {
4962 const auto *scn = static_cast<const SharedCommentNode *>(node);
4963
4964 QString heading;
4965 if (!scn->name().isEmpty())
4966 heading = scn->name() + " group";
4967 else
4968 heading = node->name();
4969 startSection(scn, heading);
4970 // This last call creates a title for this section. In other words,
4971 // titles are forbidden for the rest of the section, hence the use of
4972 // bridgehead.
4973
4974 const QList<Node *> sharedNodes = scn->collective();
4975 for (const auto &sharedNode : sharedNodes) {
4976 if (sharedNode->isQmlProperty()) {
4977 auto *qpn = static_cast<QmlPropertyNode *>(sharedNode);
4978
4979 m_writer->writeStartElement(dbNamespace, "bridgehead");
4980 m_writer->writeAttribute("renderas", "sect2");
4981 writeXmlId(qpn);
4982 m_writer->writeCharacters(getQmlPropertyTitle(qpn));
4983 m_writer->writeEndElement(); // bridgehead
4984 newLine();
4985
4986 generateDocBookSynopsis(qpn);
4987 }
4988 }
4989 } else if (node->isQmlProperty()) {
4990 auto qpn = static_cast<QmlPropertyNode *>(node);
4991 startSection(qpn, getQmlPropertyTitle(qpn));
4993 } else if (node->isSharedCommentNode()) {
4994 const auto scn = reinterpret_cast<const SharedCommentNode *>(node);
4995 const QList<Node *> &sharedNodes = scn->collective();
4996
4997 // In the section, generate a title for the first node, then bridgeheads for
4998 // the next ones.
4999 int i = 0;
5000 for (const auto &sharedNode : sharedNodes) {
5001 // Ignore this element if there is nothing to generate.
5002 if (!sharedNode->isFunction(Genus::QML) && !sharedNode->isQmlProperty()) {
5003 continue;
5004 }
5005
5006 // Write the tag containing the title.
5007 if (i == 0) {
5008 startSectionBegin(sharedNode);
5009 } else {
5010 m_writer->writeStartElement(dbNamespace, "bridgehead");
5011 m_writer->writeAttribute("renderas", "sect2");
5012 }
5013
5014 // Write the title.
5015 if (sharedNode->isFunction(Genus::QML))
5016 generateQmlMethodTitle(sharedNode);
5017 else if (sharedNode->isQmlProperty())
5018 m_writer->writeCharacters(
5019 getQmlPropertyTitle(static_cast<QmlPropertyNode *>(sharedNode)));
5020
5021 // Complete the title and the synopsis.
5022 if (i == 0)
5023 startSectionEnd();
5024 else
5025 m_writer->writeEndElement(); // bridgehead
5026 generateDocBookSynopsis(sharedNode);
5027 ++i;
5028 }
5029
5030 // If the list is empty, still generate a section.
5031 if (i == 0) {
5032 startSectionBegin(refForNode(node));
5033
5034 if (node->isFunction(Genus::QML))
5035 generateQmlMethodTitle(node);
5036 else if (node->isQmlProperty())
5037 m_writer->writeCharacters(
5038 getQmlPropertyTitle(static_cast<QmlPropertyNode *>(node)));
5039
5040 startSectionEnd();
5041 }
5042 } else if (node->isEnumType(Genus::QML)) {
5043 startSectionBegin(node);
5045 startSectionEnd();
5046 } else { // assume the node is a method/signal handler
5047 startSectionBegin(node);
5048 generateQmlMethodTitle(node);
5049 startSectionEnd();
5050 }
5051
5052 generateStatus(node);
5053 generateBody(node);
5055 generateSince(node);
5057
5058 endSection();
5059}
5060
5061/*!
5062 Recursive writing of DocBook files from the root \a node.
5063 */
5065{
5066 // Mainly from Generator::generateDocumentation, with parts from
5067 // Generator::generateDocumentation and WebXMLGenerator::generateDocumentation.
5068 // Don't generate nodes that are already processed, or if they're not
5069 // supposed to generate output, ie. external, index or images nodes.
5070 if (!node->url().isNull())
5071 return;
5072 if (node->isIndexNode())
5073 return;
5074 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
5075 const NodeContext context = node->createContext();
5076 if (!InclusionFilter::isIncluded(policy, context))
5077 return;
5078 if (node->isExternalPage())
5079 return;
5080
5081 if (node->parent()) {
5082 if (node->isCollectionNode()) {
5083 /*
5084 A collection node collects: groups, C++ modules, or QML
5085 modules. Testing for a CollectionNode must be done
5086 before testing for a TextPageNode because a
5087 CollectionNode is a PageNode at this point.
5088
5089 Don't output an HTML page for the collection node unless
5090 the \group, \module, or \qmlmodule command was actually
5091 seen by qdoc in the qdoc comment for the node.
5092
5093 A key prerequisite in this case is the call to
5094 mergeCollections(cn). We must determine whether this
5095 group, module, or QML module has members in other
5096 modules. We know at this point that cn's members list
5097 contains only members in the current module. Therefore,
5098 before outputting the page for cn, we must search for
5099 members of cn in the other modules and add them to the
5100 members list.
5101 */
5102 auto cn = static_cast<CollectionNode *>(node);
5103 if (cn->wasSeen()) {
5104 m_qdb->mergeCollections(cn);
5105 generateCollectionNode(cn);
5106 } else if (cn->isGenericCollection()) {
5107 // Currently used only for the module's related orphans page
5108 // but can be generalized for other kinds of collections if
5109 // other use cases pop up.
5110 generateGenericCollectionPage(cn);
5111 }
5112 } else if (node->isTextPageNode()) { // Pages.
5113 generatePageNode(static_cast<PageNode *>(node));
5114 } else if (node->isAggregate()) { // Aggregates.
5115 if ((node->isClassNode() || node->isHeader() || node->isNamespace())
5116 && node->docMustBeGenerated()) {
5117 generateCppReferencePage(static_cast<Aggregate *>(node));
5118 } else if (node->isQmlType()) { // Includes QML value types
5119 generateQmlTypePage(static_cast<QmlTypeNode *>(node));
5120 } else if (node->isProxyNode()) {
5121 generateProxyPage(static_cast<Aggregate *>(node));
5122 }
5123 }
5124 }
5125
5126 if (node->isAggregate()) {
5127 auto *aggregate = static_cast<Aggregate *>(node);
5128 for (auto c : aggregate->childNodes()) {
5129 if (node->isPageNode())
5130 generateDocumentation(c);
5131 }
5132 }
5133}
5134
5136{
5137 // Adapted from HtmlGenerator::generateProxyPage.
5138 Q_ASSERT(aggregate->isProxyNode());
5139
5140 // Start producing the DocBook file.
5141 Q_ASSERT(m_writer == nullptr);
5142 m_writer = startDocument(aggregate);
5143
5144 // Info container.
5145 generateHeader(aggregate->plainFullName(), "", aggregate);
5146
5147 // No element synopsis.
5148
5149 // Actual content.
5150 if (!aggregate->doc().isEmpty()) {
5151 startSection("details", "Detailed Description");
5152
5153 generateBody(aggregate);
5154 generateAlsoList(aggregate);
5155
5156 endSection();
5157 }
5158
5159 Sections sections(aggregate);
5160 const SectionVector &detailsSections = sections.detailsSections();
5161
5162 for (const auto &section : detailsSections) {
5163 if (section.isEmpty())
5164 continue;
5165
5166 startSection(section.title().toLower(), section.title());
5167
5168 const QList<Node *> &members = section.members();
5169 for (const auto &member : members) {
5170 if (!member->isClassNode()) {
5171 generateDetailedMember(member, aggregate);
5172 } else {
5173 startSectionBegin();
5174 generateFullName(member, aggregate);
5175 startSectionEnd();
5176
5177 generateBrief(member);
5178 endSection();
5179 }
5180 }
5181
5182 endSection();
5183 }
5184
5186
5187 endDocument();
5188}
5189
5190/*!
5191 Generate the HTML page for a group, module, or QML module.
5192 */
5194{
5195 // Adapted from HtmlGenerator::generateCollectionNode.
5196 // Start producing the DocBook file.
5197 Q_ASSERT(m_writer == nullptr);
5198 m_writer = startDocument(cn);
5199
5200 // Info container.
5201 generateHeader(cn->doc().title(), cn->subtitle(), cn);
5202
5203 // Element synopsis.
5205
5206 // Generate brief for C++ modules, status for all modules.
5207 if (cn->genus() != Genus::DOC && cn->genus() != Genus::DontCare) {
5208 if (cn->isModule())
5209 generateBrief(cn);
5210 generateStatus(cn);
5211 generateSince(cn);
5212 }
5213
5214 // Actual content.
5215 if (cn->isModule()) {
5216 if (!cn->noAutoList()) {
5218 if (!nmm.isEmpty()) {
5219 startSection("namespaces", "Namespaces");
5220 generateAnnotatedList(cn, nmm.values(), "namespaces");
5221 endSection();
5222 }
5223 nmm = cn->getMembers([](const Node *n){ return n->isClassNode(); });
5224 if (!nmm.isEmpty()) {
5225 startSection("classes", "Classes");
5226 generateAnnotatedList(cn, nmm.values(), "classes");
5227 endSection();
5228 }
5229 }
5230 }
5231
5232 bool generatedTitle = false;
5233 if (cn->isModule() && !cn->doc().briefText().isEmpty()) {
5234 startSection("details", "Detailed Description");
5235 generatedTitle = true;
5236 }
5237 // The anchor is only needed if the node has a body.
5238 else if (
5239 // generateBody generates something.
5240 !cn->doc().body().isEmpty() ||
5241 // generateAlsoList generates something.
5242 !cn->doc().alsoList().empty() ||
5243 // generateAnnotatedList generates something.
5244 (!cn->noAutoList() && (cn->isGroup() || cn->isQmlModule()))) {
5245 writeAnchor("details");
5246 }
5247
5248 generateBody(cn);
5249 generateAlsoList(cn);
5250
5251 if (!cn->noAutoList() && (cn->isGroup() || cn->isQmlModule()))
5252 generateAnnotatedList(cn, cn->members(), "members", AutoSection);
5253
5254 if (generatedTitle)
5255 endSection();
5256
5258
5259 endDocument();
5260}
5261
5262/*!
5263 Generate the HTML page for a generic collection. This is usually
5264 a collection of C++ elements that are related to an element in
5265 a different module.
5266 */
5268{
5269 // Adapted from HtmlGenerator::generateGenericCollectionPage.
5270 // TODO: factor out this code to generate a file name.
5271 QString name = cn->name().toLower();
5272 name.replace(QChar(' '), QString("-"));
5273 QString filename = cn->tree()->physicalModuleName() + "-" + name + "." + fileExtension();
5274
5275 // Start producing the DocBook file.
5276 Q_ASSERT(m_writer == nullptr);
5277 m_writer = startGenericDocument(cn, filename);
5278
5279 // Info container.
5280 generateHeader(cn->fullTitle(), cn->subtitle(), cn);
5281
5282 // Element synopsis.
5284
5285 // Actual content.
5286 m_writer->writeStartElement(dbNamespace, "para");
5287 m_writer->writeCharacters("Each function or type documented here is related to a class or "
5288 "namespace that is documented in a different module. The reference "
5289 "page for that class or namespace will link to the function or type "
5290 "on this page.");
5291 m_writer->writeEndElement(); // para
5292
5293 const CollectionNode *cnc = cn;
5294 const QList<Node *> members = cn->members();
5295 for (const auto &member : members)
5296 generateDetailedMember(member, cnc);
5297
5299
5300 endDocument();
5301}
5302
5303void DocBookGenerator::generateFullName(const Node *node, const Node *relative)
5304{
5305 Q_ASSERT(node);
5306 Q_ASSERT(relative);
5307
5308 // From Generator::appendFullName.
5309 m_writer->writeStartElement(dbNamespace, "link");
5310 m_writer->writeAttribute(xlinkNamespace, "href", fullDocumentLocation(node));
5311 m_writer->writeAttribute(xlinkNamespace, "role", targetType(node));
5312 m_writer->writeCharacters(node->fullName(relative));
5313 m_writer->writeEndElement(); // link
5314}
5315
5316void DocBookGenerator::generateFullName(const Node *apparentNode, const QString &fullName,
5317 const Node *actualNode)
5318{
5319 Q_ASSERT(apparentNode);
5320 Q_ASSERT(actualNode);
5321
5322 // From Generator::appendFullName.
5323 m_writer->writeStartElement(dbNamespace, "link");
5324 m_writer->writeAttribute(xlinkNamespace, "href", fullDocumentLocation(actualNode));
5325 m_writer->writeAttribute("role", targetType(actualNode));
5326 m_writer->writeCharacters(fullName);
5327 m_writer->writeEndElement(); // link
5328}
5329
5330QT_END_NAMESPACE
#define ATOM_LIST_BULLET
Definition atom.h:220
#define ATOM_FORMATTING_TELETYPE
Definition atom.h:215
#define ATOM_LIST_LOWERALPHA
Definition atom.h:223
#define ATOM_FORMATTING_UNDERLINE
Definition atom.h:218
#define ATOM_LIST_UPPERALPHA
Definition atom.h:226
#define ATOM_FORMATTING_NOTRANSLATE
Definition atom.h:210
#define ATOM_LIST_TAG
Definition atom.h:221
#define ATOM_LIST_LOWERROMAN
Definition atom.h:224
#define ATOM_FORMATTING_SUBSCRIPT
Definition atom.h:213
#define ATOM_FORMATTING_BOLD
Definition atom.h:206
#define ATOM_FORMATTING_TRADEMARK
Definition atom.h:216
#define ATOM_LIST_VALUE
Definition atom.h:222
#define ATOM_FORMATTING_ITALIC
Definition atom.h:208
#define ATOM_LIST_UPPERROMAN
Definition atom.h:227
#define ATOM_FORMATTING_LINK
Definition atom.h:209
#define ATOM_FORMATTING_SUPERSCRIPT
Definition atom.h:214
#define ATOM_FORMATTING_UICONTROL
Definition atom.h:217
#define ATOM_FORMATTING_PARAMETER
Definition atom.h:211
The Atom class is the fundamental unit for representing documents internally.
Definition atom.h:19
AtomType type() const
Return the type of this atom.
Definition atom.h:155
@ CaptionLeft
Definition atom.h:29
@ ListTagLeft
Definition atom.h:67
@ DivRight
Definition atom.h:42
@ GeneratedList
Definition atom.h:52
@ BriefRight
Definition atom.h:27
@ CodeQuoteArgument
Definition atom.h:33
@ WarningLeft
Definition atom.h:110
@ SinceList
Definition atom.h:89
@ TableOfContentsLeft
Definition atom.h:104
@ TableOfContentsRight
Definition atom.h:105
@ Keyword
Definition atom.h:59
@ TableHeaderRight
Definition atom.h:99
@ FormatElse
Definition atom.h:47
@ InlineImage
Definition atom.h:58
@ TableRowRight
Definition atom.h:101
@ LineBreak
Definition atom.h:62
@ SnippetCommand
Definition atom.h:92
@ TableRowLeft
Definition atom.h:100
@ Nop
Definition atom.h:74
@ LegaleseRight
Definition atom.h:61
@ ListTagRight
Definition atom.h:68
@ CaptionRight
Definition atom.h:30
@ NavLink
Definition atom.h:73
@ ListItemNumber
Definition atom.h:66
@ SinceTagRight
Definition atom.h:91
@ DetailsLeft
Definition atom.h:37
@ RawString
Definition atom.h:82
@ Target
Definition atom.h:106
@ AnnotatedList
Definition atom.h:22
@ SectionRight
Definition atom.h:84
@ SectionHeadingLeft
Definition atom.h:85
@ TableLeft
Definition atom.h:96
@ ListItemRight
Definition atom.h:70
@ Image
Definition atom.h:54
@ ListItemLeft
Definition atom.h:69
@ String
Definition atom.h:95
@ ListLeft
Definition atom.h:65
@ NavAutoLink
Definition atom.h:72
@ CodeQuoteCommand
Definition atom.h:34
@ BriefLeft
Definition atom.h:26
@ ImageText
Definition atom.h:55
@ LegaleseLeft
Definition atom.h:60
@ ParaRight
Definition atom.h:78
@ FormattingLeft
Definition atom.h:50
@ FormattingRight
Definition atom.h:51
@ SectionHeadingRight
Definition atom.h:86
@ Link
Definition atom.h:63
@ ImportantLeft
Definition atom.h:56
@ FormatEndif
Definition atom.h:48
@ BR
Definition atom.h:25
@ DetailsRight
Definition atom.h:38
@ AutoLink
Definition atom.h:23
@ SnippetLocation
Definition atom.h:94
@ TableHeaderLeft
Definition atom.h:98
@ ComparesRight
Definition atom.h:36
@ SectionLeft
Definition atom.h:83
@ LinkNode
Definition atom.h:64
@ HR
Definition atom.h:53
@ DivLeft
Definition atom.h:41
@ TableItemLeft
Definition atom.h:102
@ BaseName
Definition atom.h:24
@ ComparesLeft
Definition atom.h:35
@ FormatIf
Definition atom.h:49
@ SnippetIdentifier
Definition atom.h:93
@ NoteLeft
Definition atom.h:75
@ DetailsSummaryRight
Definition atom.h:40
@ DetailsSummaryLeft
Definition atom.h:39
const Atom * next() const
Return the next atom in the atom list.
Definition atom.h:152
The ClassNode represents a C++ class.
Definition classnode.h:23
bool isQmlNativeType()
Definition classnode.h:54
virtual Atom::AtomType atomType() const
Definition codemarker.h:24
A class for holding the members of a collection of doc pages.
const NodeList & members() const
NodeMap getMembers(NodeType type) const
void generatePageNode(PageNode *pn)
Generate the DocBook page for an entity that doesn't map to any underlying parsable C++ or QML elemen...
void generateQmlTypePage(QmlTypeNode *qcn)
Generate the DocBook page for a QML type.
void generateAlsoList(const Node *node) override
void generateQmlRequisites(const QmlTypeNode *qcn)
Lists the required imports and includes.
void generateAddendum(const Node *node, Generator::Addendum type, CodeMarker *marker, AdmonitionPrefix prefix) override
Generates an addendum note of type type for node.
void generateHeader(const Text &title, const Node *node)
Generates the opening portion of the DocBook header — {<db:info>} and the title element.
void initializeGenerator() override
Initializes the DocBook output generator's data structures from the configuration (Config).
bool generateText(const Text &text, const Node *relative) override
Generate the documentation for relative.
void finishHeader(const Node *node)
Emits the tail of the DocBook header — product, edition, titleabbrev, navigation links,...
void generateCppReferencePage(Node *node)
Generate a reference page for the C++ class, namespace, or header file documented in node.
void generateSortedQmlNames(const Node *base, const QStringList &knownTypes, const NodeList &subs)
DocBookGenerator(FileResolver &file_resolver)
bool generateThreadSafeness(const Node *node)
Generates text that explains how threadsafe and/or reentrant node is.
void generateCollectionNode(CollectionNode *cn)
Generate the HTML page for a group, module, or QML module.
void generateHeader(const Text &title, const QString &subtitle, const Node *node)
void generateDocBookSynopsis(const Node *node)
Generate the metadata for the given node in DocBook.
void generateGenericCollectionPage(CollectionNode *cn)
Generate the HTML page for a generic collection.
void generateList(const Node *relative, const QString &selector, Qt::SortOrder sortOrder=Qt::AscendingOrder)
void generateBody(const Node *node)
Generate the body of the documentation from the qdoc comment found with the entity represented by the...
qsizetype generateAtom(const Atom *atom, const Node *relative, CodeMarker *) override
Generate DocBook from an instance of Atom.
void generateProxyPage(Aggregate *aggregate)
bool generateStatus(const Node *node)
void generateRequisites(const Aggregate *inner)
Lists the required imports and includes.
void generateSortedNames(const ClassNode *cn, const QList< RelatedClass > &rc)
void generateDocumentation(Node *node) override
Recursive writing of DocBook files from the root node.
void generateGroupReferenceText(const Node *node)
Return a string representing a text that exposes information about the groups that the node is part o...
QString format() const override
Returns the format identifier for this producer (e.g., "HTML", "DocBook", "template").
void generateExampleFilePage(const PageNode *en, ResolvedFile resolved_file, CodeMarker *=nullptr) override
Generate a file with the contents of a C++ or QML source file.
QString fileExtension() const override
Returns "xml" for this subclass of Generator.
bool generateSince(const Node *node)
Definition doc.h:32
const Location & location() const
Returns the starting location of a qdoc comment.
Definition doc.cpp:89
static void quoteFromFile(const Location &location, Quoter &quoter, ResolvedFile resolved_file, CodeMarker *marker=nullptr)
Definition doc.cpp:461
const Text & body() const
Definition doc.cpp:114
Text briefText(bool inclusive=false) const
Definition doc.cpp:126
bool isAnonymous() const
Definition enumnode.h:34
const TypedefNode * flagsType() const
Definition enumnode.h:38
Encapsulate the logic that QDoc uses to find files whose path is provided by the user and that are re...
This node is used to represent any kind of function being documented.
bool isMacroWithoutParams() const
bool isPrivateSignal() const
bool isOverride() const
const Parameters & parameters() const
bool isPureVirtual() const override
bool isRef() const
bool isMAssign() const
bool isVirtual() const
bool isCAssign() const
bool isInvokable() const
bool hasOverloads() const
Returns true if this function has overloads.
bool isDtor() const
bool isSignal() const
bool isQmlSignal() const
bool isMacro() const override
returns true if either FunctionNode::isMacroWithParams() or FunctionNode::isMacroWithoutParams() retu...
bool isOverload() const
bool isRefRef() const
bool isDeletedAsWritten() const
bool isCCtor() const
bool isMCtor() const
bool isFinal() const
bool isCtor() const
bool isExplicitlyDefaulted() const
bool hasAssociatedProperties() const
bool isSlot() const
bool generateComparisonCategory(const Node *node, CodeMarker *marker=nullptr)
static void setQmlTypeContext(QmlTypeNode *t)
Definition generator.h:91
const Atom * generateAtomList(const Atom *atom, const Node *relative, CodeMarker *marker, bool generate, int &numGeneratedAtoms)
void unknownAtom(const Atom *atom)
static bool matchAhead(const Atom *atom, Atom::AtomType expectedAtomType)
void generateEnumValuesForQmlReference(const Node *node, CodeMarker *marker)
virtual int skipAtoms(const Atom *atom, Atom::AtomType type) const
@ AssociatedProperties
Definition generator.h:47
@ PrivateSignal
Definition generator.h:45
@ QmlSignalHandler
Definition generator.h:46
@ BindableProperty
Definition generator.h:48
@ OverloadNote
Definition generator.h:49
bool generateComparisonTable(const Node *node)
Generates a table of comparison categories for node, combining both self-comparison (from \compares) ...
virtual void initializeGenerator()
No-op base implementation.
void initializeTextOutput()
Resets the variables used during text output.
static bool isIncluded(const InclusionPolicy &policy, const NodeContext &context)
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.
Interface implemented by Node subclasses that can refer to a C++ enum.
Definition nativeenum.h:28
virtual const NativeEnum * nativeEnum() const =0
Encapsulates information about native (C++) enum values.
Definition nativeenum.h:14
const EnumNode * enumNode() const
Definition nativeenum.h:19
A PageNode is a Node that generates a documentation page.
Definition pagenode.h:19
bool noAutoList() const
Returns the value of the no auto-list flag.
Definition pagenode.h:42
The Parameter class describes one function parameter.
Definition parameter.h:14
This class describes one instance of using the Q_PROPERTY macro.
This class provides exclusive access to the qdoc database, which consists of a forrest of trees and a...
static QDocDatabase * qdocDB()
Creates the singleton.
Status
Specifies the status of the QQmlIncubator.
bool isDefault() const override
Returns true if the QML property node is marked as default.
bool isReadOnly() const
Returns true if this QML property node is marked as a read-only property.
bool isRequired() const
Const overloads that delegate to the resolving non-const versions when the attribute hasn't been cach...
bool isAttached() const override
Returns true if the QML property or QML method node is marked as attached.
ClassNode * classNode() const override
If this is a QmlTypeNode, this function returns the pointer to the C++ ClassNode that this QML type r...
Definition qmltypenode.h:27
bool isUncreatable() const
Definition qmltypenode.h:35
static void subclasses(const Node *base, NodeList &subs, bool recurse=false)
Loads the list subs with the nodes of all the subclasses of base.
bool isSingleton() const
Definition qmltypenode.h:31
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:54
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:47
A class for containing the elements of one documentation section.
Definition sections.h:17
const NodeVector & obsoleteMembers() const
Definition sections.h:54
void appendMembers(const NodeVector &nv)
Definition sections.h:55
@ 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:46
Style style() const
Definition sections.h:41
A class for creating vectors of collections for documentation.
Definition sections.h:80
const Aggregate * aggregate() const
Definition sections.h:161
Sections(const Aggregate *aggregate)
This constructor builds the section vectors based on the type of the aggregate node.
Definition sections.cpp:372
bool hasObsoleteMembers(SectionPtrVector *summary_spv, SectionPtrVector *details_spv) const
Returns true if any sections in this object contain obsolete members.
Definition sections.cpp:967
SectionVector & detailsSections()
Definition sections.h:152
Definition text.h:12
static Text sectionHeading(const Atom *sectionBegin)
Definition text.cpp:176
const Atom * firstAtom() const
Definition text.h:34
bool isEmpty() const
Definition text.h:31
Text & operator=(const Text &text)
Definition text.cpp:29
Text()
Definition text.cpp:12
const Atom * lastAtom() const
Definition text.h:35
Atom * lastAtom()
Definition text.h:22
bool isStatic() const override
Returns true if the FunctionNode represents a static function.
static bool isOneColumnValueTable(const Atom *atom)
Determines whether the list atom should be shown with just one column (value).
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)
static const QRegularExpression m_funcLeftParen
static NodeType typeFromString(const Atom *atom)
Returns the type of this atom as an enumeration.
#define CONFIG_DOCBOOKEXTENSIONS
Definition config.h:388
#define CONFIG_EXAMPLES
Definition config.h:392
#define CONFIG_URL
Definition config.h:457
#define CONFIG_PRELIMINARY
Definition config.h:433
#define CONFIG_DESCRIPTION
Definition config.h:387
#define CONFIG_PROJECT
Definition config.h:435
#define CONFIG_EXAMPLESINSTALLPATH
Definition config.h:393
#define CONFIG_NATURALLANGUAGE
Definition config.h:426
#define CONFIG_PRODUCTNAME
Definition config.h:434
#define CONFIG_BUILDVERSION
Definition config.h:378
static const char xlinkNamespace[]
static QString nodeToSynopsisTag(const Node *node)
QString removeCodeMarkers(const QString &code)
static const char dbNamespace[]
QString taggedNode(const Node *node)
static const char itsNamespace[]
NodeType
Definition genustypes.h:154
constexpr std::size_t MultilineTemplateParamThreshold
Combined button and popup list for selecting options.
QList< Node * > NodeList
Definition node.h:45
QList< Node * > NodeVector
Definition node.h:47
QMap< QString, Node * > NodeMap
Definition node.h:48
QMap< QString, NodeMap > NodeMapMap
Definition node.h:49
QMap< QString, CollectionNode * > CNMap
Definition node.h:52
QT_BEGIN_NAMESPACE typedef QMultiMap< Text, const Node * > TextToNodeMap
QMultiMap< QString, Node * > NodeMultiMap
Definition generator.h:36
QList< const Section * > SectionPtrVector
Definition sections.h:77
QList< Section > SectionVector
Definition sections.h:76
@ Active
Definition status.h:14
@ InternalAuto
Definition status.h:16
@ Internal
Definition status.h:15
The Node class is the base class for all the nodes in QDoc's parse tree.
bool isExternalPage() const
Returns true if the node type is ExternalPage.
Definition node.h:100
const Doc & doc() const
Returns a reference to the node's Doc data member.
Definition node.h:237
bool isQmlNode() const
Returns true if this node's Genus value is QML.
Definition node.h:121
bool isEnumType(Genus g) const
Definition node.h:98
bool isGroup() const
Returns true if the node type is Group.
Definition node.h:105
virtual bool docMustBeGenerated() const
This function is called to perform a test to decide if the node must have documentation generated.
Definition node.h:197
bool isPrivate() const
Returns true if this node's access is Private.
Definition node.h:113
bool isNamespace() const
Returns true if the node type is Namespace.
Definition node.h:110
bool isTypedef() const
Returns true if the node type is Typedef.
Definition node.h:128
bool isQmlBasicType() const
Returns true if the node type is QmlBasicType.
Definition node.h:119
bool isQmlType() const
Returns true if the node type is QmlType or QmlValueType.
Definition node.h:123
bool isSharedCommentNode() const
Returns true if the node type is SharedComment.
Definition node.h:126
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:871
bool isHeader() const
Returns true if the node type is HeaderFile.
Definition node.h:106
NodeType nodeType() const override
Returns this node's type.
Definition node.h:82
Genus genus() const override
Returns this node's Genus.
Definition node.h:85
virtual bool isPageNode() const
Returns true if this node represents something that generates a documentation page.
Definition node.h:150
bool isEnumType() const
Returns true if the node type is Enum.
Definition node.h:94
virtual Status status() const
Returns the node's status value.
Definition node.h:241
virtual bool isTextPageNode() const
Returns true if the node is a PageNode but not an Aggregate.
Definition node.h:155
Aggregate * parent() const
Returns the node's parent pointer.
Definition node.h:210
bool isVariable() const
Returns true if the node type is Variable.
Definition node.h:133
virtual bool isDeprecated() const
Returns true if this node's status is Deprecated.
Definition node.h:136
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:138
static bool nodeNameLessThan(const Node *first, const Node *second)
Returns true if the node n1 is less than node n2.
Definition node.cpp:111
const Location & location() const
If this node's definition location is empty, this function returns this node's declaration location.
Definition node.h:233
bool isProxyNode() const
Returns true if the node type is Proxy.
Definition node.h:115
Access access() const
Returns the node's Access setting, which can be Public, Protected, or Private.
Definition node.h:230
bool isFunction(Genus g=Genus::DontCare) const
Returns true if this is a FunctionNode and its Genus is set to g.
Definition node.h:101
ThreadSafeness threadSafeness() const
Returns the thread safeness value for whatever this node represents.
Definition node.cpp:848
virtual bool isMarkedReimp() const
Returns true if the FunctionNode is marked as a reimplemented function.
Definition node.h:152
bool isProperty() const
Returns true if the node type is Property.
Definition node.h:114
NodeContext createContext() const
Definition node.cpp:175
bool isModule() const
Returns true if the node type is Module.
Definition node.h:108
bool isClass() const
Returns true if the node type is Class.
Definition node.h:91
virtual bool isPropertyGroup() const
Returns true if the node is a SharedCommentNode for documenting multiple C++ properties or multiple Q...
Definition node.h:153
ThreadSafeness
An unsigned char that specifies the degree of thread-safeness of the element.
Definition node.h:58
@ ThreadSafe
Definition node.h:62
@ NonReentrant
Definition node.h:60
@ Reentrant
Definition node.h:61
bool isSharingComment() const
This function returns true if the node is sharing a comment with other nodes.
Definition node.h:248
bool hasDoc() const
Returns true if this node is documented, or it represents a documented node read from the index ('had...
Definition node.cpp:945
bool isRelatedNonmember() const
Returns true if this is a related nonmember of something.
Definition node.h:124
virtual bool isClassNode() const
Returns true if this is an instance of ClassNode.
Definition node.h:145
virtual bool isCollectionNode() const
Returns true if this is an instance of CollectionNode.
Definition node.h:146
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:161
bool isQmlModule() const
Returns true if the node type is QmlModule.
Definition node.h:120
@ SignatureReturnType
Definition node.h:68
@ SignaturePlain
Definition node.h:66
bool isExample() const
Returns true if the node type is Example.
Definition node.h:99
bool isIndexNode() const
Returns true if this node was created from something in an index file.
Definition node.h:107
bool isQmlProperty() const
Returns true if the node type is QmlProperty.
Definition node.h:122
A class for parsing and managing a function parameter list.
Definition main.cpp:28
bool isEmpty() const
Definition parameters.h:32
const Parameter & at(int i) const
Definition parameters.h:36
int count() const
Definition parameters.h:34
std::size_t visibleParameterCount() const
Returns the number of template parameters that are visible in rendered output — SFINAE-annotated para...
ValuedDeclaration valued_declaration
Represents a file that is reachable by QDoc based on its current configuration.