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