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