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