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