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

source code of qttools/src/qdoc/qdoc/src/qdoc/docbookgenerator.cpp