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