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 SectionVector sectionVector =
3156 (aggregate->isNamespace() || aggregate->isHeader()) ?
3157 sections.stdDetailsSections() :
3159 for (const Section &section : sectionVector) {
3160 if (section.members().isEmpty())
3161 continue;
3162
3163 startSection(section.title().toLower(), section.title());
3164
3165 for (const Node *member : section.members()) {
3166 if (member->nodeType() != NodeType::Class) {
3167 // This function starts its own section.
3168 generateDetailedMember(member, aggregate);
3169 } else {
3170 startSectionBegin();
3171 m_writer->writeCharacters("class ");
3172 generateFullName(member, aggregate);
3173 startSectionEnd();
3174
3175 generateBrief(member);
3176
3177 endSection();
3178 }
3179 }
3180
3181 endSection();
3182 }
3183
3184 generateObsoleteMembers(sections);
3185
3186 endDocument();
3187}
3188
3189void DocBookGenerator::generateSynopsisInfo(const QString &key, const QString &value)
3190{
3191 m_writer->writeStartElement(dbNamespace, "synopsisinfo");
3192 m_writer->writeAttribute("role", key);
3193 m_writer->writeCharacters(value);
3194 m_writer->writeEndElement(); // synopsisinfo
3195 newLine();
3196}
3197
3198void DocBookGenerator::generateModifier(const QString &value)
3199{
3200 m_writer->writeTextElement(dbNamespace, "modifier", value);
3201 newLine();
3202}
3203
3204/*!
3205 Generate the metadata for the given \a node in DocBook.
3206 */
3208{
3209 if (!node)
3210 return;
3211
3212 // From Generator::generateStatus, HtmlGenerator::generateRequisites,
3213 // Generator::generateThreadSafeness, QDocIndexFiles::generateIndexSection.
3214
3215 // This function is the major place where DocBook extensions are used.
3216 if (!m_useDocBook52)
3217 return;
3218
3219 // Nothing to export in some cases. Note that isSharedCommentNode() returns
3220 // true also for QML property groups.
3222 return;
3223
3224 // Cast the node to several subtypes (null pointer if the node is not of the required type).
3225 const Aggregate *aggregate =
3226 node->isAggregate() ? static_cast<const Aggregate *>(node) : nullptr;
3227 const ClassNode *classNode = node->isClass() ? static_cast<const ClassNode *>(node) : nullptr;
3228 const FunctionNode *functionNode =
3229 node->isFunction() ? static_cast<const FunctionNode *>(node) : nullptr;
3230 const PropertyNode *propertyNode =
3231 node->isProperty() ? static_cast<const PropertyNode *>(node) : nullptr;
3232 const VariableNode *variableNode =
3233 node->isVariable() ? static_cast<const VariableNode *>(node) : nullptr;
3234 const EnumNode *enumNode = node->isEnumType() ? static_cast<const EnumNode *>(node) : nullptr;
3235 const QmlPropertyNode *qpn =
3236 node->isQmlProperty() ? static_cast<const QmlPropertyNode *>(node) : nullptr;
3237 const QmlTypeNode *qcn = node->isQmlType() ? static_cast<const QmlTypeNode *>(node) : nullptr;
3238 // Typedefs are ignored, as they correspond to enums.
3239 // Groups and modules are ignored.
3240 // Documents are ignored, they have no interesting metadata.
3241
3242 // Start the synopsis tag.
3243 QString synopsisTag = nodeToSynopsisTag(node);
3244 m_writer->writeStartElement(dbNamespace, synopsisTag);
3245 newLine();
3246
3247 // Name and basic properties of each tag (like types and parameters).
3248 if (node->isClass()) {
3249 m_writer->writeStartElement(dbNamespace, "ooclass");
3250 m_writer->writeTextElement(dbNamespace, "classname", node->plainName());
3251 m_writer->writeEndElement(); // ooclass
3252 newLine();
3253 } else if (node->isNamespace()) {
3254 m_writer->writeTextElement(dbNamespace, "namespacename", node->plainName());
3255 newLine();
3256 } else if (node->isQmlType()) {
3257 m_writer->writeStartElement(dbNamespace, "ooclass");
3258 m_writer->writeTextElement(dbNamespace, "classname", node->plainName());
3259 m_writer->writeEndElement(); // ooclass
3260 newLine();
3261 if (!qcn->groupNames().isEmpty())
3262 m_writer->writeAttribute("groups", qcn->groupNames().join(QLatin1Char(',')));
3263 } else if (node->isProperty()) {
3264 m_writer->writeTextElement(dbNamespace, "modifier", "(Qt property)");
3265 newLine();
3266 m_writer->writeTextElement(dbNamespace, "type", propertyNode->dataType());
3267 newLine();
3268 m_writer->writeTextElement(dbNamespace, "varname", node->plainName());
3269 newLine();
3270 } else if (node->isVariable()) {
3271 if (variableNode->isStatic()) {
3272 m_writer->writeTextElement(dbNamespace, "modifier", "static");
3273 newLine();
3274 }
3275 m_writer->writeTextElement(dbNamespace, "type", variableNode->dataType());
3276 newLine();
3277 m_writer->writeTextElement(dbNamespace, "varname", node->plainName());
3278 newLine();
3279 } else if (node->isEnumType()) {
3280 if (!enumNode->isAnonymous()) {
3281 m_writer->writeTextElement(dbNamespace, "enumname", node->plainName());
3282 newLine();
3283 }
3284 } else if (node->isQmlProperty()) {
3285 QString name = node->name();
3286 if (qpn->isAttached())
3287 name.prepend(qpn->element() + QLatin1Char('.'));
3288
3289 m_writer->writeTextElement(dbNamespace, "type", qpn->dataType());
3290 newLine();
3291 m_writer->writeTextElement(dbNamespace, "varname", name);
3292 newLine();
3293
3294 const bool readOnly = qpn->isReadOnly();
3295 const bool required = qpn->isRequired();
3296
3297 // Semantic modifiers for DocBook (machine-readable)
3298 if (qpn->isAttached()) {
3299 m_writer->writeTextElement(dbNamespace, "modifier", "attached");
3300 newLine();
3301 }
3302 if (!readOnly) {
3303 m_writer->writeTextElement(dbNamespace, "modifier", "writable");
3304 newLine();
3305 }
3306 if (required) {
3307 m_writer->writeTextElement(dbNamespace, "modifier", "required");
3308 newLine();
3309 }
3310 // Presentation modifiers (human-readable)
3311 if (readOnly) {
3312 generateModifier("[read-only]");
3313 newLine();
3314 }
3315 if (qpn->isDefault()) {
3316 generateModifier("[default]");
3317 newLine();
3318 }
3319 } else if (node->isFunction()) {
3320 if (functionNode->virtualness() != "non")
3321 generateModifier("virtual");
3322 if (functionNode->isConst())
3323 generateModifier("const");
3324 if (functionNode->isStatic())
3325 generateModifier("static");
3326
3327 if (!functionNode->isMacro() && !functionNode->isCtor() &&
3328 !functionNode->isCCtor() && !functionNode->isMCtor()
3329 && !functionNode->isDtor()) {
3330 if (functionNode->returnType() == "void")
3331 m_writer->writeEmptyElement(dbNamespace, "void");
3332 else
3333 m_writer->writeTextElement(dbNamespace, "type", functionNode->returnTypeString());
3334 newLine();
3335 }
3336 // Remove two characters from the plain name to only get the name
3337 // of the method without parentheses (only for functions, not macros).
3338 QString name = node->plainName();
3339 if (name.endsWith("()"))
3340 name.chop(2);
3341 m_writer->writeTextElement(dbNamespace, "methodname", name);
3342 newLine();
3343
3344 if (functionNode->parameters().isEmpty()) {
3345 m_writer->writeEmptyElement(dbNamespace, "void");
3346 newLine();
3347 }
3348
3349 const Parameters &lp = functionNode->parameters();
3350 for (int i = 0; i < lp.count(); ++i) {
3351 const Parameter &parameter = lp.at(i);
3352 m_writer->writeStartElement(dbNamespace, "methodparam");
3353 newLine();
3354 m_writer->writeTextElement(dbNamespace, "type", parameter.type());
3355 newLine();
3356 m_writer->writeTextElement(dbNamespace, "parameter", parameter.name());
3357 newLine();
3358 if (!parameter.defaultValue().isEmpty()) {
3359 m_writer->writeTextElement(dbNamespace, "initializer", parameter.defaultValue());
3360 newLine();
3361 }
3362 m_writer->writeEndElement(); // methodparam
3363 newLine();
3364 }
3365
3366 if (functionNode->isImplicitlyGenerated())
3367 generateModifier("implicit");
3368 else if (functionNode->isExplicitlyDefaulted())
3369 generateModifier("default");
3370 else if (functionNode->isDeletedAsWritten())
3371 generateModifier("delete");
3372 if (functionNode->isFinal())
3373 generateModifier("final");
3374 if (functionNode->isOverride())
3375 generateModifier("override");
3376 } else if (node->isTypedef()) {
3377 m_writer->writeTextElement(dbNamespace, "typedefname", node->plainName());
3378 newLine();
3379 } else {
3380 node->doc().location().warning(
3381 QStringLiteral("Unexpected node type in generateDocBookSynopsis: %1")
3382 .arg(node->nodeTypeString()));
3383 newLine();
3384 }
3385
3386 // Enums and typedefs.
3387 if (enumNode) {
3388 for (const EnumItem &item : enumNode->items()) {
3389 m_writer->writeStartElement(dbNamespace, "enumitem");
3390 newLine();
3391 m_writer->writeTextElement(dbNamespace, "enumidentifier", item.name());
3392 newLine();
3393 m_writer->writeTextElement(dbNamespace, "enumvalue", item.value());
3394 newLine();
3395 m_writer->writeEndElement(); // enumitem
3396 newLine();
3397 }
3398
3399 if (enumNode->items().isEmpty()) {
3400 // If the enumeration is empty (really rare case), still produce
3401 // something for the DocBook document to be valid.
3402 m_writer->writeStartElement(dbNamespace, "enumitem");
3403 newLine();
3404 m_writer->writeEmptyElement(dbNamespace, "enumidentifier");
3405 newLine();
3406 m_writer->writeEndElement(); // enumitem
3407 newLine();
3408 }
3409 }
3410
3411 // Below: only synopsisinfo within synopsisTag. These elements must be at
3412 // the end of the tag, as per DocBook grammar.
3413
3414 // Information for functions that could not be output previously
3415 // (synopsisinfo).
3416 if (node->isFunction()) {
3417 generateSynopsisInfo("meta", functionNode->metanessString());
3418
3419 if (functionNode->isOverload()) {
3420 generateSynopsisInfo("overload", "overload");
3421 generateSynopsisInfo("overload-number",
3422 QString::number(functionNode->overloadNumber()));
3423 }
3424
3425 if (functionNode->isRef())
3426 generateSynopsisInfo("refness", QString::number(1));
3427 else if (functionNode->isRefRef())
3428 generateSynopsisInfo("refness", QString::number(2));
3429
3430 if (functionNode->hasAssociatedProperties()) {
3431 QStringList associatedProperties;
3432 const auto &nodes = functionNode->associatedProperties();
3433 for (const Node *n : nodes) {
3434 const auto pn = static_cast<const PropertyNode *>(n);
3435 associatedProperties << pn->name();
3436 }
3437 associatedProperties.sort();
3438 generateSynopsisInfo("associated-property",
3439 associatedProperties.join(QLatin1Char(',')));
3440 }
3441
3442 QString signature = functionNode->signature(Node::SignatureReturnType);
3443 // 'const' is already part of FunctionNode::signature()
3444 if (functionNode->isFinal())
3445 signature += " final";
3446 if (functionNode->isOverride())
3447 signature += " override";
3448 if (functionNode->isPureVirtual())
3449 signature += " = 0";
3450 else if (functionNode->isExplicitlyDefaulted())
3451 signature += " = default";
3452 else if (functionNode->isDeletedAsWritten())
3453 signature += " = delete";
3454 if (const auto &req = functionNode->trailingRequiresClause(); req && !req->isEmpty())
3455 signature += " requires " + *req;
3456 generateSynopsisInfo("signature", signature);
3457 }
3458
3459 // Accessibility status.
3460 if (!node->isPageNode() && !node->isCollectionNode()) {
3461 switch (node->access()) {
3462 case Access::Public:
3463 generateSynopsisInfo("access", "public");
3464 break;
3465 case Access::Protected:
3466 generateSynopsisInfo("access", "protected");
3467 break;
3468 case Access::Private:
3469 generateSynopsisInfo("access", "private");
3470 break;
3471 default:
3472 break;
3473 }
3474 if (node->isAbstract())
3475 generateSynopsisInfo("abstract", "true");
3476 }
3477
3478 // Status.
3479 switch (node->status()) {
3480 case Status::Active:
3481 generateSynopsisInfo("status", "active");
3482 break;
3483 case Status::Preliminary:
3484 generateSynopsisInfo("status",
3485 Config::instance().get(CONFIG_PRELIMINARY).asString().toLower());
3486 break;
3487 case Status::Deprecated:
3488 generateSynopsisInfo("status", "deprecated");
3489 break;
3490 case Status::Internal:
3491 generateSynopsisInfo("status", "internal");
3492 break;
3493 default:
3494 generateSynopsisInfo("status", "main");
3495 break;
3496 }
3497
3498 // C++ classes and name spaces.
3499 if (aggregate) {
3500 // Includes.
3501 if (aggregate->includeFile()) generateSynopsisInfo("headers", *aggregate->includeFile());
3502
3503 // Since and project.
3504 if (!aggregate->since().isEmpty())
3505 generateSynopsisInfo("since", formatSince(aggregate));
3506
3507 if (aggregate->nodeType() == NodeType::Class || aggregate->nodeType() == NodeType::Namespace) {
3508 // CMake and QT variable.
3509 if (!aggregate->physicalModuleName().isEmpty()) {
3510 const CollectionNode *cn =
3511 m_qdb->getCollectionNode(aggregate->physicalModuleName(), NodeType::Module);
3512
3513 if (const auto result = cmakeRequisite(cn)) {
3514 generateSynopsisInfo("cmake-find-package", result->first);
3515 generateSynopsisInfo("cmake-target-link-libraries", result->second);
3516 }
3517
3518 if (cn && !cn->qtVariable().isEmpty())
3519 generateSynopsisInfo("qmake", "QT += " + cn->qtVariable());
3520 }
3521 }
3522
3523 if (aggregate->nodeType() == NodeType::Class) {
3524 // Native type
3525 auto *classe = const_cast<ClassNode *>(static_cast<const ClassNode *>(aggregate));
3526 if (classe && classe->isQmlNativeType() && classe->status() != Status::Internal) {
3527 m_writer->writeStartElement(dbNamespace, "synopsisinfo");
3528 m_writer->writeAttribute("role", "nativeTypeFor");
3529
3530 QList<QmlTypeNode *> nativeTypes { classe->qmlNativeTypes().cbegin(), classe->qmlNativeTypes().cend()};
3531 std::sort(nativeTypes.begin(), nativeTypes.end(), Node::nodeNameLessThan);
3532
3533 for (auto item : std::as_const(nativeTypes)) {
3534 const Node *otherNode{nullptr};
3535 Atom a = Atom(Atom::LinkNode, Utilities::stringForNode(item));
3536 const QString &link = getAutoLink(&a, aggregate, &otherNode);
3537 generateSimpleLink(link, item->name());
3538 }
3539
3540 m_writer->writeEndElement(); // synopsisinfo
3541 }
3542
3543 // Inherits.
3544 QList<RelatedClass>::ConstIterator r;
3545 if (!classe->baseClasses().isEmpty()) {
3546 m_writer->writeStartElement(dbNamespace, "synopsisinfo");
3547 m_writer->writeAttribute("role", "inherits");
3548
3549 r = classe->baseClasses().constBegin();
3550 int index = 0;
3551 while (r != classe->baseClasses().constEnd()) {
3552 if ((*r).m_node) {
3553 generateFullName((*r).m_node, classe);
3554
3555 if ((*r).m_access == Access::Protected) {
3556 m_writer->writeCharacters(" (protected)");
3557 } else if ((*r).m_access == Access::Private) {
3558 m_writer->writeCharacters(" (private)");
3559 }
3560 m_writer->writeCharacters(
3561 Utilities::comma(index++, classe->baseClasses().size()));
3562 }
3563 ++r;
3564 }
3565
3566 m_writer->writeEndElement(); // synopsisinfo
3567 newLine();
3568 }
3569
3570 // Inherited by.
3571 if (!classe->derivedClasses().isEmpty()) {
3572 m_writer->writeStartElement(dbNamespace, "synopsisinfo");
3573 m_writer->writeAttribute("role", "inheritedBy");
3574 generateSortedNames(classe, classe->derivedClasses());
3575 m_writer->writeEndElement(); // synopsisinfo
3576 newLine();
3577 }
3578 }
3579 }
3580
3581 // QML types.
3582 if (qcn) {
3583 // Module name and version (i.e. import).
3584 QString logicalModuleVersion;
3585 const CollectionNode *collection =
3586 m_qdb->getCollectionNode(qcn->logicalModuleName(), qcn->nodeType());
3587 if (collection)
3588 logicalModuleVersion = collection->logicalModuleVersion();
3589 else
3590 logicalModuleVersion = qcn->logicalModuleVersion();
3591
3592 QStringList importText;
3593 importText << "import " + qcn->logicalModuleName();
3594 if (!logicalModuleVersion.isEmpty())
3595 importText << logicalModuleVersion;
3596 generateSynopsisInfo("import", importText.join(' '));
3597
3598 // Since and project.
3599 if (!qcn->since().isEmpty())
3600 generateSynopsisInfo("since", formatSince(qcn));
3601
3602 QmlTypeNode *base = qcn->qmlBaseNode();
3603 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
3604 while (base) {
3605 const NodeContext context = base->createContext();
3606 if (InclusionFilter::isIncluded(policy, context))
3607 break;
3608 base = base->qmlBaseNode();
3609 }
3610
3611 QStringList knownTypeNames{qcn->name()};
3612 if (base)
3613 knownTypeNames << base->name();
3614
3615 // Inherited by.
3616 NodeList subs;
3617 QmlTypeNode::subclasses(qcn, subs);
3618 if (!subs.isEmpty()) {
3619 m_writer->writeTextElement(dbNamespace, "synopsisinfo");
3620 m_writer->writeAttribute("role", "inheritedBy");
3621 generateSortedQmlNames(qcn, knownTypeNames, subs);
3622 m_writer->writeEndElement(); // synopsisinfo
3623 newLine();
3624 }
3625
3626 // Inherits.
3627 if (base) {
3628 const Node *otherNode = nullptr;
3629 Atom a = Atom(Atom::LinkNode, Utilities::stringForNode(base));
3630 QString link = getAutoLink(&a, base, &otherNode);
3631
3632 m_writer->writeTextElement(dbNamespace, "synopsisinfo");
3633 m_writer->writeAttribute("role", "inherits");
3634 generateSimpleLink(link, base->name());
3635 // Disambiguate with '(<QML module name>)' if there are clashing type names
3636 for (const auto sub : std::as_const(subs)) {
3637 if (knownTypeNames.contains(sub->name())) {
3638 m_writer->writeCharacters(" (%1)"_L1.arg(base->logicalModuleName()));
3639 break;
3640 }
3641 }
3642 m_writer->writeEndElement(); // synopsisinfo
3643 newLine();
3644 }
3645
3646 // Native type
3647 ClassNode *cn = (const_cast<QmlTypeNode *>(qcn))->classNode();
3648
3649 if (cn && cn->isQmlNativeType() && (cn->status() != Status::Internal)) {
3650 const Node *otherNode = nullptr;
3651 Atom a = Atom(Atom::LinkNode, Utilities::stringForNode(qcn));
3652 QString link = getAutoLink(&a, cn, &otherNode);
3653
3654 m_writer->writeTextElement(dbNamespace, "synopsisinfo");
3655 m_writer->writeAttribute("role", "nativeType");
3656 generateSimpleLink(link, cn->name());
3657 m_writer->writeEndElement(); // synopsisinfo
3658 newLine();
3659 }
3660 }
3661
3662 // Thread safeness.
3663 switch (node->threadSafeness()) {
3664 case Node::UnspecifiedSafeness:
3665 generateSynopsisInfo("threadsafeness", "unspecified");
3666 break;
3667 case Node::NonReentrant:
3668 generateSynopsisInfo("threadsafeness", "non-reentrant");
3669 break;
3670 case Node::Reentrant:
3671 generateSynopsisInfo("threadsafeness", "reentrant");
3672 break;
3673 case Node::ThreadSafe:
3674 generateSynopsisInfo("threadsafeness", "thread safe");
3675 break;
3676 default:
3677 generateSynopsisInfo("threadsafeness", "unspecified");
3678 break;
3679 }
3680
3681 // Module.
3682 if (!node->physicalModuleName().isEmpty())
3683 generateSynopsisInfo("module", node->physicalModuleName());
3684
3685 // Group.
3686 if (classNode && !classNode->groupNames().isEmpty()) {
3687 generateSynopsisInfo("groups", classNode->groupNames().join(QLatin1Char(',')));
3688 } else if (qcn && !qcn->groupNames().isEmpty()) {
3689 generateSynopsisInfo("groups", qcn->groupNames().join(QLatin1Char(',')));
3690 }
3691
3692 // Properties.
3693 if (propertyNode) {
3694 for (const Node *fnNode : propertyNode->getters()) {
3695 if (fnNode) {
3696 const auto funcNode = static_cast<const FunctionNode *>(fnNode);
3697 generateSynopsisInfo("getter", funcNode->name());
3698 }
3699 }
3700 for (const Node *fnNode : propertyNode->setters()) {
3701 if (fnNode) {
3702 const auto funcNode = static_cast<const FunctionNode *>(fnNode);
3703 generateSynopsisInfo("setter", funcNode->name());
3704 }
3705 }
3706 for (const Node *fnNode : propertyNode->resetters()) {
3707 if (fnNode) {
3708 const auto funcNode = static_cast<const FunctionNode *>(fnNode);
3709 generateSynopsisInfo("resetter", funcNode->name());
3710 }
3711 }
3712 for (const Node *fnNode : propertyNode->notifiers()) {
3713 if (fnNode) {
3714 const auto funcNode = static_cast<const FunctionNode *>(fnNode);
3715 generateSynopsisInfo("notifier", funcNode->name());
3716 }
3717 }
3718 }
3719
3720 m_writer->writeEndElement(); // nodeToSynopsisTag (like classsynopsis)
3721 newLine();
3722
3723 // The typedef associated to this enum. It is output *after* the main tag,
3724 // i.e. it must be after the synopsisinfo.
3725 if (enumNode && enumNode->flagsType()) {
3726 m_writer->writeStartElement(dbNamespace, "typedefsynopsis");
3727 newLine();
3728
3729 m_writer->writeTextElement(dbNamespace, "typedefname",
3730 enumNode->flagsType()->fullDocumentName());
3731 newLine();
3732
3733 m_writer->writeEndElement(); // typedefsynopsis
3734 newLine();
3735 }
3736}
3737
3739{
3740 // From CodeMarker::taggedNode, but without the tag part (i.e. only the QML specific case
3741 // remaining).
3742 // TODO: find a better name for this.
3743 if (node->nodeType() == NodeType::QmlType && node->name().startsWith(QLatin1String("QML:")))
3744 return node->name().mid(4);
3745 return node->name();
3746}
3747
3748/*!
3749 Parses a string with method/variable name and (return) type
3750 to include type tags.
3751 */
3752void DocBookGenerator::typified(const QString &string, const Node *relative, bool trailingSpace,
3753 bool generateType)
3754{
3755 // Adapted from CodeMarker::typified and HtmlGenerator::highlightedCode.
3756 QString result;
3757 QString pendingWord;
3758
3759 for (int i = 0; i <= string.size(); ++i) {
3760 QChar ch;
3761 if (i != string.size())
3762 ch = string.at(i);
3763
3764 QChar lower = ch.toLower();
3765 if ((lower >= QLatin1Char('a') && lower <= QLatin1Char('z')) || ch.digitValue() >= 0
3766 || ch == QLatin1Char('_') || ch == QLatin1Char(':')) {
3767 pendingWord += ch;
3768 } else {
3769 if (!pendingWord.isEmpty()) {
3770 bool isProbablyType = (pendingWord != QLatin1String("const"));
3771 if (generateType && isProbablyType) {
3772 // Flush the current buffer.
3773 m_writer->writeCharacters(result);
3774 result.truncate(0);
3775
3776 // Add the link, logic from HtmlGenerator::highlightedCode.
3777 const Node *n = m_qdb->findTypeNode(pendingWord, relative, Genus::DontCare);
3778 QString href;
3779 if (!(n && n->isQmlBasicType())
3780 || (relative
3781 && (relative->genus() == n->genus() || Genus::DontCare == n->genus()))) {
3782 href = linkForNode(n, relative);
3783 }
3784
3785 m_writer->writeStartElement(dbNamespace, "type");
3786 if (href.isEmpty())
3787 m_writer->writeCharacters(pendingWord);
3788 else
3789 generateSimpleLink(href, pendingWord);
3790 m_writer->writeEndElement(); // type
3791 } else {
3792 result += pendingWord;
3793 }
3794 }
3795 pendingWord.clear();
3796
3797 if (ch.unicode() != '\0')
3798 result += ch;
3799 }
3800 }
3801
3802 if (trailingSpace && string.size()) {
3803 if (!string.endsWith(QLatin1Char('*')) && !string.endsWith(QLatin1Char('&')))
3804 result += QLatin1Char(' ');
3805 }
3806
3807 m_writer->writeCharacters(result);
3808}
3809
3810void DocBookGenerator::generateSynopsisName(const Node *node, const Node *relative,
3811 bool generateNameLink)
3812{
3813 // Implements the rewriting of <@link> from HtmlGenerator::highlightedCode, only due to calls to
3814 // CodeMarker::linkTag in CppCodeMarker::markedUpSynopsis.
3815 QString name = taggedNode(node);
3816
3817 if (!generateNameLink) {
3818 m_writer->writeCharacters(name);
3819 return;
3820 }
3821
3822 m_writer->writeStartElement(dbNamespace, "emphasis");
3823 m_writer->writeAttribute("role", "bold");
3824 generateSimpleLink(linkForNode(node, relative), name);
3825 m_writer->writeEndElement(); // emphasis
3826}
3827
3828void DocBookGenerator::generateParameter(const Parameter &parameter, const Node *relative,
3829 bool generateExtra, bool generateType)
3830{
3831 const QString &pname = parameter.name();
3832 const QString &ptype = parameter.type();
3833 QString paramName;
3834 if (!pname.isEmpty()) {
3835 typified(ptype, relative, true, generateType);
3836 paramName = pname;
3837 } else {
3838 paramName = ptype;
3839 }
3840
3841 if (generateExtra || pname.isEmpty()) {
3842 m_writer->writeStartElement(dbNamespace, "emphasis");
3843 m_writer->writeCharacters(paramName);
3844 m_writer->writeEndElement(); // emphasis
3845 }
3846
3847 const QString &pvalue = parameter.defaultValue();
3848 if (generateExtra && !pvalue.isEmpty())
3849 m_writer->writeCharacters(" = " + pvalue);
3850}
3851
3852void DocBookGenerator::generateSynopsis(const Node *node, const Node *relative,
3853 Section::Style style)
3854{
3855 // From HtmlGenerator::generateSynopsis (conditions written as booleans).
3856 const bool generateExtra = style != Section::AllMembers;
3857 const bool generateType = style != Section::Details;
3858 const bool generateNameLink = style != Section::Details;
3859
3860 // From CppCodeMarker::markedUpSynopsis, reversed the generation of "extra" and "synopsis".
3861 const int MaxEnumValues = 6;
3862
3863 if (generateExtra) {
3864 if (auto extra = CodeMarker::extraSynopsis(node, style); !extra.isEmpty()) {
3865 generateExtraSynopsis(extra);
3866 m_writer->writeCharacters(u" "_s);
3867 }
3868 }
3869
3870 // Then generate the synopsis.
3871 QString namePrefix {};
3872 if (style == Section::Details) {
3873 if (!node->isRelatedNonmember() && !node->isProxyNode() && !node->parent()->name().isEmpty()
3874 && !node->parent()->isHeader() && !node->isProperty() && !node->isQmlNode()) {
3875 namePrefix = taggedNode(node->parent()) + "::";
3876 }
3877 }
3878
3879 switch (node->nodeType()) {
3880 case NodeType::Namespace:
3881 m_writer->writeCharacters("namespace ");
3882 m_writer->writeCharacters(namePrefix);
3883 generateSynopsisName(node, relative, generateNameLink);
3884 break;
3885 case NodeType::Class:
3886 m_writer->writeCharacters("class ");
3887 m_writer->writeCharacters(namePrefix);
3888 generateSynopsisName(node, relative, generateNameLink);
3889 break;
3890 case NodeType::Function: {
3891 const auto func = (const FunctionNode *)node;
3892
3893 if (style == Section::Details) {
3894 if (auto templateDecl = func->templateDecl()) {
3895 if (templateDecl->parameters.size() > QDoc::MultilineTemplateParamThreshold)
3896 m_writer->writeCharacters(templateDecl->to_qstring_multiline() + QLatin1Char('\n'));
3897 else
3898 m_writer->writeCharacters(templateDecl->to_qstring() + QLatin1Char(' '));
3899 }
3900 }
3901
3902 // First, the part coming before the name.
3903 if (style == Section::Summary || style == Section::Accessors) {
3904 if (!func->isNonvirtual())
3905 m_writer->writeCharacters(QStringLiteral("virtual "));
3906 }
3907
3908 // Name and parameters.
3909 if (style != Section::AllMembers && !func->returnType().isEmpty())
3910 typified(func->returnTypeString(), relative, true, generateType);
3911 m_writer->writeCharacters(namePrefix);
3912 generateSynopsisName(node, relative, generateNameLink);
3913
3914 if (!func->isMacroWithoutParams()) {
3915 m_writer->writeCharacters(QStringLiteral("("));
3916 if (!func->parameters().isEmpty()) {
3917 const Parameters &parameters = func->parameters();
3918 for (int i = 0; i < parameters.count(); i++) {
3919 if (i > 0)
3920 m_writer->writeCharacters(QStringLiteral(", "));
3921 generateParameter(parameters.at(i), relative, generateExtra, generateType);
3922 }
3923 }
3924 m_writer->writeCharacters(QStringLiteral(")"));
3925 }
3926
3927 if (func->isConst())
3928 m_writer->writeCharacters(QStringLiteral(" const"));
3929
3930 if (style == Section::Summary || style == Section::Accessors) {
3931 // virtual is prepended, if needed.
3932 QString synopsis;
3933 if (func->isFinal())
3934 synopsis += QStringLiteral(" final");
3935 if (func->isOverride())
3936 synopsis += QStringLiteral(" override");
3937 if (func->isPureVirtual())
3938 synopsis += QStringLiteral(" = 0");
3939 if (func->isRef())
3940 synopsis += QStringLiteral(" &");
3941 else if (func->isRefRef())
3942 synopsis += QStringLiteral(" &&");
3943 m_writer->writeCharacters(synopsis);
3944 } else if (style == Section::AllMembers) {
3945 if (!func->returnType().isEmpty() && func->returnType() != "void") {
3946 m_writer->writeCharacters(QStringLiteral(" : "));
3947 typified(func->returnTypeString(), relative, false, generateType);
3948 }
3949 } else {
3950 QString synopsis;
3951 if (func->isRef())
3952 synopsis += QStringLiteral(" &");
3953 else if (func->isRefRef())
3954 synopsis += QStringLiteral(" &&");
3955 if (const auto &req = func->trailingRequiresClause(); req && !req->isEmpty())
3956 synopsis += " requires " + *req;
3957 m_writer->writeCharacters(synopsis);
3958 }
3959 } break;
3960 case NodeType::Enum: {
3961 const auto enume = static_cast<const EnumNode *>(node);
3962 if (!enume->isAnonymous()) {
3963 m_writer->writeCharacters("enum "_L1);
3964 m_writer->writeCharacters(namePrefix);
3965 generateSynopsisName(node, relative, generateNameLink);
3966 } else if (generateNameLink) {
3967 m_writer->writeStartElement(dbNamespace, "emphasis");
3968 m_writer->writeAttribute("role", "bold");
3969 generateSimpleLink(linkForNode(node, relative), "enum");
3970 m_writer->writeEndElement(); // emphasis
3971 } else {
3972 m_writer->writeCharacters("enum"_L1);
3973 }
3974
3975 QString synopsis;
3976 if (style == Section::Summary) {
3977 synopsis += " { ";
3978
3979 QStringList documentedItems = enume->doc().enumItemNames();
3980 if (documentedItems.isEmpty()) {
3981 const auto &enumItems = enume->items();
3982 for (const auto &item : enumItems)
3983 documentedItems << item.name();
3984 }
3985 const QStringList omitItems = enume->doc().omitEnumItemNames();
3986 for (const auto &item : omitItems)
3987 documentedItems.removeAll(item);
3988
3989 if (documentedItems.size() > MaxEnumValues) {
3990 // Take the last element and keep it safe, then elide the surplus.
3991 const QString last = documentedItems.last();
3992 documentedItems = documentedItems.mid(0, MaxEnumValues - 1);
3993 documentedItems += "&#x2026;"; // Ellipsis: in HTML, &hellip;.
3994 documentedItems += last;
3995 }
3996 synopsis += documentedItems.join(QLatin1String(", "));
3997
3998 if (!documentedItems.isEmpty())
3999 synopsis += QLatin1Char(' ');
4000 synopsis += QLatin1Char('}');
4001 }
4002 m_writer->writeCharacters(synopsis);
4003 } break;
4004 case NodeType::TypeAlias: {
4005 if (style == Section::Details) {
4006 if (auto templateDecl = node->templateDecl()) {
4007 if (templateDecl->parameters.size() > QDoc::MultilineTemplateParamThreshold)
4008 m_writer->writeCharacters(templateDecl->to_qstring_multiline() + QLatin1Char('\n'));
4009 else
4010 m_writer->writeCharacters(templateDecl->to_qstring() + QLatin1Char(' '));
4011 }
4012 }
4013 m_writer->writeCharacters(namePrefix);
4014 generateSynopsisName(node, relative, generateNameLink);
4015 } break;
4016 case NodeType::Typedef: {
4017 if (static_cast<const TypedefNode *>(node)->associatedEnum())
4018 m_writer->writeCharacters("flags ");
4019 m_writer->writeCharacters(namePrefix);
4020 generateSynopsisName(node, relative, generateNameLink);
4021 } break;
4022 case NodeType::Property: {
4023 const auto property = static_cast<const PropertyNode *>(node);
4024 m_writer->writeCharacters(namePrefix);
4025 generateSynopsisName(node, relative, generateNameLink);
4026 m_writer->writeCharacters(" : ");
4027 typified(property->qualifiedDataType(), relative, false, generateType);
4028 } break;
4029 case NodeType::Variable: {
4030 const auto variable = static_cast<const VariableNode *>(node);
4031 if (style == Section::AllMembers) {
4032 generateSynopsisName(node, relative, generateNameLink);
4033 m_writer->writeCharacters(" : ");
4034 typified(variable->dataType(), relative, false, generateType);
4035 } else {
4036 typified(variable->leftType(), relative, false, generateType);
4037 m_writer->writeCharacters(" ");
4038 m_writer->writeCharacters(namePrefix);
4039 generateSynopsisName(node, relative, generateNameLink);
4040 m_writer->writeCharacters(variable->rightType());
4041 }
4042 } break;
4043 default:
4044 m_writer->writeCharacters(namePrefix);
4045 generateSynopsisName(node, relative, generateNameLink);
4046 }
4047}
4048
4049void DocBookGenerator::generateEnumValue(const QString &enumValue, const Node *relative)
4050{
4051 // From CppCodeMarker::markedUpEnumValue, simplifications from Generator::plainCode (removing
4052 // <@op>). With respect to CppCodeMarker::markedUpEnumValue, the order of generation of parents
4053 // must be reversed so that they are processed in the order
4054 const auto *node = relative->parent();
4055
4056 const NativeEnum *nativeEnum{nullptr};
4057 if (auto *ne_if = dynamic_cast<const NativeEnumInterface *>(relative))
4058 nativeEnum = ne_if->nativeEnum();
4059
4060 if (nativeEnum && nativeEnum->enumNode() && !enumValue.startsWith("%1."_L1.arg(nativeEnum->prefix()))) {
4061 m_writer->writeCharacters("%1.%2"_L1.arg(nativeEnum->prefix(), enumValue));
4062 return;
4063 }
4064
4065 // Respect existing prefixes in \value arguments of \qmlenum topic
4066 if (!relative->isEnumType() || (relative->isEnumType(Genus::QML)
4067 && enumValue.section(' ', 0, 0).contains('.'_L1))) {
4068 m_writer->writeCharacters(enumValue);
4069 return;
4070 }
4071
4072 QList<const Node *> parents;
4073 while (!node->isHeader() && node->parent()) {
4074 parents.prepend(node);
4075 if (node->parent() == relative || node->parent()->name().isEmpty())
4076 break;
4077 node = node->parent();
4078 }
4079 if (static_cast<const EnumNode *>(relative)->isScoped())
4080 parents << relative;
4081
4082 m_writer->writeStartElement(dbNamespace, "code");
4083 for (auto parent : parents) {
4084 generateSynopsisName(parent, relative, true);
4085 m_writer->writeCharacters((relative->genus() == Genus::QML) ? "."_L1 : "::"_L1);
4086 }
4087
4088 m_writer->writeCharacters(enumValue);
4089 m_writer->writeEndElement(); // code
4090}
4091
4092/*!
4093 Generates an addendum note of type \a type for \a node. \a marker
4094 is unused in this generator.
4095*/
4097 AdmonitionPrefix prefix)
4098{
4099 Q_UNUSED(marker)
4100 Q_ASSERT(node && !node->name().isEmpty());
4101
4102 switch (prefix) {
4104 break;
4106 m_writer->writeStartElement(dbNamespace, "note");
4107 newLine();
4108 break;
4109 }
4110 }
4111 switch (type) {
4112 case Invokable:
4113 m_writer->writeStartElement(dbNamespace, "para");
4114 m_writer->writeCharacters(
4115 "This function can be invoked via the meta-object system and from QML. See ");
4116 generateSimpleLink(node->url(), "Q_INVOKABLE");
4117 m_writer->writeCharacters(".");
4118 m_writer->writeEndElement(); // para
4119 newLine();
4120 break;
4121 case PrivateSignal:
4122 m_writer->writeTextElement(
4123 dbNamespace, "para",
4124 "This is a private signal. It can be used in signal connections but "
4125 "cannot be emitted by the user.");
4126 break;
4127 case QmlSignalHandler:
4128 {
4129 QString handler(node->name());
4130 int prefixLocation = handler.lastIndexOf('.', -2) + 1;
4131 handler[prefixLocation] = handler[prefixLocation].toTitleCase();
4132 handler.insert(prefixLocation, QLatin1String("on"));
4133 m_writer->writeStartElement(dbNamespace, "para");
4134 m_writer->writeCharacters("The corresponding handler is ");
4135 m_writer->writeTextElement(dbNamespace, "code", handler);
4136 m_writer->writeCharacters(".");
4137 m_writer->writeEndElement(); // para
4138 newLine();
4139 break;
4140 }
4142 {
4143 if (!node->isFunction())
4144 return;
4145 const auto *fn = static_cast<const FunctionNode *>(node);
4146 auto nodes = fn->associatedProperties();
4147 if (nodes.isEmpty())
4148 return;
4149 std::sort(nodes.begin(), nodes.end(), Node::nodeNameLessThan);
4150
4151 // Group properties by their role for more concise output
4152 QMap<PropertyNode::FunctionRole, QList<const PropertyNode *>> roleGroups;
4153 for (const auto *n : std::as_const(nodes)) {
4154 const auto *pn = static_cast<const PropertyNode *>(n);
4155 PropertyNode::FunctionRole role = pn->role(fn);
4156 roleGroups[role].append(pn);
4157 }
4158
4159 // Generate text for each role group in an explicit order
4160 static constexpr PropertyNode::FunctionRole roleOrder[] = {
4166 };
4167
4168 for (auto role : roleOrder) {
4169 const auto it = roleGroups.constFind(role);
4170 if (it == roleGroups.cend())
4171 continue;
4172
4173 const auto &properties = it.value();
4174
4175 QString msg;
4176 switch (role) {
4178 msg = QStringLiteral("Getter function");
4179 break;
4181 msg = QStringLiteral("Setter function");
4182 break;
4184 msg = QStringLiteral("Resetter function");
4185 break;
4187 msg = QStringLiteral("Notifier signal");
4188 break;
4190 msg = QStringLiteral("Bindable function");
4191 break;
4192 default:
4193 continue;
4194 }
4195
4196 m_writer->writeStartElement(dbNamespace, "para");
4197 if (properties.size() == 1) {
4198 const auto *pn = properties.first();
4199 m_writer->writeCharacters(msg + " for property ");
4200 generateSimpleLink(linkForNode(pn, nullptr), pn->name());
4201 m_writer->writeCharacters(". ");
4202 } else {
4203 m_writer->writeCharacters(msg + " for properties ");
4204 for (qsizetype i = 0; i < properties.size(); ++i) {
4205 const auto *pn = properties.at(i);
4206 generateSimpleLink(linkForNode(pn, nullptr), pn->name());
4207 m_writer->writeCharacters(Utilities::separator(i, properties.size()));
4208 }
4209 m_writer->writeCharacters(" ");
4210 }
4211 m_writer->writeEndElement(); // para
4212 newLine();
4213 }
4214 break;
4215 }
4216 case BindableProperty:
4217 {
4218 const Node *linkNode;
4219 Atom linkAtom = Atom(Atom::Link, "QProperty");
4220 QString link = getAutoLink(&linkAtom, node, &linkNode);
4221 m_writer->writeStartElement(dbNamespace, "para");
4222 m_writer->writeCharacters("This property supports ");
4223 generateSimpleLink(link, "QProperty");
4224 m_writer->writeCharacters(" bindings.");
4225 m_writer->writeEndElement(); // para
4226 newLine();
4227 break;
4228 }
4229 case OverloadNote: {
4230 const auto *func = static_cast<const FunctionNode *>(node);
4231
4232 // Primary overloads should not display any overload note text
4233 if (func->isPrimaryOverload())
4234 return;
4235
4236 m_writer->writeStartElement(dbNamespace, "para");
4237
4238 if (func->isSignal() || func->isSlot()) {
4239 auto writeDocBookLink = [&](const QString &target, const QString &label) {
4240 const Node *linkNode = nullptr;
4241 const Atom &linkAtom = Atom(Atom::AutoLink, target);
4242 const QString &link = getAutoLink(&linkAtom, node, &linkNode);
4243
4244 m_writer->writeStartElement(dbNamespace, "link");
4245 if (!link.isEmpty() && linkNode) {
4246 m_writer->writeAttribute(xlinkNamespace, "href", link);
4247 } else {
4248 m_writer->writeAttribute(dbNamespace, "linkend", target);
4249 }
4250 m_writer->writeCharacters(label);
4251 m_writer->writeEndElement(); // link
4252 };
4253
4254 const QString &functionType = func->isSignal() ? "signal" : "slot";
4255 const QString &configKey = func->isSignal() ? "overloadedsignalstarget" : "overloadedslotstarget";
4256 const QString &defaultTarget = func->isSignal() ? "connecting-overloaded-signals" : "connecting-overloaded-slots";
4257 const QString &linkTarget = Config::instance().get(configKey).asString(defaultTarget);
4258
4259 m_writer->writeCharacters("This " + functionType + " is overloaded. ");
4260
4261 QString snippet = generateOverloadSnippet(func);
4262 if (!snippet.isEmpty()) {
4263 m_writer->writeCharacters("To connect to this " + functionType + ":");
4264 m_writer->writeEndElement(); // para
4265 newLine();
4266 m_writer->writeStartElement(dbNamespace, "programlisting");
4267 m_writer->writeCharacters(snippet);
4268 m_writer->writeEndElement(); // programlisting
4269 newLine();
4270 // A new para element is needed here because the caller closes it.
4271 // When linkTarget is empty, this creates an empty para, which is
4272 // harmless but slightly verbose.
4273 m_writer->writeStartElement(dbNamespace, "para");
4274 if (!linkTarget.isEmpty()) {
4275 m_writer->writeCharacters("For more examples and approaches, see ");
4276 writeDocBookLink(linkTarget, "connecting to overloaded " + functionType + "s");
4277 m_writer->writeCharacters(".");
4278 }
4279 } else if (!linkTarget.isEmpty()) {
4280 m_writer->writeCharacters("For more examples and approaches, see ");
4281 writeDocBookLink(linkTarget, "connecting to overloaded " + functionType + "s");
4282 m_writer->writeCharacters(".");
4283 }
4284 } else {
4285 // Original behavior for regular overloaded functions
4286 const auto &args = node->doc().overloadList();
4287 if (args.first().first.isEmpty()) {
4288 m_writer->writeCharacters("This is an overloaded function.");
4289 } else {
4290 QString target = args.first().first;
4291 // If the target is not fully qualified and we have a parent class context,
4292 // attempt to qualify it to improve link resolution
4293 if (!target.contains("::")) {
4294 const auto *parent = node->parent();
4295 if (parent && (parent->isClassNode() || parent->isNamespace())) {
4296 target = parent->name() + "::" + target;
4297 }
4298 }
4299 m_writer->writeCharacters("This function overloads ");
4300 // Use the same approach as AutoLink resolution in other generators
4301 const Node *linkNode = nullptr;
4302 Atom linkAtom = Atom(Atom::AutoLink, target);
4303 QString link = getAutoLink(&linkAtom, node, &linkNode);
4304 if (!link.isEmpty() && linkNode)
4305 generateSimpleLink(link, target);
4306 else
4307 m_writer->writeCharacters(target);
4308 m_writer->writeCharacters(".");
4309 }
4310 }
4311
4312 m_writer->writeEndElement(); // para
4313 newLine();
4314 break;
4315 }
4316 default:
4317 break;
4318 }
4319
4320 if (prefix == AdmonitionPrefix::Note) {
4321 m_writer->writeEndElement(); // note
4322 newLine();
4323 }
4324}
4325
4326void DocBookGenerator::generateDetailedMember(const Node *node, const PageNode *relative)
4327{
4328 // From HtmlGenerator::generateDetailedMember.
4329 bool closeSupplementarySection = false;
4330
4331 if (node->isSharedCommentNode()) {
4332 const auto *scn = reinterpret_cast<const SharedCommentNode *>(node);
4333 const QList<Node *> &collective = scn->collective();
4334
4335 bool firstFunction = true;
4336 for (const auto *sharedNode : collective) {
4337 if (firstFunction) {
4338 startSectionBegin(sharedNode);
4339 } else {
4340 m_writer->writeStartElement(dbNamespace, "bridgehead");
4341 m_writer->writeAttribute("renderas", "sect2");
4342 writeXmlId(sharedNode);
4343 }
4344 if (m_useITS)
4345 m_writer->writeAttribute(itsNamespace, "translate", "no");
4346
4347 generateSynopsis(sharedNode, relative, Section::Details);
4348
4349 if (firstFunction) {
4350 startSectionEnd();
4351 firstFunction = false;
4352 } else {
4353 m_writer->writeEndElement(); // bridgehead
4354 newLine();
4355 }
4356 }
4357 } else {
4358 const EnumNode *etn;
4359 if (node->isEnumType(Genus::CPP) && (etn = static_cast<const EnumNode *>(node))->flagsType()) {
4360 startSectionBegin(node);
4361 if (m_useITS)
4362 m_writer->writeAttribute(itsNamespace, "translate", "no");
4363 generateSynopsis(etn, relative, Section::Details);
4364 startSectionEnd();
4365
4366 m_writer->writeStartElement(dbNamespace, "bridgehead");
4367 m_writer->writeAttribute("renderas", "sect2");
4368 generateSynopsis(etn->flagsType(), relative, Section::Details);
4369 m_writer->writeEndElement(); // bridgehead
4370 newLine();
4371 } else {
4372 startSectionBegin(node);
4373 if (m_useITS)
4374 m_writer->writeAttribute(itsNamespace, "translate", "no");
4375 generateSynopsis(node, relative, Section::Details);
4376 startSectionEnd();
4377 }
4378 }
4379 Q_ASSERT(m_hasSection);
4380
4382
4383 generateStatus(node);
4384 generateBody(node);
4385
4386 // If the body ends with a section, the rest of the description must be wrapped in a section too.
4388 closeSupplementarySection = true;
4389 startSection("", "Notes");
4390 }
4391
4392 if (node->isFunction()) {
4393 const auto *func = static_cast<const FunctionNode *>(node);
4394 if (func->hasOverloads() && (func->isSignal() || func->isSlot()))
4396 }
4399 generateSince(node);
4400
4401 if (node->isProperty()) {
4402 const auto property = static_cast<const PropertyNode *>(node);
4403 if (property->propertyType() == PropertyNode::PropertyType::StandardProperty) {
4404 Section section("", "", "", "", Section::Accessors);
4405
4406 section.appendMembers(property->getters().toVector());
4407 section.appendMembers(property->setters().toVector());
4408 section.appendMembers(property->resetters().toVector());
4409
4410 if (!section.members().isEmpty()) {
4411 m_writer->writeStartElement(dbNamespace, "para");
4412 newLine();
4413 m_writer->writeStartElement(dbNamespace, "emphasis");
4414 m_writer->writeAttribute("role", "bold");
4415 m_writer->writeCharacters("Access functions:");
4416 newLine();
4417 m_writer->writeEndElement(); // emphasis
4418 newLine();
4419 m_writer->writeEndElement(); // para
4420 newLine();
4421 generateSectionList(section, node);
4422 }
4423
4424 Section notifiers("", "", "", "", Section::Accessors);
4425 notifiers.appendMembers(property->notifiers().toVector());
4426
4427 if (!notifiers.members().isEmpty()) {
4428 m_writer->writeStartElement(dbNamespace, "para");
4429 newLine();
4430 m_writer->writeStartElement(dbNamespace, "emphasis");
4431 m_writer->writeAttribute("role", "bold");
4432 m_writer->writeCharacters("Notifier signal:");
4433 newLine();
4434 m_writer->writeEndElement(); // emphasis
4435 newLine();
4436 m_writer->writeEndElement(); // para
4437 newLine();
4438 generateSectionList(notifiers, node);
4439 }
4440 }
4441 } else if (node->isEnumType(Genus::CPP)) {
4442 const auto en = static_cast<const EnumNode *>(node);
4443
4444 if (m_qflagsHref.isEmpty()) {
4445 Node *qflags = m_qdb->findClassNode(QStringList("QFlags"));
4446 if (qflags)
4447 m_qflagsHref = linkForNode(qflags, nullptr);
4448 }
4449
4450 if (en->flagsType()) {
4451 m_writer->writeStartElement(dbNamespace, "para");
4452 m_writer->writeCharacters("The ");
4453 m_writer->writeStartElement(dbNamespace, "code");
4454 m_writer->writeCharacters(en->flagsType()->name());
4455 m_writer->writeEndElement(); // code
4456 m_writer->writeCharacters(" type is a typedef for ");
4457 m_writer->writeStartElement(dbNamespace, "code");
4458 generateSimpleLink(m_qflagsHref, "QFlags");
4459 m_writer->writeCharacters("<" + en->name() + ">. ");
4460 m_writer->writeEndElement(); // code
4461 m_writer->writeCharacters("It stores an OR combination of ");
4462 m_writer->writeStartElement(dbNamespace, "code");
4463 m_writer->writeCharacters(en->name());
4464 m_writer->writeEndElement(); // code
4465 m_writer->writeCharacters(" values.");
4466 m_writer->writeEndElement(); // para
4467 newLine();
4468 }
4469 }
4470
4471 if (closeSupplementarySection)
4472 endSection();
4473
4474 // The list of linked pages is always in its own section.
4476
4477 // Close the section for this member.
4478 endSection(); // section
4479}
4480
4481void DocBookGenerator::generateSectionList(const Section &section, const Node *relative,
4482 bool useObsoleteMembers)
4483{
4484 // From HtmlGenerator::generateSectionList, just generating a list (not tables).
4485 const NodeVector &members =
4486 (useObsoleteMembers ? section.obsoleteMembers() : section.members());
4487 if (!members.isEmpty()) {
4488 bool hasPrivateSignals = false;
4489 bool isInvokable = false;
4490
4491 m_writer->writeStartElement(dbNamespace, "itemizedlist");
4492 if (m_useITS)
4493 m_writer->writeAttribute(itsNamespace, "translate", "no");
4494 newLine();
4495
4496 NodeVector::ConstIterator m = members.constBegin();
4497 while (m != members.constEnd()) {
4498 if ((*m)->access() == Access::Private) {
4499 ++m;
4500 continue;
4501 }
4502
4503 m_writer->writeStartElement(dbNamespace, "listitem");
4504 newLine();
4505 m_writer->writeStartElement(dbNamespace, "para");
4506
4507 // prefix no more needed.
4508 generateSynopsis(*m, relative, section.style());
4509 if ((*m)->isFunction()) {
4510 const auto fn = static_cast<const FunctionNode *>(*m);
4511 if (fn->isPrivateSignal())
4512 hasPrivateSignals = true;
4513 else if (fn->isInvokable())
4514 isInvokable = true;
4515 }
4516
4517 m_writer->writeEndElement(); // para
4518 newLine();
4519 m_writer->writeEndElement(); // listitem
4520 newLine();
4521
4522 ++m;
4523 }
4524
4525 m_writer->writeEndElement(); // itemizedlist
4526 newLine();
4527
4528 if (hasPrivateSignals)
4530 if (isInvokable)
4532 }
4533
4534 if (!useObsoleteMembers && section.style() == Section::Summary
4535 && !section.inheritedMembers().isEmpty()) {
4536 m_writer->writeStartElement(dbNamespace, "itemizedlist");
4537 if (m_useITS)
4538 m_writer->writeAttribute(itsNamespace, "translate", "no");
4539 newLine();
4540
4541 generateSectionInheritedList(section, relative);
4542
4543 m_writer->writeEndElement(); // itemizedlist
4544 newLine();
4545 }
4546}
4547
4548void DocBookGenerator::generateSectionInheritedList(const Section &section, const Node *relative)
4549{
4550 // From HtmlGenerator::generateSectionInheritedList.
4551 QList<std::pair<Aggregate *, int>>::ConstIterator p = section.inheritedMembers().constBegin();
4552 while (p != section.inheritedMembers().constEnd()) {
4553 m_writer->writeStartElement(dbNamespace, "listitem");
4554 m_writer->writeCharacters(QString::number((*p).second) + u' ');
4555 if ((*p).second == 1)
4556 m_writer->writeCharacters(section.singular());
4557 else
4558 m_writer->writeCharacters(section.plural());
4559 m_writer->writeCharacters(" inherited from ");
4560 generateSimpleLink(fileName((*p).first) + '#'
4561 + Generator::cleanRef(section.title().toLower()),
4562 (*p).first->plainFullName(relative));
4563 ++p;
4564 }
4565}
4566
4567/*!
4568 Generate the DocBook page for an entity that doesn't map
4569 to any underlying parsable C++ or QML element.
4570 */
4572{
4573 // From HtmlGenerator::generatePageNode, remove anything related to TOCs.
4574 Q_ASSERT(m_writer == nullptr);
4575 m_writer = startDocument(pn);
4576
4577 generateHeader(pn->doc().title(), pn->subtitle(), pn);
4578 generateBody(pn);
4579 generateAlsoList(pn);
4581
4582 endDocument();
4583}
4584
4585/*!
4586 Generate the DocBook page for a QML type. \qcn is the QML type.
4587 */
4589{
4590 // From HtmlGenerator::generateQmlTypePage.
4591 // Start producing the DocBook file.
4592 Q_ASSERT(m_writer == nullptr);
4593 m_writer = startDocument(qcn);
4594
4596 QString title = qcn->name();
4597 if (qcn->isQmlBasicType())
4598 title.append(" QML Value Type");
4599 else
4600 title.append(" QML Type");
4601
4602 if (qcn->isSingleton())
4603 title.append(" (Singleton)");
4604 // TODO: for ITS attribute, only apply translate="no" on qcn->fullTitle(),
4605 // not its suffix (which should be translated). generateHeader doesn't
4606 // allow this kind of input, the title isn't supposed to be structured.
4607 // Ideally, do the same in HTML.
4608
4609 generateHeader(title, qcn->subtitle(), qcn);
4611 generateStatus(qcn);
4612
4613 if (qcn->isSingleton()) {
4614 m_writer->writeStartElement(dbNamespace, "note");
4615 m_writer->writeStartElement(dbNamespace, "para");
4616 m_writer->writeStartElement(dbNamespace, "emphasis");
4617 m_writer->writeAttribute("role", "bold");
4618 m_writer->writeCharacters("Note: ");
4619 m_writer->writeEndElement(); // emphasis
4620 m_writer->writeCharacters("This type is a QML singleton. "
4621 "There is only one instance of this type in the QML engine.");
4622 m_writer->writeEndElement(); // para
4623 m_writer->writeEndElement(); // note
4624 }
4625
4626 startSection("details", "Detailed Description");
4627 generateBody(qcn);
4628
4629 generateAlsoList(qcn);
4630
4631 endSection();
4632
4633 Sections sections(qcn);
4634 for (const auto &section : sections.stdQmlTypeDetailsSections()) {
4635 if (!section.isEmpty()) {
4636 startSection(section.title().toLower(), section.title());
4637
4638 for (const auto &member : section.members())
4639 generateDetailedQmlMember(member, qcn);
4640
4641 endSection();
4642 }
4643 }
4644
4645 generateObsoleteQmlMembers(sections);
4646
4649
4650 endDocument();
4651}
4652
4653/*!
4654 Outputs the DocBook detailed documentation for a section
4655 on a QML element reference page.
4656 */
4657void DocBookGenerator::generateDetailedQmlMember(Node *node, const Aggregate *relative)
4658{
4659 // From HtmlGenerator::generateDetailedQmlMember, with elements from
4660 // CppCodeMarker::markedUpQmlItem and HtmlGenerator::generateQmlItem.
4661 auto getQmlPropertyTitle = [&](QmlPropertyNode *n) {
4662 QString title{CodeMarker::extraSynopsis(n, Section::Details)};
4663 if (!title.isEmpty())
4664 title += ' '_L1;
4665 // Finalise generation of name, as per CppCodeMarker::markedUpQmlItem.
4666 if (n->isAttached())
4667 title += n->element() + QLatin1Char('.');
4668 title += n->name() + " : " + n->dataType();
4669
4670 return title;
4671 };
4672
4673 auto generateQmlMethodTitle = [&](Node *node) {
4674 generateSynopsis(node, relative, Section::Details);
4675 };
4676
4677 if (node->isPropertyGroup()) {
4678 const auto *scn = static_cast<const SharedCommentNode *>(node);
4679
4680 QString heading;
4681 if (!scn->name().isEmpty())
4682 heading = scn->name() + " group";
4683 else
4684 heading = node->name();
4685 startSection(scn, heading);
4686 // This last call creates a title for this section. In other words,
4687 // titles are forbidden for the rest of the section, hence the use of
4688 // bridgehead.
4689
4690 const QList<Node *> sharedNodes = scn->collective();
4691 for (const auto &sharedNode : sharedNodes) {
4692 if (sharedNode->isQmlProperty()) {
4693 auto *qpn = static_cast<QmlPropertyNode *>(sharedNode);
4694
4695 m_writer->writeStartElement(dbNamespace, "bridgehead");
4696 m_writer->writeAttribute("renderas", "sect2");
4697 writeXmlId(qpn);
4698 m_writer->writeCharacters(getQmlPropertyTitle(qpn));
4699 m_writer->writeEndElement(); // bridgehead
4700 newLine();
4701
4702 generateDocBookSynopsis(qpn);
4703 }
4704 }
4705 } else if (node->isQmlProperty()) {
4706 auto qpn = static_cast<QmlPropertyNode *>(node);
4707 startSection(qpn, getQmlPropertyTitle(qpn));
4709 } else if (node->isSharedCommentNode()) {
4710 const auto scn = reinterpret_cast<const SharedCommentNode *>(node);
4711 const QList<Node *> &sharedNodes = scn->collective();
4712
4713 // In the section, generate a title for the first node, then bridgeheads for
4714 // the next ones.
4715 int i = 0;
4716 for (const auto &sharedNode : sharedNodes) {
4717 // Ignore this element if there is nothing to generate.
4718 if (!sharedNode->isFunction(Genus::QML) && !sharedNode->isQmlProperty()) {
4719 continue;
4720 }
4721
4722 // Write the tag containing the title.
4723 if (i == 0) {
4724 startSectionBegin(sharedNode);
4725 } else {
4726 m_writer->writeStartElement(dbNamespace, "bridgehead");
4727 m_writer->writeAttribute("renderas", "sect2");
4728 }
4729
4730 // Write the title.
4731 if (sharedNode->isFunction(Genus::QML))
4732 generateQmlMethodTitle(sharedNode);
4733 else if (sharedNode->isQmlProperty())
4734 m_writer->writeCharacters(
4735 getQmlPropertyTitle(static_cast<QmlPropertyNode *>(sharedNode)));
4736
4737 // Complete the title and the synopsis.
4738 if (i == 0)
4739 startSectionEnd();
4740 else
4741 m_writer->writeEndElement(); // bridgehead
4742 generateDocBookSynopsis(sharedNode);
4743 ++i;
4744 }
4745
4746 // If the list is empty, still generate a section.
4747 if (i == 0) {
4748 startSectionBegin(refForNode(node));
4749
4750 if (node->isFunction(Genus::QML))
4751 generateQmlMethodTitle(node);
4752 else if (node->isQmlProperty())
4753 m_writer->writeCharacters(
4754 getQmlPropertyTitle(static_cast<QmlPropertyNode *>(node)));
4755
4756 startSectionEnd();
4757 }
4758 } else if (node->isEnumType(Genus::QML)) {
4759 startSectionBegin(node);
4761 startSectionEnd();
4762 } else { // assume the node is a method/signal handler
4763 startSectionBegin(node);
4764 generateQmlMethodTitle(node);
4765 startSectionEnd();
4766 }
4767
4768 generateStatus(node);
4769 generateBody(node);
4771 generateSince(node);
4773
4774 endSection();
4775}
4776
4777/*!
4778 Recursive writing of DocBook files from the root \a node.
4779 */
4781{
4782 // Mainly from Generator::generateDocumentation, with parts from
4783 // Generator::generateDocumentation and WebXMLGenerator::generateDocumentation.
4784 // Don't generate nodes that are already processed, or if they're not
4785 // supposed to generate output, ie. external, index or images nodes.
4786 if (!node->url().isNull())
4787 return;
4788 if (node->isIndexNode())
4789 return;
4790 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
4791 const NodeContext context = node->createContext();
4792 if (!InclusionFilter::isIncluded(policy, context))
4793 return;
4794 if (node->isExternalPage())
4795 return;
4796
4797 if (node->parent()) {
4798 if (node->isCollectionNode()) {
4799 /*
4800 A collection node collects: groups, C++ modules, or QML
4801 modules. Testing for a CollectionNode must be done
4802 before testing for a TextPageNode because a
4803 CollectionNode is a PageNode at this point.
4804
4805 Don't output an HTML page for the collection node unless
4806 the \group, \module, or \qmlmodule command was actually
4807 seen by qdoc in the qdoc comment for the node.
4808
4809 A key prerequisite in this case is the call to
4810 mergeCollections(cn). We must determine whether this
4811 group, module, or QML module has members in other
4812 modules. We know at this point that cn's members list
4813 contains only members in the current module. Therefore,
4814 before outputting the page for cn, we must search for
4815 members of cn in the other modules and add them to the
4816 members list.
4817 */
4818 auto cn = static_cast<CollectionNode *>(node);
4819 if (cn->wasSeen()) {
4820 m_qdb->mergeCollections(cn);
4821 generateCollectionNode(cn);
4822 } else if (cn->isGenericCollection()) {
4823 // Currently used only for the module's related orphans page
4824 // but can be generalized for other kinds of collections if
4825 // other use cases pop up.
4826 generateGenericCollectionPage(cn);
4827 }
4828 } else if (node->isTextPageNode()) { // Pages.
4829 generatePageNode(static_cast<PageNode *>(node));
4830 } else if (node->isAggregate()) { // Aggregates.
4831 if ((node->isClassNode() || node->isHeader() || node->isNamespace())
4832 && node->docMustBeGenerated()) {
4833 generateCppReferencePage(static_cast<Aggregate *>(node));
4834 } else if (node->isQmlType()) { // Includes QML value types
4835 generateQmlTypePage(static_cast<QmlTypeNode *>(node));
4836 } else if (node->isProxyNode()) {
4837 generateProxyPage(static_cast<Aggregate *>(node));
4838 }
4839 }
4840 }
4841
4842 if (node->isAggregate()) {
4843 auto *aggregate = static_cast<Aggregate *>(node);
4844 for (auto c : aggregate->childNodes()) {
4845 if (node->isPageNode())
4846 generateDocumentation(c);
4847 }
4848 }
4849}
4850
4852{
4853 // Adapted from HtmlGenerator::generateProxyPage.
4854 Q_ASSERT(aggregate->isProxyNode());
4855
4856 // Start producing the DocBook file.
4857 Q_ASSERT(m_writer == nullptr);
4858 m_writer = startDocument(aggregate);
4859
4860 // Info container.
4861 generateHeader(aggregate->plainFullName(), "", aggregate);
4862
4863 // No element synopsis.
4864
4865 // Actual content.
4866 if (!aggregate->doc().isEmpty()) {
4867 startSection("details", "Detailed Description");
4868
4869 generateBody(aggregate);
4870 generateAlsoList(aggregate);
4871
4872 endSection();
4873 }
4874
4875 Sections sections(aggregate);
4876 SectionVector *detailsSections = &sections.stdDetailsSections();
4877
4878 for (const auto &section : std::as_const(*detailsSections)) {
4879 if (section.isEmpty())
4880 continue;
4881
4882 startSection(section.title().toLower(), section.title());
4883
4884 const QList<Node *> &members = section.members();
4885 for (const auto &member : members) {
4886 if (!member->isClassNode()) {
4887 generateDetailedMember(member, aggregate);
4888 } else {
4889 startSectionBegin();
4890 generateFullName(member, aggregate);
4891 startSectionEnd();
4892
4893 generateBrief(member);
4894 endSection();
4895 }
4896 }
4897
4898 endSection();
4899 }
4900
4902
4903 endDocument();
4904}
4905
4906/*!
4907 Generate the HTML page for a group, module, or QML module.
4908 */
4910{
4911 // Adapted from HtmlGenerator::generateCollectionNode.
4912 // Start producing the DocBook file.
4913 Q_ASSERT(m_writer == nullptr);
4914 m_writer = startDocument(cn);
4915
4916 // Info container.
4917 generateHeader(cn->doc().title(), cn->subtitle(), cn);
4918
4919 // Element synopsis.
4921
4922 // Generate brief for C++ modules, status for all modules.
4923 if (cn->genus() != Genus::DOC && cn->genus() != Genus::DontCare) {
4924 if (cn->isModule())
4925 generateBrief(cn);
4926 generateStatus(cn);
4927 generateSince(cn);
4928 }
4929
4930 // Actual content.
4931 if (cn->isModule()) {
4932 if (!cn->noAutoList()) {
4934 if (!nmm.isEmpty()) {
4935 startSection("namespaces", "Namespaces");
4936 generateAnnotatedList(cn, nmm.values(), "namespaces");
4937 endSection();
4938 }
4939 nmm = cn->getMembers([](const Node *n){ return n->isClassNode(); });
4940 if (!nmm.isEmpty()) {
4941 startSection("classes", "Classes");
4942 generateAnnotatedList(cn, nmm.values(), "classes");
4943 endSection();
4944 }
4945 }
4946 }
4947
4948 bool generatedTitle = false;
4949 if (cn->isModule() && !cn->doc().briefText().isEmpty()) {
4950 startSection("details", "Detailed Description");
4951 generatedTitle = true;
4952 }
4953 // The anchor is only needed if the node has a body.
4954 else if (
4955 // generateBody generates something.
4956 !cn->doc().body().isEmpty() ||
4957 // generateAlsoList generates something.
4958 !cn->doc().alsoList().empty() ||
4959 // generateAnnotatedList generates something.
4960 (!cn->noAutoList() && (cn->isGroup() || cn->isQmlModule()))) {
4961 writeAnchor("details");
4962 }
4963
4964 generateBody(cn);
4965 generateAlsoList(cn);
4966
4967 if (!cn->noAutoList() && (cn->isGroup() || cn->isQmlModule()))
4968 generateAnnotatedList(cn, cn->members(), "members", AutoSection);
4969
4970 if (generatedTitle)
4971 endSection();
4972
4974
4975 endDocument();
4976}
4977
4978/*!
4979 Generate the HTML page for a generic collection. This is usually
4980 a collection of C++ elements that are related to an element in
4981 a different module.
4982 */
4984{
4985 // Adapted from HtmlGenerator::generateGenericCollectionPage.
4986 // TODO: factor out this code to generate a file name.
4987 QString name = cn->name().toLower();
4988 name.replace(QChar(' '), QString("-"));
4989 QString filename = cn->tree()->physicalModuleName() + "-" + name + "." + fileExtension();
4990
4991 // Start producing the DocBook file.
4992 Q_ASSERT(m_writer == nullptr);
4993 m_writer = startGenericDocument(cn, filename);
4994
4995 // Info container.
4996 generateHeader(cn->fullTitle(), cn->subtitle(), cn);
4997
4998 // Element synopsis.
5000
5001 // Actual content.
5002 m_writer->writeStartElement(dbNamespace, "para");
5003 m_writer->writeCharacters("Each function or type documented here is related to a class or "
5004 "namespace that is documented in a different module. The reference "
5005 "page for that class or namespace will link to the function or type "
5006 "on this page.");
5007 m_writer->writeEndElement(); // para
5008
5009 const CollectionNode *cnc = cn;
5010 const QList<Node *> members = cn->members();
5011 for (const auto &member : members)
5012 generateDetailedMember(member, cnc);
5013
5015
5016 endDocument();
5017}
5018
5019void DocBookGenerator::generateFullName(const Node *node, const Node *relative)
5020{
5021 Q_ASSERT(node);
5022 Q_ASSERT(relative);
5023
5024 // From Generator::appendFullName.
5025 m_writer->writeStartElement(dbNamespace, "link");
5026 m_writer->writeAttribute(xlinkNamespace, "href", fullDocumentLocation(node));
5027 m_writer->writeAttribute(xlinkNamespace, "role", targetType(node));
5028 m_writer->writeCharacters(node->fullName(relative));
5029 m_writer->writeEndElement(); // link
5030}
5031
5032void DocBookGenerator::generateFullName(const Node *apparentNode, const QString &fullName,
5033 const Node *actualNode)
5034{
5035 Q_ASSERT(apparentNode);
5036 Q_ASSERT(actualNode);
5037
5038 // From Generator::appendFullName.
5039 m_writer->writeStartElement(dbNamespace, "link");
5040 m_writer->writeAttribute(xlinkNamespace, "href", fullDocumentLocation(actualNode));
5041 m_writer->writeAttribute("role", targetType(actualNode));
5042 m_writer->writeCharacters(fullName);
5043 m_writer->writeEndElement(); // link
5044}
5045
5046QT_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:31
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)
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() 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:56
void appendMembers(const NodeVector &nv)
Definition sections.h:57
@ 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:49
Style style() const
Definition sections.h:44
A class for creating vectors of collections for documentation.
Definition sections.h:82
Aggregate * aggregate() const
Definition sections.h:184
Sections(Aggregate *aggregate)
This constructor builds the vectors of sections based on the type of the aggregate node.
Definition sections.cpp:367
SectionVector & stdDetailsSections()
Definition sections.h:159
SectionVector & stdCppClassDetailsSections()
Definition sections.h:161
bool hasObsoleteMembers(SectionPtrVector *summary_spv, SectionPtrVector *details_spv) const
Returns true if any sections in this object contain obsolete members.
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:376
#define CONFIG_EXAMPLES
Definition config.h:380
#define CONFIG_URL
Definition config.h:445
#define CONFIG_PRELIMINARY
Definition config.h:421
#define CONFIG_DESCRIPTION
Definition config.h:375
#define CONFIG_PROJECT
Definition config.h:423
#define CONFIG_EXAMPLESINSTALLPATH
Definition config.h:381
#define CONFIG_NATURALLANGUAGE
Definition config.h:414
#define CONFIG_PRODUCTNAME
Definition config.h:422
#define CONFIG_BUILDVERSION
Definition config.h:367
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:79
QList< Section > SectionVector
Definition sections.h:78
@ 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.