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

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