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