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