summaryrefslogtreecommitdiffstats
path: root/src/qdoc/docbookgenerator.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qdoc/docbookgenerator.cpp')
-rw-r--r--src/qdoc/docbookgenerator.cpp4216
1 files changed, 4216 insertions, 0 deletions
diff --git a/src/qdoc/docbookgenerator.cpp b/src/qdoc/docbookgenerator.cpp
new file mode 100644
index 000000000..f6476c9ec
--- /dev/null
+++ b/src/qdoc/docbookgenerator.cpp
@@ -0,0 +1,4216 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 Thibaut Cuvelier
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <cctype>
+#include <qlist.h>
+#include <qiterator.h>
+#include <qtextcodec.h>
+#include <quuid.h>
+#include <qurl.h>
+#include <qmap.h>
+#include <QtCore/qversionnumber.h>
+
+#include "codemarker.h"
+#include "config.h"
+#include "generator.h"
+#include "docbookgenerator.h"
+#include "node.h"
+#include "quoter.h"
+#include "qdocdatabase.h"
+#include "separator.h"
+
+QT_BEGIN_NAMESPACE
+
+static const char dbNamespace[] = "http://docbook.org/ns/docbook";
+static const char xlinkNamespace[] = "http://www.w3.org/1999/xlink";
+
+inline void DocBookGenerator::newLine()
+{
+ writer->writeCharacters("\n");
+}
+
+void DocBookGenerator::startSectionBegin()
+{
+ writer->writeStartElement(dbNamespace, "section");
+ newLine();
+ writer->writeStartElement(dbNamespace, "title");
+}
+
+void DocBookGenerator::startSectionBegin(const QString &id)
+{
+ writer->writeStartElement(dbNamespace, "section");
+ writer->writeAttribute("xml:id", id);
+ newLine();
+ writer->writeStartElement(dbNamespace, "title");
+}
+
+void DocBookGenerator::startSectionEnd()
+{
+ writer->writeEndElement(); // title
+ newLine();
+}
+
+void DocBookGenerator::startSection(const QString &id, const QString &title)
+{
+ startSectionBegin(id);
+ writer->writeCharacters(title);
+ startSectionEnd();
+}
+
+void DocBookGenerator::endSection()
+{
+ writer->writeEndElement(); // section
+ newLine();
+}
+
+void DocBookGenerator::writeAnchor(const QString &id)
+{
+ writer->writeEmptyElement(dbNamespace, "anchor");
+ writer->writeAttribute("xml:id", id);
+ newLine();
+}
+
+/*!
+ Initializes the DocBook output generator's data structures
+ from the configuration (Config).
+ */
+void DocBookGenerator::initializeGenerator()
+{
+ // Excerpts from HtmlGenerator::initializeGenerator.
+ Generator::initializeGenerator();
+ config = &Config::instance();
+
+ project = config->getString(CONFIG_PROJECT);
+
+ projectDescription = config->getString(CONFIG_DESCRIPTION);
+ if (projectDescription.isEmpty() && !project.isEmpty())
+ projectDescription = project + QLatin1String(" Reference Documentation");
+
+ naturalLanguage = config->getString(CONFIG_NATURALLANGUAGE);
+ if (naturalLanguage.isEmpty())
+ naturalLanguage = QLatin1String("en");
+
+ buildversion = config->getString(CONFIG_BUILDVERSION);
+}
+
+QString DocBookGenerator::format()
+{
+ return QStringLiteral("DocBook");
+}
+
+/*!
+ Returns "xml" for this subclass of Generator.
+ */
+QString DocBookGenerator::fileExtension() const
+{
+ return QStringLiteral("xml");
+}
+
+/*!
+ Generate the documentation for \a relative. i.e. \a relative
+ is the node that represents the entity where a qdoc comment
+ was found, and \a text represents the qdoc comment.
+ */
+bool DocBookGenerator::generateText(const Text &text, const Node *relative, CodeMarker *marker)
+{
+ Q_UNUSED(marker);
+ // From Generator::generateText.
+ if (!text.firstAtom())
+ return false;
+
+ int numAtoms = 0;
+ initializeTextOutput();
+ generateAtomList(text.firstAtom(), relative, true, numAtoms);
+ closeTextSections();
+ return true;
+}
+
+/*!
+ Generate the text for \a atom relatively to \a relative.
+ \a generate indicates if output to \a writer is expected.
+ The number of generated atoms is returned in the argument
+ \a numAtoms. The returned value is the first atom that was not
+ generated.
+ */
+const Atom *DocBookGenerator::generateAtomList(const Atom *atom, const Node *relative,
+ bool generate, int &numAtoms)
+{
+ Q_ASSERT(writer);
+ // From Generator::generateAtomList.
+ while (atom) {
+ switch (atom->type()) {
+ case Atom::FormatIf: {
+ int numAtoms0 = numAtoms;
+ atom = generateAtomList(atom->next(), relative, generate, numAtoms);
+ if (!atom)
+ return nullptr;
+
+ if (atom->type() == Atom::FormatElse) {
+ ++numAtoms;
+ atom = generateAtomList(atom->next(), relative, false, numAtoms);
+ if (!atom)
+ return nullptr;
+ }
+
+ if (atom->type() == Atom::FormatEndif) {
+ if (generate && numAtoms0 == numAtoms) {
+ relative->location().warning(
+ tr("Output format %1 not handled %2").arg(format()).arg(outFileName()));
+ Atom unhandledFormatAtom(Atom::UnhandledFormat, format());
+ generateAtomList(&unhandledFormatAtom, relative, generate, numAtoms);
+ }
+ atom = atom->next();
+ }
+ } break;
+ case Atom::FormatElse:
+ case Atom::FormatEndif:
+ return atom;
+ default:
+ int n = 1;
+ if (generate) {
+ n += generateAtom(atom, relative);
+ numAtoms += n;
+ }
+ while (n-- > 0)
+ atom = atom->next();
+ }
+ }
+ return nullptr;
+}
+
+/*!
+ Generate DocBook from an instance of Atom.
+ */
+int DocBookGenerator::generateAtom(const Atom *atom, const Node *relative, CodeMarker *marker)
+{
+ Q_ASSERT(writer);
+ Q_UNUSED(marker);
+ // From HtmlGenerator::generateAtom, without warning generation.
+ int idx = 0;
+ int skipAhead = 0;
+ static bool inPara = false;
+
+ switch (atom->type()) {
+ case Atom::AutoLink:
+ case Atom::NavAutoLink:
+ if (!inLink && !inContents_ && !inSectionHeading_) {
+ const Node *node = nullptr;
+ QString link = getAutoLink(atom, relative, &node);
+ if (!link.isEmpty() && node && node->status() == Node::Obsolete
+ && relative->parent() != node && !relative->isObsolete()) {
+ link.clear();
+ }
+ if (link.isEmpty()) {
+ writer->writeCharacters(atom->string());
+ } else {
+ beginLink(link, node, relative);
+ generateLink(atom);
+ endLink();
+ }
+ } else {
+ writer->writeCharacters(atom->string());
+ }
+ break;
+ case Atom::BaseName:
+ break;
+ case Atom::BriefLeft:
+ if (!hasBrief(relative)) {
+ skipAhead = skipAtoms(atom, Atom::BriefRight);
+ break;
+ }
+ writer->writeStartElement(dbNamespace, "para");
+ rewritePropertyBrief(atom, relative);
+ break;
+ case Atom::BriefRight:
+ if (hasBrief(relative)) {
+ writer->writeEndElement(); // para
+ newLine();
+ }
+ break;
+ case Atom::C:
+ // This may at one time have been used to mark up C++ code but it is
+ // now widely used to write teletype text. As a result, text marked
+ // with the \c command is not passed to a code marker.
+ writer->writeTextElement(dbNamespace, "code", plainCode(atom->string()));
+ break;
+ case Atom::CaptionLeft:
+ writer->writeStartElement(dbNamespace, "title");
+ break;
+ case Atom::CaptionRight:
+ endLink();
+ writer->writeEndElement(); // title
+ newLine();
+ break;
+ case Atom::Qml:
+ writer->writeStartElement(dbNamespace, "programlisting");
+ writer->writeAttribute("language", "qml");
+ writer->writeCharacters(atom->string());
+ writer->writeEndElement(); // programlisting
+ newLine();
+ break;
+ case Atom::JavaScript:
+ writer->writeStartElement(dbNamespace, "programlisting");
+ writer->writeAttribute("language", "js");
+ writer->writeCharacters(atom->string());
+ writer->writeEndElement(); // programlisting
+ newLine();
+ break;
+ case Atom::CodeNew:
+ writer->writeTextElement(dbNamespace, "para", "you can rewrite it as");
+ newLine();
+ writer->writeStartElement(dbNamespace, "programlisting");
+ writer->writeAttribute("language", "cpp");
+ writer->writeAttribute("role", "new");
+ writer->writeCharacters(atom->string());
+ writer->writeEndElement(); // programlisting
+ newLine();
+ break;
+ case Atom::Code:
+ writer->writeStartElement(dbNamespace, "programlisting");
+ writer->writeAttribute("language", "cpp");
+ writer->writeCharacters(atom->string());
+ writer->writeEndElement(); // programlisting
+ newLine();
+ break;
+ case Atom::CodeOld:
+ writer->writeTextElement(dbNamespace, "para", "For example, if you have code like");
+ newLine();
+ Q_FALLTHROUGH();
+ case Atom::CodeBad:
+ writer->writeStartElement(dbNamespace, "programlisting");
+ writer->writeAttribute("language", "cpp");
+ writer->writeAttribute("role", "bad");
+ writer->writeCharacters(atom->string());
+ writer->writeEndElement(); // programlisting
+ newLine();
+ break;
+ case Atom::DivLeft:
+ case Atom::DivRight:
+ break;
+ case Atom::FootnoteLeft:
+ writer->writeStartElement(dbNamespace, "footnote");
+ newLine();
+ writer->writeStartElement(dbNamespace, "para");
+ break;
+ case Atom::FootnoteRight:
+ writer->writeEndElement(); // para
+ newLine();
+ writer->writeEndElement(); // footnote
+ break;
+ case Atom::FormatElse:
+ case Atom::FormatEndif:
+ case Atom::FormatIf:
+ break;
+ case Atom::FormattingLeft:
+ if (atom->string() == ATOM_FORMATTING_BOLD) {
+ writer->writeStartElement(dbNamespace, "emphasis");
+ writer->writeAttribute("role", "bold");
+ } else if (atom->string() == ATOM_FORMATTING_ITALIC) {
+ writer->writeStartElement(dbNamespace, "emphasis");
+ } else if (atom->string() == ATOM_FORMATTING_UNDERLINE) {
+ writer->writeStartElement(dbNamespace, "emphasis");
+ writer->writeAttribute("role", "underline");
+ } else if (atom->string() == ATOM_FORMATTING_SUBSCRIPT) {
+ writer->writeStartElement(dbNamespace, "sub");
+ } else if (atom->string() == ATOM_FORMATTING_SUPERSCRIPT) {
+ writer->writeStartElement(dbNamespace, "sup");
+ } else if (atom->string() == ATOM_FORMATTING_TELETYPE
+ || atom->string() == ATOM_FORMATTING_PARAMETER) {
+ writer->writeStartElement(dbNamespace, "code");
+
+ if (atom->string() == ATOM_FORMATTING_PARAMETER)
+ writer->writeAttribute("role", "parameter");
+ }
+ break;
+ case Atom::FormattingRight:
+ if (atom->string() == ATOM_FORMATTING_BOLD || atom->string() == ATOM_FORMATTING_ITALIC
+ || atom->string() == ATOM_FORMATTING_UNDERLINE
+ || atom->string() == ATOM_FORMATTING_SUBSCRIPT
+ || atom->string() == ATOM_FORMATTING_SUPERSCRIPT
+ || atom->string() == ATOM_FORMATTING_TELETYPE
+ || atom->string() == ATOM_FORMATTING_PARAMETER) {
+ writer->writeEndElement();
+ }
+ if (atom->string() == ATOM_FORMATTING_LINK)
+ endLink();
+ break;
+ case Atom::AnnotatedList:
+ if (const CollectionNode *cn = qdb_->getCollectionNode(atom->string(), Node::Group))
+ generateList(cn, atom->string());
+ break;
+ case Atom::GeneratedList:
+ if (atom->string() == QLatin1String("annotatedclasses")
+ || atom->string() == QLatin1String("attributions")
+ || atom->string() == QLatin1String("namespaces")) {
+ const NodeMultiMap things = atom->string() == QLatin1String("annotatedclasses")
+ ? qdb_->getCppClasses()
+ : atom->string() == QLatin1String("attributions") ? qdb_->getAttributions()
+ : qdb_->getNamespaces();
+ generateAnnotatedList(relative, things, atom->string());
+ } else if (atom->string() == QLatin1String("annotatedexamples")
+ || atom->string() == QLatin1String("annotatedattributions")) {
+ const NodeMultiMap things = atom->string() == QLatin1String("annotatedexamples")
+ ? qdb_->getAttributions()
+ : qdb_->getExamples();
+ generateAnnotatedLists(relative, things, atom->string());
+ } else if (atom->string() == QLatin1String("classes")
+ || atom->string() == QLatin1String("qmlbasictypes")
+ || atom->string() == QLatin1String("qmltypes")) {
+ const NodeMultiMap things = atom->string() == QLatin1String("classes")
+ ? qdb_->getCppClasses()
+ : atom->string() == QLatin1String("qmlbasictypes") ? qdb_->getQmlBasicTypes()
+ : qdb_->getQmlTypes();
+ generateCompactList(Generic, relative, things, QString(), atom->string());
+ } else if (atom->string().contains("classes ")) {
+ QString rootName = atom->string().mid(atom->string().indexOf("classes") + 7).trimmed();
+ generateCompactList(Generic, relative, qdb_->getCppClasses(), rootName, atom->string());
+ } else if ((idx = atom->string().indexOf(QStringLiteral("bymodule"))) != -1) {
+ QString moduleName = atom->string().mid(idx + 8).trimmed();
+ Node::NodeType type = typeFromString(atom);
+ QDocDatabase *qdb = QDocDatabase::qdocDB();
+ if (const CollectionNode *cn = qdb->getCollectionNode(moduleName, type)) {
+ if (type == Node::Module) {
+ NodeMap m;
+ cn->getMemberClasses(m);
+ if (!m.isEmpty())
+ generateAnnotatedList(relative, m, atom->string());
+ } else {
+ generateAnnotatedList(relative, cn->members(), atom->string());
+ }
+ }
+ } else if (atom->string().startsWith("examplefiles")
+ || atom->string().startsWith("exampleimages")) {
+ if (relative->isExample())
+ qDebug() << "GENERATE FILE LIST CALLED" << relative->name() << atom->string();
+ } else if (atom->string() == QLatin1String("classhierarchy")) {
+ generateClassHierarchy(relative, qdb_->getCppClasses());
+ } else if (atom->string().startsWith("obsolete")) {
+ ListType type = atom->string().endsWith("members") ? Obsolete : Generic;
+ QString prefix = atom->string().contains("cpp") ? QStringLiteral("Q") : QString();
+ const NodeMultiMap &things = atom->string() == QLatin1String("obsoleteclasses")
+ ? qdb_->getObsoleteClasses()
+ : atom->string() == QLatin1String("obsoleteqmltypes")
+ ? qdb_->getObsoleteQmlTypes()
+ : atom->string() == QLatin1String("obsoletecppmembers")
+ ? qdb_->getClassesWithObsoleteMembers()
+ : qdb_->getQmlTypesWithObsoleteMembers();
+ generateCompactList(type, relative, things, prefix, atom->string());
+ } else if (atom->string() == QLatin1String("functionindex")) {
+ generateFunctionIndex(relative);
+ } else if (atom->string() == QLatin1String("legalese")) {
+ generateLegaleseList(relative);
+ } else if (atom->string() == QLatin1String("overviews")
+ || atom->string() == QLatin1String("cpp-modules")
+ || atom->string() == QLatin1String("qml-modules")
+ || atom->string() == QLatin1String("related")) {
+ generateList(relative, atom->string());
+ }
+ break;
+ case Atom::SinceList:
+ // Table of contents, should automatically be generated by the DocBook processor.
+ break;
+ case Atom::LineBreak:
+ case Atom::BR:
+ case Atom::HR:
+ // Not supported in DocBook.
+ break;
+ case Atom::Image: // mediaobject
+ case Atom::InlineImage: { // inlinemediaobject
+ QString tag = atom->type() == Atom::Image ? "mediaobject" : "inlinemediaobject";
+ writer->writeStartElement(dbNamespace, tag);
+ newLine();
+
+ QString fileName = imageFileName(relative, atom->string());
+ if (fileName.isEmpty()) {
+ writer->writeStartElement(dbNamespace, "textobject");
+ newLine();
+ writer->writeStartElement(dbNamespace, "para");
+ writer->writeTextElement(dbNamespace, "emphasis",
+ "[Missing image " + atom->string() + "]");
+ writer->writeEndElement(); // para
+ newLine();
+ writer->writeEndElement(); // textobject
+ newLine();
+ } else {
+ if (atom->next() && !atom->next()->string().isEmpty())
+ writer->writeTextElement(dbNamespace, "alt", atom->next()->string());
+
+ writer->writeStartElement(dbNamespace, "imageobject");
+ newLine();
+ writer->writeEmptyElement(dbNamespace, "imagedata");
+ writer->writeAttribute("fileref", fileName);
+ newLine();
+ writer->writeEndElement(); // imageobject
+ newLine();
+
+ setImageFileName(relative, fileName);
+ }
+
+ writer->writeEndElement(); // [inline]mediaobject
+ if (atom->type() == Atom::Image)
+ newLine();
+ } break;
+ case Atom::ImageText:
+ break;
+ case Atom::ImportantLeft:
+ case Atom::NoteLeft: {
+ QString tag = atom->type() == Atom::ImportantLeft ? "important" : "note";
+ writer->writeStartElement(dbNamespace, tag);
+ newLine();
+ writer->writeStartElement(dbNamespace, "para");
+ } break;
+ case Atom::ImportantRight:
+ case Atom::NoteRight:
+ writer->writeEndElement(); // para
+ newLine();
+ writer->writeEndElement(); // note/important
+ newLine();
+ break;
+ case Atom::LegaleseLeft:
+ case Atom::LegaleseRight:
+ break;
+ case Atom::Link:
+ case Atom::NavLink: {
+ const Node *node = nullptr;
+ QString link = getLink(atom, relative, &node);
+ beginLink(link, node, relative); // Ended at Atom::FormattingRight
+ skipAhead = 1;
+ } break;
+ case Atom::LinkNode: {
+ const Node *node = CodeMarker::nodeForString(atom->string());
+ beginLink(linkForNode(node, relative), node, relative);
+ skipAhead = 1;
+ } break;
+ case Atom::ListLeft:
+ if (inPara) {
+ writer->writeEndElement(); // para
+ newLine();
+ inPara = false;
+ }
+ if (atom->string() == ATOM_LIST_BULLET) {
+ writer->writeStartElement(dbNamespace, "itemizedlist");
+ newLine();
+ } else if (atom->string() == ATOM_LIST_TAG) {
+ writer->writeStartElement(dbNamespace, "variablelist");
+ newLine();
+ } else if (atom->string() == ATOM_LIST_VALUE) {
+ writer->writeStartElement(dbNamespace, "informaltable");
+ newLine();
+ writer->writeStartElement(dbNamespace, "thead");
+ newLine();
+ writer->writeStartElement(dbNamespace, "tr");
+ newLine();
+ writer->writeTextElement(dbNamespace, "th", "Constant");
+ newLine();
+
+ threeColumnEnumValueTable_ = isThreeColumnEnumValueTable(atom);
+ if (threeColumnEnumValueTable_ && relative->nodeType() == Node::Enum) {
+ // If not in \enum topic, skip the value column
+ writer->writeTextElement(dbNamespace, "th", "Value");
+ newLine();
+ }
+
+ writer->writeTextElement(dbNamespace, "th", "Description");
+ newLine();
+
+ writer->writeEndElement(); // tr
+ newLine();
+ writer->writeEndElement(); // thead
+ newLine();
+ } else {
+ writer->writeStartElement(dbNamespace, "orderedlist");
+
+ if (atom->next() != nullptr && atom->next()->string().toInt() > 1)
+ writer->writeAttribute("startingnumber", atom->next()->string());
+
+ if (atom->string() == ATOM_LIST_UPPERALPHA)
+ writer->writeAttribute("numeration", "upperalpha");
+ else if (atom->string() == ATOM_LIST_LOWERALPHA)
+ writer->writeAttribute("numeration", "loweralpha");
+ else if (atom->string() == ATOM_LIST_UPPERROMAN)
+ writer->writeAttribute("numeration", "upperroman");
+ else if (atom->string() == ATOM_LIST_LOWERROMAN)
+ writer->writeAttribute("numeration", "lowerroman");
+ else // (atom->string() == ATOM_LIST_NUMERIC)
+ writer->writeAttribute("numeration", "arabic");
+
+ newLine();
+ }
+ break;
+ case Atom::ListItemNumber:
+ break;
+ case Atom::ListTagLeft:
+ if (atom->string() == ATOM_LIST_TAG) {
+ writer->writeStartElement(dbNamespace, "varlistentry");
+ newLine();
+ writer->writeStartElement(dbNamespace, "item");
+ } else { // (atom->string() == ATOM_LIST_VALUE)
+ QPair<QString, int> pair = getAtomListValue(atom);
+ skipAhead = pair.second;
+
+ writer->writeStartElement(dbNamespace, "tr");
+ newLine();
+ writer->writeStartElement(dbNamespace, "td");
+ newLine();
+ writer->writeStartElement(dbNamespace, "para");
+ generateEnumValue(pair.first, relative);
+ writer->writeEndElement(); // para
+ newLine();
+ writer->writeEndElement(); // td
+ newLine();
+
+ if (relative->nodeType() == Node::Enum) {
+ const auto enume = static_cast<const EnumNode *>(relative);
+ QString itemValue = enume->itemValue(atom->next()->string());
+
+ writer->writeStartElement(dbNamespace, "td");
+ if (itemValue.isEmpty())
+ writer->writeCharacters("?");
+ else
+ writer->writeTextElement(dbNamespace, "code", itemValue);
+ writer->writeEndElement(); // td
+ newLine();
+ }
+ }
+ break;
+ case Atom::SinceTagRight:
+ case Atom::ListTagRight:
+ if (atom->string() == ATOM_LIST_TAG) {
+ writer->writeEndElement(); // item
+ newLine();
+ }
+ break;
+ case Atom::ListItemLeft:
+ inListItemLineOpen = false;
+ if (atom->string() == ATOM_LIST_TAG) {
+ writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ writer->writeStartElement(dbNamespace, "para");
+ } else if (atom->string() == ATOM_LIST_VALUE) {
+ if (threeColumnEnumValueTable_) {
+ if (matchAhead(atom, Atom::ListItemRight)) {
+ writer->writeEmptyElement(dbNamespace, "td");
+ newLine();
+ inListItemLineOpen = false;
+ } else {
+ writer->writeStartElement(dbNamespace, "td");
+ newLine();
+ inListItemLineOpen = true;
+ }
+ }
+ } else {
+ writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ }
+ // Don't skip a paragraph, DocBook requires them within list items.
+ break;
+ case Atom::ListItemRight:
+ if (atom->string() == ATOM_LIST_TAG) {
+ writer->writeEndElement(); // para
+ newLine();
+ writer->writeEndElement(); // listitem
+ newLine();
+ writer->writeEndElement(); // varlistentry
+ newLine();
+ } else if (atom->string() == ATOM_LIST_VALUE) {
+ if (inListItemLineOpen) {
+ writer->writeEndElement(); // td
+ newLine();
+ inListItemLineOpen = false;
+ }
+ writer->writeEndElement(); // tr
+ newLine();
+ } else {
+ writer->writeEndElement(); // listitem
+ newLine();
+ }
+ break;
+ case Atom::ListRight:
+ // Depending on atom->string(), closing a different item:
+ // - ATOM_LIST_BULLET: itemizedlist
+ // - ATOM_LIST_TAG: variablelist
+ // - ATOM_LIST_VALUE: informaltable
+ // - ATOM_LIST_NUMERIC: orderedlist
+ writer->writeEndElement();
+ newLine();
+ break;
+ case Atom::Nop:
+ break;
+ case Atom::ParaLeft:
+ writer->writeStartElement(dbNamespace, "para");
+ inPara = true;
+ break;
+ case Atom::ParaRight:
+ endLink();
+ if (inPara) {
+ writer->writeEndElement(); // para
+ newLine();
+ inPara = false;
+ }
+ break;
+ case Atom::QuotationLeft:
+ writer->writeStartElement(dbNamespace, "blockquote");
+ inPara = true;
+ break;
+ case Atom::QuotationRight:
+ writer->writeEndElement(); // blockquote
+ newLine();
+ break;
+ case Atom::RawString:
+ writer->writeCharacters(atom->string());
+ break;
+ case Atom::SectionLeft:
+ currentSectionLevel = atom->string().toInt() + hOffset(relative);
+ // Level 1 is dealt with at the header level (info tag).
+ if (currentSectionLevel > 1) {
+ // Unfortunately, SectionRight corresponds to the end of any section,
+ // i.e. going to a new section, even deeper.
+ while (!sectionLevels.empty() && sectionLevels.top() >= currentSectionLevel) {
+ sectionLevels.pop();
+ writer->writeEndElement(); // section
+ newLine();
+ }
+
+ sectionLevels.push(currentSectionLevel);
+
+ writer->writeStartElement(dbNamespace, "section");
+ writer->writeAttribute("xml:id",
+ Doc::canonicalTitle(Text::sectionHeading(atom).toString()));
+ newLine();
+ // Unlike startSectionBegin, don't start a title here.
+ }
+ break;
+ case Atom::SectionRight:
+ // All the logic about closing sections is done in the SectionLeft case
+ // and generateFooter() for the end of the page.
+ break;
+ case Atom::SectionHeadingLeft:
+ // Level 1 is dealt with at the header level (info tag).
+ if (currentSectionLevel > 1) {
+ writer->writeStartElement(dbNamespace, "title");
+ inSectionHeading_ = true;
+ }
+ break;
+ case Atom::SectionHeadingRight:
+ // Level 1 is dealt with at the header level (info tag).
+ if (currentSectionLevel > 1) {
+ writer->writeEndElement(); // title
+ newLine();
+ inSectionHeading_ = false;
+ }
+ break;
+ case Atom::SidebarLeft:
+ writer->writeStartElement(dbNamespace, "sidebar");
+ break;
+ case Atom::SidebarRight:
+ writer->writeEndElement(); // sidebar
+ newLine();
+ break;
+ case Atom::String:
+ if (inLink && !inContents_ && !inSectionHeading_)
+ generateLink(atom);
+ else
+ writer->writeCharacters(atom->string());
+ break;
+ case Atom::TableLeft: {
+ QPair<QString, QString> pair = getTableWidthAttr(atom);
+ QString attr = pair.second;
+ QString width = pair.first;
+
+ if (inPara) {
+ writer->writeEndElement(); // para or blockquote
+ newLine();
+ inPara = false;
+ }
+
+ writer->writeStartElement(dbNamespace, "informaltable");
+ writer->writeAttribute("style", attr);
+ if (!width.isEmpty())
+ writer->writeAttribute("width", width);
+ newLine();
+ numTableRows_ = 0;
+ } break;
+ case Atom::TableRight:
+ writer->writeEndElement(); // table
+ newLine();
+ break;
+ case Atom::TableHeaderLeft:
+ writer->writeStartElement(dbNamespace, "thead");
+ newLine();
+ writer->writeStartElement(dbNamespace, "tr");
+ newLine();
+ inTableHeader_ = true;
+ break;
+ case Atom::TableHeaderRight:
+ writer->writeEndElement(); // tr
+ newLine();
+ if (matchAhead(atom, Atom::TableHeaderLeft)) {
+ skipAhead = 1;
+ writer->writeStartElement(dbNamespace, "tr");
+ newLine();
+ } else {
+ writer->writeEndElement(); // thead
+ newLine();
+ inTableHeader_ = false;
+ }
+ break;
+ case Atom::TableRowLeft:
+ writer->writeStartElement(dbNamespace, "tr");
+ if (atom->string().isEmpty()) {
+ writer->writeAttribute("valign", "top");
+ } else {
+ // Basic parsing of attributes, should be enough. The input string (atom->string())
+ // looks like:
+ // arg1="val1" arg2="val2"
+ QStringList args = atom->string().split("\"", Qt::SkipEmptyParts);
+ // arg1=, val1, arg2=, val2,
+ // \-- 1st --/ \-- 2nd --/ \-- remainder
+ if (args.size() % 2) {
+ // Problem...
+ relative->doc().location().warning(
+ tr("Error when parsing attributes for the table: got \"%1\"")
+ .arg(atom->string()));
+ }
+ for (int i = 0; i + 1 < args.size(); i += 2)
+ writer->writeAttribute(args.at(i).chopped(1), args.at(i + 1));
+ }
+ newLine();
+ break;
+ case Atom::TableRowRight:
+ writer->writeEndElement(); // tr
+ newLine();
+ break;
+ case Atom::TableItemLeft:
+ writer->writeStartElement(dbNamespace, inTableHeader_ ? "th" : "td");
+
+ for (int i = 0; i < atom->count(); ++i) {
+ const QString &p = atom->string(i);
+ if (p.contains('=')) {
+ QStringList lp = p.split(QLatin1Char('='));
+ writer->writeAttribute(lp.at(0), lp.at(1));
+ } else {
+ QStringList spans = p.split(QLatin1Char(','));
+ if (spans.size() == 2) {
+ if (spans.at(0) != "1")
+ writer->writeAttribute("colspan", spans.at(0));
+ if (spans.at(1) != "1")
+ writer->writeAttribute("rowspan", spans.at(1));
+ }
+ }
+ }
+ newLine();
+ // No skipahead, as opposed to HTML: in DocBook, the text must be wrapped in paragraphs.
+ break;
+ case Atom::TableItemRight:
+ writer->writeEndElement(); // th if inTableHeader_, otherwise td
+ newLine();
+ break;
+ case Atom::TableOfContents:
+ break;
+ case Atom::Keyword:
+ break;
+ case Atom::Target:
+ writeAnchor(Doc::canonicalTitle(atom->string()));
+ break;
+ case Atom::UnhandledFormat:
+ writer->writeStartElement(dbNamespace, "emphasis");
+ writer->writeAttribute("role", "bold");
+ writer->writeCharacters("&lt;Missing DocBook&gt;");
+ writer->writeEndElement(); // emphasis
+ break;
+ case Atom::UnknownCommand:
+ writer->writeStartElement(dbNamespace, "emphasis");
+ writer->writeAttribute("role", "bold");
+ writer->writeCharacters("&lt;Unknown command&gt;");
+ writer->writeStartElement(dbNamespace, "code");
+ writer->writeCharacters(atom->string());
+ writer->writeEndElement(); // code
+ writer->writeEndElement(); // emphasis
+ break;
+ case Atom::QmlText:
+ case Atom::EndQmlText:
+ // don't do anything with these. They are just tags.
+ break;
+ case Atom::CodeQuoteArgument:
+ case Atom::CodeQuoteCommand:
+ case Atom::SnippetCommand:
+ case Atom::SnippetIdentifier:
+ case Atom::SnippetLocation:
+ // no output (ignore)
+ break;
+ default:
+ unknownAtom(atom);
+ }
+ return skipAhead;
+}
+
+void DocBookGenerator::generateClassHierarchy(const Node *relative, NodeMap &classMap)
+{
+ // From HtmlGenerator::generateClassHierarchy.
+ if (classMap.isEmpty())
+ return;
+
+ NodeMap topLevel;
+ NodeMap::Iterator c = classMap.begin();
+ while (c != classMap.end()) {
+ auto *classe = static_cast<ClassNode *>(*c);
+ if (classe->baseClasses().isEmpty())
+ topLevel.insert(classe->name(), classe);
+ ++c;
+ }
+
+ QStack<NodeMap> stack;
+ stack.push(topLevel);
+
+ writer->writeStartElement(dbNamespace, "itemizedlist");
+ newLine();
+ while (!stack.isEmpty()) {
+ if (stack.top().isEmpty()) {
+ stack.pop();
+ writer->writeEndElement(); // listitem
+ newLine();
+ writer->writeEndElement(); // itemizedlist
+ newLine();
+ } else {
+ ClassNode *child = static_cast<ClassNode *>(*stack.top().begin());
+ writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ writer->writeStartElement(dbNamespace, "para");
+ generateFullName(child, relative);
+ writer->writeEndElement(); // para
+ newLine();
+ // Don't close the listitem now, as DocBook requires sublists to reside in items.
+ stack.top().erase(stack.top().begin());
+
+ NodeMap newTop;
+ for (const RelatedClass &d : child->derivedClasses()) {
+ if (d.node_ && !d.isPrivate() && !d.node_->isInternal() && d.node_->hasDoc())
+ newTop.insert(d.node_->name(), d.node_);
+ }
+ if (!newTop.isEmpty()) {
+ stack.push(newTop);
+ writer->writeStartElement(dbNamespace, "itemizedlist");
+ newLine();
+ }
+ }
+ }
+}
+
+void DocBookGenerator::generateLink(const Atom *atom)
+{
+ // From HtmlGenerator::generateLink.
+ QRegExp funcLeftParen("\\S(\\()");
+ if (funcLeftParen.indexIn(atom->string()) != -1) {
+ // hack for C++: move () outside of link
+ int k = funcLeftParen.pos(1);
+ writer->writeCharacters(atom->string().left(k));
+ writer->writeEndElement(); // link
+ inLink = false;
+ writer->writeCharacters(atom->string().mid(k));
+ } else {
+ writer->writeCharacters(atom->string());
+ }
+}
+
+/*!
+ This version of the function is called when the \a link is known
+ to be correct.
+ */
+void DocBookGenerator::beginLink(const QString &link, const Node *node, const Node *relative)
+{
+ // From HtmlGenerator::beginLink.
+ writer->writeStartElement(dbNamespace, "link");
+ writer->writeAttribute(xlinkNamespace, "href", link);
+ if (node && !(relative && node->status() == relative->status())
+ && node->status() == Node::Obsolete)
+ writer->writeAttribute("role", "obsolete");
+ inLink = true;
+}
+
+void DocBookGenerator::endLink()
+{
+ // From HtmlGenerator::endLink.
+ if (inLink)
+ writer->writeEndElement(); // link
+ inLink = false;
+}
+
+void DocBookGenerator::generateList(const Node *relative, const QString &selector)
+{
+ // From HtmlGenerator::generateList, without warnings, changing prototype.
+ CNMap cnm;
+ Node::NodeType type = Node::NoType;
+ if (selector == QLatin1String("overviews"))
+ type = Node::Group;
+ else if (selector == QLatin1String("cpp-modules"))
+ type = Node::Module;
+ else if (selector == QLatin1String("qml-modules"))
+ type = Node::QmlModule;
+ else if (selector == QLatin1String("js-modules"))
+ type = Node::JsModule;
+
+ if (type != Node::NoType) {
+ NodeList nodeList;
+ qdb_->mergeCollections(type, cnm, relative);
+ const QList<CollectionNode *> collectionList = cnm.values();
+ nodeList.reserve(collectionList.size());
+ for (auto *collectionNode : collectionList)
+ nodeList.append(collectionNode);
+ generateAnnotatedList(relative, nodeList, selector);
+ } else {
+ /*
+ \generatelist {selector} is only allowed in a
+ comment where the topic is \group, \module,
+ \qmlmodule, or \jsmodule
+ */
+ Node *n = const_cast<Node *>(relative);
+ auto *cn = static_cast<CollectionNode *>(n);
+ qdb_->mergeCollections(cn);
+ generateAnnotatedList(cn, cn->members(), selector);
+ }
+}
+
+/*!
+ Output an annotated list of the nodes in \a nodeMap.
+ A two-column table is output.
+ */
+void DocBookGenerator::generateAnnotatedList(const Node *relative, const NodeMultiMap &nmm,
+ const QString &selector)
+{
+ // From HtmlGenerator::generateAnnotatedList
+ if (nmm.isEmpty())
+ return;
+ generateAnnotatedList(relative, nmm.values(), selector);
+}
+
+void DocBookGenerator::generateAnnotatedList(const Node *relative, const NodeList &nodeList,
+ const QString &selector)
+{
+ // From WebXMLGenerator::generateAnnotatedList.
+ writer->writeStartElement(dbNamespace, "variablelist");
+ writer->writeAttribute("role", selector);
+ newLine();
+
+ for (auto node : nodeList) {
+ writer->writeStartElement(dbNamespace, "varlistentry");
+ newLine();
+ writer->writeStartElement(dbNamespace, "term");
+ generateFullName(node, relative);
+ writer->writeEndElement(); // term
+ newLine();
+
+ writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ writer->writeStartElement(dbNamespace, "para");
+ writer->writeCharacters(node->doc().briefText().toString());
+ writer->writeEndElement(); // para
+ newLine();
+ writer->writeEndElement(); // listitem
+ newLine();
+ writer->writeEndElement(); // varlistentry
+ newLine();
+ }
+ writer->writeEndElement(); // variablelist
+ newLine();
+}
+
+/*!
+ Outputs a series of annotated lists from the nodes in \a nmm,
+ divided into sections based by the key names in the multimap.
+ */
+void DocBookGenerator::generateAnnotatedLists(const Node *relative, const NodeMultiMap &nmm,
+ const QString &selector)
+{
+ // From HtmlGenerator::generateAnnotatedLists.
+ for (const QString &name : nmm.uniqueKeys()) {
+ if (!name.isEmpty())
+ startSection(registerRef(name.toLower()), name);
+ generateAnnotatedList(relative, nmm.values(name), selector);
+ if (!name.isEmpty())
+ endSection();
+ }
+}
+
+/*!
+ This function finds the common prefix of the names of all
+ the classes in the class map \a nmm and then generates a
+ compact list of the class names alphabetized on the part
+ of the name not including the common prefix. You can tell
+ the function to use \a comonPrefix as the common prefix,
+ but normally you let it figure it out itself by looking at
+ the name of the first and last classes in the class map
+ \a nmm.
+ */
+void DocBookGenerator::generateCompactList(ListType listType, const Node *relative,
+ const NodeMultiMap &nmm, const QString &commonPrefix,
+ const QString &selector)
+{
+ // From HtmlGenerator::generateCompactList. No more "includeAlphabet", this should be handled by
+ // the DocBook toolchain afterwards.
+ // TODO: In DocBook, probably no need for this method: this is purely presentational, i.e. to be
+ // fully handled by the DocBook toolchain.
+ if (nmm.isEmpty())
+ return;
+
+ const int NumParagraphs = 37; // '0' to '9', 'A' to 'Z', '_'
+ int commonPrefixLen = commonPrefix.length();
+
+ /*
+ Divide the data into 37 paragraphs: 0, ..., 9, A, ..., Z,
+ underscore (_). QAccel will fall in paragraph 10 (A) and
+ QXtWidget in paragraph 33 (X). This is the only place where we
+ assume that NumParagraphs is 37. Each paragraph is a NodeMultiMap.
+ */
+ NodeMultiMap paragraph[NumParagraphs + 1];
+ QString paragraphName[NumParagraphs + 1];
+ QSet<char> usedParagraphNames;
+
+ NodeMultiMap::ConstIterator c = nmm.constBegin();
+ while (c != nmm.constEnd()) {
+ QStringList pieces = c.key().split("::");
+ QString key;
+ int idx = commonPrefixLen;
+ if (idx > 0 && !pieces.last().startsWith(commonPrefix, Qt::CaseInsensitive))
+ idx = 0;
+ key = pieces.last().mid(idx).toLower();
+
+ int paragraphNr = NumParagraphs - 1;
+
+ if (key[0].digitValue() != -1)
+ paragraphNr = key[0].digitValue();
+ else if (key[0] >= QLatin1Char('a') && key[0] <= QLatin1Char('z'))
+ paragraphNr = 10 + key[0].unicode() - 'a';
+
+ paragraphName[paragraphNr] = key[0].toUpper();
+ usedParagraphNames.insert(key[0].toLower().cell());
+ paragraph[paragraphNr].insert(c.key(), c.value());
+ ++c;
+ }
+
+ /*
+ Each paragraph j has a size: paragraph[j].count(). In the
+ discussion, we will assume paragraphs 0 to 5 will have sizes
+ 3, 1, 4, 1, 5, 9.
+
+ We now want to compute the paragraph offset. Paragraphs 0 to 6
+ start at offsets 0, 3, 4, 8, 9, 14, 23.
+ */
+ int paragraphOffset[NumParagraphs + 1]; // 37 + 1
+ paragraphOffset[0] = 0;
+ for (int i = 0; i < NumParagraphs; i++) // i = 0..36
+ paragraphOffset[i + 1] = paragraphOffset[i] + paragraph[i].count();
+
+ // No table of contents in DocBook.
+
+ // Actual output.
+ numTableRows_ = 0;
+
+ int curParNr = 0;
+ int curParOffset = 0;
+ QString previousName;
+ bool multipleOccurrences = false;
+
+ for (int i = 0; i < nmm.count(); i++) {
+ while ((curParNr < NumParagraphs) && (curParOffset == paragraph[curParNr].count())) {
+ ++curParNr;
+ curParOffset = 0;
+ }
+
+ /*
+ Starting a new paragraph means starting a new variablelist.
+ */
+ if (curParOffset == 0) {
+ if (i > 0) {
+ writer->writeEndElement(); // variablelist
+ newLine();
+ }
+
+ writer->writeStartElement(dbNamespace, "variablelist");
+ writer->writeAttribute("role", selector);
+ newLine();
+ writer->writeStartElement(dbNamespace, "varlistentry");
+ newLine();
+
+ writer->writeStartElement(dbNamespace, "term");
+ writer->writeStartElement(dbNamespace, "emphasis");
+ writer->writeAttribute("role", "bold");
+ writer->writeCharacters(paragraphName[curParNr]);
+ writer->writeEndElement(); // emphasis
+ writer->writeEndElement(); // term
+ newLine();
+ }
+
+ /*
+ Output a listitem for the current offset in the current paragraph.
+ */
+ writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ writer->writeStartElement(dbNamespace, "para");
+ if ((curParNr < NumParagraphs) && !paragraphName[curParNr].isEmpty()) {
+ NodeMultiMap::Iterator it;
+ NodeMultiMap::Iterator next;
+ it = paragraph[curParNr].begin();
+ for (int j = 0; j < curParOffset; j++)
+ ++it;
+
+ if (listType == Generic) {
+ generateFullName(it.value(), relative);
+ writer->writeStartElement(dbNamespace, "link");
+ writer->writeAttribute(xlinkNamespace, "href", fullDocumentLocation(*it));
+ writer->writeAttribute("type", targetType(it.value()));
+ } else if (listType == Obsolete) {
+ QString fn = fileName(it.value(), fileExtension());
+ QString link;
+ if (useOutputSubdirs())
+ link = QString("../" + it.value()->outputSubdirectory() + QLatin1Char('/'));
+ link += fn;
+
+ writer->writeStartElement(dbNamespace, "link");
+ writer->writeAttribute(xlinkNamespace, "href", link);
+ writer->writeAttribute("type", targetType(it.value()));
+ }
+
+ QStringList pieces;
+ if (it.value()->isQmlType() || it.value()->isJsType()) {
+ QString name = it.value()->name();
+ next = it;
+ ++next;
+ if (name != previousName)
+ multipleOccurrences = false;
+ if ((next != paragraph[curParNr].end()) && (name == next.value()->name())) {
+ multipleOccurrences = true;
+ previousName = name;
+ }
+ if (multipleOccurrences)
+ name += ": " + it.value()->tree()->camelCaseModuleName();
+ pieces << name;
+ } else
+ pieces = it.value()->fullName(relative).split("::");
+
+ writer->writeCharacters(pieces.last());
+ writer->writeEndElement(); // link
+
+ if (pieces.size() > 1) {
+ writer->writeCharacters(" (");
+ generateFullName(it.value()->parent(), relative);
+ writer->writeCharacters(")");
+ }
+ }
+ writer->writeEndElement(); // para
+ newLine();
+ writer->writeEndElement(); // listitem
+ newLine();
+ writer->writeEndElement(); // varlistentry
+ newLine();
+ curParOffset++;
+ }
+ if (nmm.count() > 0) {
+ writer->writeEndElement(); // variablelist
+ }
+}
+
+void DocBookGenerator::generateFunctionIndex(const Node *relative)
+{
+ // From HtmlGenerator::generateFunctionIndex.
+ writer->writeStartElement(dbNamespace, "simplelist");
+ writer->writeAttribute("role", "functionIndex");
+ newLine();
+ for (int i = 0; i < 26; i++) {
+ QChar ch('a' + i);
+ writer->writeStartElement(dbNamespace, "member");
+ writer->writeAttribute(xlinkNamespace, "href", QString("#") + ch);
+ writer->writeCharacters(ch.toUpper());
+ writer->writeEndElement(); // member
+ newLine();
+ }
+ writer->writeEndElement(); // simplelist
+ newLine();
+
+ char nextLetter = 'a';
+ char currentLetter;
+
+ writer->writeStartElement(dbNamespace, "itemizedlist");
+ newLine();
+
+ NodeMapMap &funcIndex = qdb_->getFunctionIndex();
+ QMap<QString, NodeMap>::ConstIterator f = funcIndex.constBegin();
+ while (f != funcIndex.constEnd()) {
+ writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ writer->writeStartElement(dbNamespace, "para");
+ writer->writeCharacters(f.key() + ": ");
+
+ currentLetter = f.key()[0].unicode();
+ while (islower(currentLetter) && currentLetter >= nextLetter) {
+ writeAnchor(QString(nextLetter));
+ nextLetter++;
+ }
+
+ NodeMap::ConstIterator s = (*f).constBegin();
+ while (s != (*f).constEnd()) {
+ writer->writeCharacters(" ");
+ generateFullName((*s)->parent(), relative);
+ ++s;
+ }
+
+ writer->writeEndElement(); // para
+ newLine();
+ writer->writeEndElement(); // listitem
+ newLine();
+ ++f;
+ }
+ writer->writeEndElement(); // itemizedlist
+ newLine();
+}
+
+void DocBookGenerator::generateLegaleseList(const Node *relative)
+{
+ // From HtmlGenerator::generateLegaleseList.
+ TextToNodeMap &legaleseTexts = qdb_->getLegaleseTexts();
+ QMap<Text, const Node *>::ConstIterator it = legaleseTexts.constBegin();
+ while (it != legaleseTexts.constEnd()) {
+ Text text = it.key();
+ generateText(text, relative);
+ writer->writeStartElement(dbNamespace, "itemizedlist");
+ newLine();
+ do {
+ writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ writer->writeStartElement(dbNamespace, "para");
+ generateFullName(it.value(), relative);
+ writer->writeEndElement(); // para
+ newLine();
+ writer->writeEndElement(); // listitem
+ newLine();
+ ++it;
+ } while (it != legaleseTexts.constEnd() && it.key() == text);
+ writer->writeEndElement(); // itemizedlist
+ newLine();
+ }
+}
+
+void DocBookGenerator::generateBrief(const Node *node)
+{
+ // From HtmlGenerator::generateBrief. Also see generateHeader, which is specifically dealing
+ // with the DocBook header (and thus wraps the brief in an abstract).
+ Text brief = node->doc().briefText();
+
+ if (!brief.isEmpty()) {
+ if (!brief.lastAtom()->string().endsWith('.'))
+ brief << Atom(Atom::String, ".");
+
+ writer->writeStartElement(dbNamespace, "para");
+ generateText(brief, node);
+ writer->writeEndElement(); // para
+ newLine();
+ }
+}
+
+bool DocBookGenerator::generateSince(const Node *node)
+{
+ // From Generator::generateSince.
+ if (!node->since().isEmpty()) {
+ writer->writeStartElement(dbNamespace, "para");
+ writer->writeCharacters("This " + typeString(node) + " was introduced");
+ if (node->nodeType() == Node::Enum)
+ writer->writeCharacters(" or modified");
+ writer->writeCharacters(" in " + formatSince(node) + ".");
+ writer->writeEndElement(); // para
+ newLine();
+
+ return true;
+ }
+
+ return false;
+}
+
+void DocBookGenerator::generateHeader(const QString &title, const QString &subTitle,
+ const Node *node)
+{
+ // From HtmlGenerator::generateHeader.
+ refMap.clear();
+
+ // Output the DocBook header.
+ writer->writeStartElement(dbNamespace, "info");
+ newLine();
+ writer->writeTextElement(dbNamespace, "title", title);
+ newLine();
+
+ if (!subTitle.isEmpty()) {
+ writer->writeTextElement(dbNamespace, "subtitle", subTitle);
+ newLine();
+ }
+
+ if (!project.isEmpty()) {
+ writer->writeTextElement(dbNamespace, "productname", project);
+ newLine();
+ }
+
+ if (!buildversion.isEmpty()) {
+ writer->writeTextElement(dbNamespace, "edition", buildversion);
+ newLine();
+ }
+
+ if (!projectDescription.isEmpty()) {
+ writer->writeTextElement(dbNamespace, "titleabbrev", projectDescription);
+ newLine();
+ }
+
+ // Deal with links.
+ // Adapted from HtmlGenerator::generateHeader (output part: no need to update a navigationLinks
+ // or useSeparator field, as this content is only output in the info tag, not in the main
+ // content).
+ if (node && !node->links().empty()) {
+ QPair<QString, QString> linkPair;
+ QPair<QString, QString> anchorPair;
+ const Node *linkNode;
+
+ if (node->links().contains(Node::PreviousLink)) {
+ linkPair = node->links()[Node::PreviousLink];
+ linkNode = qdb_->findNodeForTarget(linkPair.first, node);
+ if (!linkNode || linkNode == node)
+ anchorPair = linkPair;
+ else
+ anchorPair = anchorForNode(linkNode);
+
+ writer->writeStartElement(dbNamespace, "extendedlink");
+ writer->writeEmptyElement(dbNamespace, "link");
+ writer->writeAttribute(xlinkNamespace, "to", anchorPair.first);
+ writer->writeAttribute(xlinkNamespace, "title", "prev");
+ if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty())
+ writer->writeAttribute(xlinkNamespace, "label", anchorPair.second);
+ else
+ writer->writeAttribute(xlinkNamespace, "label", linkPair.second);
+ writer->writeEndElement(); // extendedlink
+ }
+ if (node->links().contains(Node::NextLink)) {
+ linkPair = node->links()[Node::NextLink];
+ linkNode = qdb_->findNodeForTarget(linkPair.first, node);
+ if (!linkNode || linkNode == node)
+ anchorPair = linkPair;
+ else
+ anchorPair = anchorForNode(linkNode);
+
+ writer->writeStartElement(dbNamespace, "extendedlink");
+ writer->writeEmptyElement(dbNamespace, "link");
+ writer->writeAttribute(xlinkNamespace, "to", anchorPair.first);
+ writer->writeAttribute(xlinkNamespace, "title", "prev");
+ if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty())
+ writer->writeAttribute(xlinkNamespace, "label", anchorPair.second);
+ else
+ writer->writeAttribute(xlinkNamespace, "label", linkPair.second);
+ writer->writeEndElement(); // extendedlink
+ }
+ if (node->links().contains(Node::StartLink)) {
+ linkPair = node->links()[Node::StartLink];
+ linkNode = qdb_->findNodeForTarget(linkPair.first, node);
+ if (!linkNode || linkNode == node)
+ anchorPair = linkPair;
+ else
+ anchorPair = anchorForNode(linkNode);
+
+ writer->writeStartElement(dbNamespace, "extendedlink");
+ writer->writeEmptyElement(dbNamespace, "link");
+ writer->writeAttribute(xlinkNamespace, "to", anchorPair.first);
+ writer->writeAttribute(xlinkNamespace, "title", "start");
+ if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty())
+ writer->writeAttribute(xlinkNamespace, "label", anchorPair.second);
+ else
+ writer->writeAttribute(xlinkNamespace, "label", linkPair.second);
+ writer->writeEndElement(); // extendedlink
+ }
+ }
+
+ // Deal with the abstract (what qdoc calls brief).
+ if (node) {
+ // Adapted from HtmlGenerator::generateBrief, without extraction marks. The parameter
+ // addLink is always false. Factoring this function out is not as easy as in HtmlGenerator:
+ // abstracts only happen in the header (info tag), slightly different tags must be used at
+ // other places. Also includes code from HtmlGenerator::generateCppReferencePage to handle
+ // the name spaces.
+ writer->writeStartElement(dbNamespace, "abstract");
+ newLine();
+
+ bool generatedSomething = false;
+
+ Text brief;
+ const NamespaceNode *ns = node->isAggregate()
+ ? static_cast<const NamespaceNode *>(static_cast<const Aggregate *>(node))
+ : nullptr;
+ if (node->isAggregate() && ns && !ns->hasDoc() && ns->docNode()) {
+ NamespaceNode *NS = ns->docNode();
+ brief << "The " << ns->name()
+ << " namespace includes the following elements from module "
+ << ns->tree()->camelCaseModuleName() << ". The full namespace is "
+ << "documented in module " << NS->tree()->camelCaseModuleName()
+ << Atom(Atom::LinkNode, fullDocumentLocation(NS))
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
+ << Atom(Atom::String, " here.")
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
+ } else {
+ brief = node->doc().briefText();
+ }
+
+ if (!brief.isEmpty()) {
+ if (!brief.lastAtom()->string().endsWith('.'))
+ brief << Atom(Atom::String, ".");
+
+ writer->writeStartElement(dbNamespace, "para");
+ generateText(brief, node);
+ writer->writeEndElement(); // para
+ newLine();
+
+ generatedSomething = true;
+ }
+
+ // Generate other paragraphs that should go into the abstract.
+ generatedSomething |= generateStatus(node);
+ generatedSomething |= generateSince(node);
+ generatedSomething |= generateThreadSafeness(node);
+
+ // An abstract cannot be empty, hence use the project description.
+ if (!generatedSomething)
+ writer->writeTextElement(dbNamespace, "para", projectDescription + ".");
+
+ writer->writeEndElement(); // abstract
+ newLine();
+ }
+
+ // End of the DocBook header.
+ writer->writeEndElement(); // info
+ newLine();
+}
+
+void DocBookGenerator::closeTextSections()
+{
+ while (!sectionLevels.isEmpty()) {
+ sectionLevels.pop();
+ endSection();
+ }
+}
+
+void DocBookGenerator::generateFooter()
+{
+ closeTextSections();
+ writer->writeEndElement(); // article
+}
+
+void DocBookGenerator::generateSimpleLink(const QString &href, const QString &text)
+{
+ writer->writeStartElement(dbNamespace, "link");
+ writer->writeAttribute(xlinkNamespace, "href", href);
+ writer->writeCharacters(text);
+ writer->writeEndElement(); // link
+}
+
+void DocBookGenerator::generateObsoleteMembers(const Sections &sections)
+{
+ // From HtmlGenerator::generateObsoleteMembersFile.
+ SectionPtrVector summary_spv; // Summaries are ignored in DocBook (table of contents).
+ SectionPtrVector details_spv;
+ if (!sections.hasObsoleteMembers(&summary_spv, &details_spv))
+ return;
+
+ Aggregate *aggregate = sections.aggregate();
+ QString link;
+ if (useOutputSubdirs() && !Generator::outputSubdir().isEmpty())
+ link = QString("../" + Generator::outputSubdir() + QLatin1Char('/'));
+ link += fileName(aggregate, fileExtension());
+ aggregate->setObsoleteLink(link);
+
+ startSection("obsolete", "Obsolete Members for " + aggregate->name());
+
+ writer->writeStartElement(dbNamespace, "para");
+ writer->writeStartElement(dbNamespace, "emphasis");
+ writer->writeAttribute("role", "bold");
+ writer->writeCharacters("The following members of class ");
+ generateSimpleLink(linkForNode(aggregate, nullptr), aggregate->name());
+ writer->writeCharacters(" are obsolete.");
+ writer->writeEndElement(); // emphasis bold
+ writer->writeCharacters(" They are provided to keep old source code working. "
+ "We strongly advise against using them in new code.");
+ writer->writeEndElement(); // para
+ newLine();
+
+ for (int i = 0; i < details_spv.size(); ++i) {
+ QString title = details_spv.at(i)->title();
+ QString ref = registerRef(title.toLower());
+ startSection(ref, title);
+
+ const NodeVector &members = details_spv.at(i)->obsoleteMembers();
+ NodeVector::ConstIterator m = members.constBegin();
+ while (m != members.constEnd()) {
+ if ((*m)->access() != Node::Private)
+ generateDetailedMember(*m, aggregate);
+ ++m;
+ }
+
+ endSection();
+ }
+
+ endSection();
+}
+
+/*!
+ Generates a separate file where obsolete members of the QML
+ type \a qcn are listed. The \a marker is used to generate
+ the section lists, which are then traversed and output here.
+
+ Note that this function currently only handles correctly the
+ case where \a status is \c {Section::Obsolete}.
+ */
+void DocBookGenerator::generateObsoleteQmlMembers(const Sections &sections)
+{
+ // From HtmlGenerator::generateObsoleteQmlMembersFile.
+ SectionPtrVector summary_spv; // Summaries are not useful in DocBook.
+ SectionPtrVector details_spv;
+ if (!sections.hasObsoleteMembers(&summary_spv, &details_spv))
+ return;
+
+ Aggregate *aggregate = sections.aggregate();
+ QString title = "Obsolete Members for " + aggregate->name();
+ QString fn = fileName(aggregate, fileExtension());
+ QString link;
+ if (useOutputSubdirs() && !Generator::outputSubdir().isEmpty())
+ link = QString("../" + Generator::outputSubdir() + QLatin1Char('/'));
+ link += fn;
+ aggregate->setObsoleteLink(link);
+
+ startSection("obsolete", "Obsolete Members for " + aggregate->name());
+
+ writer->writeStartElement(dbNamespace, "para");
+ writer->writeStartElement(dbNamespace, "emphasis");
+ writer->writeAttribute("role", "bold");
+ writer->writeCharacters("The following members of QML type ");
+ generateSimpleLink(linkForNode(aggregate, nullptr), aggregate->name());
+ writer->writeCharacters(" are obsolete.");
+ writer->writeEndElement(); // emphasis bold
+ writer->writeCharacters("They are provided to keep old source code working. "
+ "We strongly advise against using them in new code.");
+ writer->writeEndElement(); // para
+ newLine();
+
+ for (auto i : details_spv) {
+ QString ref = registerRef(i->title().toLower());
+ startSection(ref, i->title());
+
+ NodeVector::ConstIterator m = i->members().constBegin();
+ while (m != i->members().constEnd()) {
+ generateDetailedQmlMember(*m, aggregate);
+ ++m;
+ }
+
+ endSection();
+ }
+
+ endSection();
+}
+
+static QString nodeToSynopsisTag(const Node *node)
+{
+ // Order from Node::nodeTypeString.
+ if (node->isClass() || node->isQmlType() || node->isQmlBasicType())
+ return QStringLiteral("classsynopsis");
+ if (node->isNamespace())
+ return QStringLiteral("namespacesynopsis");
+ if (node->isPageNode()) {
+ node->doc().location().warning("Unexpected document node in nodeToSynopsisTag");
+ return QString();
+ }
+ if (node->isEnumType())
+ return QStringLiteral("enumsynopsis");
+ if (node->isTypedef())
+ return QStringLiteral("typedefsynopsis");
+ if (node->isFunction()) {
+ // Signals are also encoded as functions (including QML/JS ones).
+ const auto fn = static_cast<const FunctionNode *>(node);
+ if (fn->isCtor() || fn->isCCtor() || fn->isMCtor())
+ return QStringLiteral("constructorsynopsis");
+ if (fn->isDtor())
+ return QStringLiteral("destructorsynopsis");
+ return QStringLiteral("methodsynopsis");
+ }
+ if (node->isProperty() || node->isVariable() || node->isQmlProperty())
+ return QStringLiteral("fieldsynopsis");
+
+ node->doc().location().warning(QString("Unknown node tag %1").arg(node->nodeTypeString()));
+ return QStringLiteral("synopsis");
+}
+
+void DocBookGenerator::generateStartRequisite(const QString &description)
+{
+ writer->writeStartElement(dbNamespace, "varlistentry");
+ newLine();
+ writer->writeTextElement(dbNamespace, "term", description);
+ newLine();
+ writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ writer->writeStartElement(dbNamespace, "para");
+}
+
+void DocBookGenerator::generateEndRequisite()
+{
+ writer->writeEndElement(); // para
+ newLine();
+ writer->writeEndElement(); // listitem
+ newLine();
+ writer->writeEndElement(); // varlistentry
+ newLine();
+}
+
+void DocBookGenerator::generateRequisite(const QString &description, const QString &value)
+{
+ generateStartRequisite(description);
+ writer->writeCharacters(value);
+ generateEndRequisite();
+}
+
+void DocBookGenerator::generateSortedNames(const ClassNode *cn, const QVector<RelatedClass> &rc)
+{
+ // From Generator::appendSortedNames.
+ QMap<QString, ClassNode *> classMap;
+ QVector<RelatedClass>::ConstIterator r = rc.constBegin();
+ while (r != rc.constEnd()) {
+ ClassNode *rcn = (*r).node_;
+ if (rcn && rcn->access() == Node::Public && rcn->status() != Node::Internal
+ && !rcn->doc().isEmpty()) {
+ classMap[rcn->plainFullName(cn).toLower()] = rcn;
+ }
+ ++r;
+ }
+
+ QStringList classNames = classMap.keys();
+ classNames.sort();
+
+ int index = 0;
+ for (const QString &className : classNames) {
+ generateFullName(classMap.value(className), cn);
+ writer->writeCharacters(comma(index++, classNames.count()));
+ }
+}
+
+void DocBookGenerator::generateSortedQmlNames(const Node *base, const NodeList &subs)
+{
+ // From Generator::appendSortedQmlNames.
+ QMap<QString, Node *> classMap;
+ int index = 0;
+
+ for (auto sub : subs)
+ if (!base->isQtQuickNode() || !sub->isQtQuickNode()
+ || (base->logicalModuleName() == sub->logicalModuleName()))
+ classMap[sub->plainFullName(base).toLower()] = sub;
+
+ QStringList names = classMap.keys();
+ names.sort();
+
+ for (const QString &name : names) {
+ generateFullName(classMap.value(name), base);
+ writer->writeCharacters(comma(index++, names.count()));
+ }
+}
+
+/*!
+ Lists the required imports and includes.
+*/
+void DocBookGenerator::generateRequisites(const Aggregate *aggregate)
+{
+ // Adapted from HtmlGenerator::generateRequisites, but simplified: no need to store all the
+ // elements, they can be produced one by one.
+ writer->writeStartElement(dbNamespace, "variablelist");
+ newLine();
+
+ // Includes.
+ if (!aggregate->includeFiles().isEmpty()) {
+ for (const QString &include : aggregate->includeFiles())
+ generateRequisite("Header", include);
+ }
+
+ // Since and project.
+ if (!aggregate->since().isEmpty())
+ generateRequisite("Since", formatSince(aggregate));
+
+ if (aggregate->isClassNode() || aggregate->isNamespace()) {
+ // QT variable.
+ if (!aggregate->physicalModuleName().isEmpty()) {
+ const CollectionNode *cn =
+ qdb_->getCollectionNode(aggregate->physicalModuleName(), Node::Module);
+ if (cn && !cn->qtVariable().isEmpty()) {
+ generateRequisite("qmake", "QT += " + cn->qtVariable());
+ }
+ }
+ }
+
+ if (aggregate->nodeType() == Node::Class) {
+ // Instantiated by.
+ auto *classe = const_cast<ClassNode *>(static_cast<const ClassNode *>(aggregate));
+ if (classe->qmlElement() != nullptr && classe->status() != Node::Internal) {
+ generateStartRequisite("Inherited By");
+ generateSortedNames(classe, classe->derivedClasses());
+ generateEndRequisite();
+ generateRequisite("Instantiated By", fullDocumentLocation(classe->qmlElement()));
+ }
+
+ // Inherits.
+ QVector<RelatedClass>::ConstIterator r;
+ if (!classe->baseClasses().isEmpty()) {
+ generateStartRequisite("Inherits");
+
+ r = classe->baseClasses().constBegin();
+ int index = 0;
+ while (r != classe->baseClasses().constEnd()) {
+ if ((*r).node_) {
+ generateFullName((*r).node_, classe);
+
+ if ((*r).access_ == Node::Protected)
+ writer->writeCharacters(" (protected)");
+ else if ((*r).access_ == Node::Private)
+ writer->writeCharacters(" (private)");
+ writer->writeCharacters(comma(index++, classe->baseClasses().count()));
+ }
+ ++r;
+ }
+
+ generateEndRequisite();
+ }
+
+ // Inherited by.
+ if (!classe->derivedClasses().isEmpty()) {
+ generateStartRequisite("Inherited By");
+ generateSortedNames(classe, classe->derivedClasses());
+ generateEndRequisite();
+ }
+ }
+
+ writer->writeEndElement(); // variablelist
+ newLine();
+}
+
+/*!
+ Lists the required imports and includes.
+*/
+void DocBookGenerator::generateQmlRequisites(const QmlTypeNode *qcn)
+{
+ // From HtmlGenerator::generateQmlRequisites, but simplified: no need to store all the elements,
+ // they can be produced one by one.
+ if (!qcn)
+ return;
+
+ writer->writeStartElement(dbNamespace, "variablelist");
+ newLine();
+
+ // Module name and version (i.e. import).
+ QString logicalModuleVersion;
+ const CollectionNode *collection = qcn->logicalModule();
+
+ // skip import statement for \internal collections
+ if (!collection || !collection->isInternal() || showInternal_) {
+ logicalModuleVersion =
+ collection ? collection->logicalModuleVersion() : qcn->logicalModuleVersion();
+
+ generateRequisite("Import Statement",
+ "import " + qcn->logicalModuleName() + QLatin1Char(' ')
+ + logicalModuleVersion);
+ }
+
+ // Since and project.
+ if (!qcn->since().isEmpty())
+ generateRequisite("Since:", formatSince(qcn));
+
+ // Inherited by.
+ NodeList subs;
+ QmlTypeNode::subclasses(qcn, subs);
+ if (!subs.isEmpty()) {
+ generateStartRequisite("Inherited By:");
+ generateSortedQmlNames(qcn, subs);
+ generateEndRequisite();
+ }
+
+ // Inherits.
+ QmlTypeNode *base = qcn->qmlBaseNode();
+ while (base && base->isInternal()) {
+ base = base->qmlBaseNode();
+ }
+ if (base) {
+ const Node *otherNode = nullptr;
+ Atom a = Atom(Atom::LinkNode, CodeMarker::stringForNode(base));
+ QString link = getAutoLink(&a, qcn, &otherNode);
+
+ generateStartRequisite("Inherits:");
+ generateSimpleLink(link, base->name());
+ generateEndRequisite();
+ }
+
+ // Instantiates.
+ ClassNode *cn = (const_cast<QmlTypeNode *>(qcn))->classNode();
+ if (cn && (cn->status() != Node::Internal)) {
+ const Node *otherNode = nullptr;
+ Atom a = Atom(Atom::LinkNode, CodeMarker::stringForNode(qcn));
+ QString link = getAutoLink(&a, cn, &otherNode);
+
+ generateStartRequisite("Instantiates:");
+ generateSimpleLink(fullDocumentLocation(cn), cn->name());
+ generateEndRequisite();
+ }
+
+ writer->writeEndElement(); // variablelist
+ newLine();
+}
+
+bool DocBookGenerator::generateStatus(const Node *node)
+{
+ // From Generator::generateStatus.
+ switch (node->status()) {
+ case Node::Active:
+ // Do nothing.
+ return false;
+ case Node::Preliminary:
+ writer->writeStartElement(dbNamespace, "para");
+ writer->writeStartElement(dbNamespace, "emphasis");
+ writer->writeAttribute("role", "bold");
+ writer->writeCharacters("This " + typeString(node)
+ + " is under development and is subject to change.");
+ writer->writeEndElement(); // emphasis
+ writer->writeEndElement(); // para
+ newLine();
+ return true;
+ case Node::Deprecated:
+ writer->writeStartElement(dbNamespace, "para");
+ if (node->isAggregate()) {
+ writer->writeStartElement(dbNamespace, "emphasis");
+ writer->writeAttribute("role", "bold");
+ }
+ writer->writeCharacters("This " + typeString(node) + " is deprecated.");
+ if (node->isAggregate())
+ writer->writeEndElement(); // emphasis
+ writer->writeEndElement(); // para
+ newLine();
+ return true;
+ case Node::Obsolete:
+ writer->writeStartElement(dbNamespace, "para");
+ if (node->isAggregate()) {
+ writer->writeStartElement(dbNamespace, "emphasis");
+ writer->writeAttribute("role", "bold");
+ }
+ writer->writeCharacters("This " + typeString(node) + " is obsolete.");
+ if (node->isAggregate())
+ writer->writeEndElement(); // emphasis
+ writer->writeCharacters(" It is provided to keep old source code working. "
+ "We strongly advise against using it in new code.");
+ writer->writeEndElement(); // para
+ newLine();
+ return true;
+ case Node::Internal:
+ default:
+ return false;
+ }
+}
+
+/*!
+ Generate a list of function signatures. The function nodes
+ are in \a nodes.
+ */
+void DocBookGenerator::generateSignatureList(const NodeList &nodes)
+{
+ // From Generator::signatureList and Generator::appendSignature.
+ writer->writeStartElement(dbNamespace, "itemizedlist");
+ newLine();
+
+ NodeList::ConstIterator n = nodes.constBegin();
+ while (n != nodes.constEnd()) {
+ writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ writer->writeStartElement(dbNamespace, "para");
+
+ generateSimpleLink(currentGenerator()->fullDocumentLocation(*n),
+ (*n)->signature(false, true));
+
+ writer->writeEndElement(); // para
+ newLine();
+ writer->writeEndElement(); // itemizedlist
+ newLine();
+ ++n;
+ }
+
+ writer->writeEndElement(); // itemizedlist
+ newLine();
+}
+
+/*!
+ Generates text that explains how threadsafe and/or reentrant
+ \a node is.
+ */
+bool DocBookGenerator::generateThreadSafeness(const Node *node)
+{
+ // From Generator::generateThreadSafeness
+ Node::ThreadSafeness ts = node->threadSafeness();
+
+ const Node *reentrantNode;
+ Atom reentrantAtom = Atom(Atom::Link, "reentrant");
+ QString linkReentrant = getAutoLink(&reentrantAtom, node, &reentrantNode);
+ const Node *threadSafeNode;
+ Atom threadSafeAtom = Atom(Atom::Link, "thread-safe");
+ QString linkThreadSafe = getAutoLink(&threadSafeAtom, node, &threadSafeNode);
+
+ if (ts == Node::NonReentrant) {
+ writer->writeStartElement(dbNamespace, "warning");
+ newLine();
+ writer->writeStartElement(dbNamespace, "para");
+ writer->writeCharacters("This " + typeString(node) + " is not ");
+ generateSimpleLink(linkReentrant, "reentrant");
+ writer->writeCharacters(".");
+ writer->writeEndElement(); // para
+ newLine();
+ writer->writeEndElement(); // warning
+
+ return true;
+ }
+ if (ts == Node::Reentrant || ts == Node::ThreadSafe) {
+ writer->writeStartElement(dbNamespace, "note");
+ newLine();
+ writer->writeStartElement(dbNamespace, "para");
+
+ if (node->isAggregate()) {
+ writer->writeCharacters("All functions in this " + typeString(node) + " are ");
+ if (ts == Node::ThreadSafe)
+ generateSimpleLink(linkThreadSafe, "thread-safe");
+ else
+ generateSimpleLink(linkReentrant, "reentrant");
+
+ NodeList reentrant;
+ NodeList threadsafe;
+ NodeList nonreentrant;
+ bool exceptions = hasExceptions(node, reentrant, threadsafe, nonreentrant);
+ if (!exceptions || (ts == Node::Reentrant && !threadsafe.isEmpty())) {
+ writer->writeCharacters(".");
+ writer->writeEndElement(); // para
+ newLine();
+ } else {
+ writer->writeCharacters(" with the following exceptions:");
+ writer->writeEndElement(); // para
+ newLine();
+ writer->writeStartElement(dbNamespace, "para");
+
+ if (ts == Node::Reentrant) {
+ if (!nonreentrant.isEmpty()) {
+ writer->writeCharacters("These functions are not ");
+ generateSimpleLink(linkReentrant, "reentrant");
+ writer->writeCharacters(":");
+ writer->writeEndElement(); // para
+ newLine();
+ generateSignatureList(nonreentrant);
+ }
+ if (!threadsafe.isEmpty()) {
+ writer->writeCharacters("These functions are also ");
+ generateSimpleLink(linkThreadSafe, "thread-safe");
+ writer->writeCharacters(":");
+ writer->writeEndElement(); // para
+ newLine();
+ generateSignatureList(threadsafe);
+ }
+ } else { // thread-safe
+ if (!reentrant.isEmpty()) {
+ writer->writeCharacters("These functions are only ");
+ generateSimpleLink(linkReentrant, "reentrant");
+ writer->writeCharacters(":");
+ writer->writeEndElement(); // para
+ newLine();
+ generateSignatureList(reentrant);
+ }
+ if (!nonreentrant.isEmpty()) {
+ writer->writeCharacters("These functions are not ");
+ generateSimpleLink(linkReentrant, "reentrant");
+ writer->writeCharacters(":");
+ writer->writeEndElement(); // para
+ newLine();
+ generateSignatureList(nonreentrant);
+ }
+ }
+ }
+ } else {
+ writer->writeCharacters("This " + typeString(node) + " is ");
+ if (ts == Node::ThreadSafe)
+ generateSimpleLink(linkThreadSafe, "thread-safe");
+ else
+ generateSimpleLink(linkReentrant, "reentrant");
+ writer->writeCharacters(".");
+ writer->writeEndElement(); // para
+ newLine();
+ }
+ writer->writeEndElement(); // note
+
+ return true;
+ }
+
+ return false;
+}
+
+/*!
+ Generate the body of the documentation from the qdoc comment
+ found with the entity represented by the \a node.
+ */
+void DocBookGenerator::generateBody(const Node *node)
+{
+ // From Generator::generateBody, without warnings.
+ const FunctionNode *fn = node->isFunction() ? static_cast<const FunctionNode *>(node) : nullptr;
+
+ if (!node->hasDoc() && !node->hasSharedDoc()) {
+ /*
+ Test for special function, like a destructor or copy constructor,
+ that has no documentation.
+ */
+ if (fn) {
+ QString t;
+ if (fn->isDtor()) {
+ t = "Destroys the instance of " + fn->parent()->name() + ".";
+ if (fn->isVirtual())
+ t += " The destructor is virtual.";
+ } else if (fn->isCtor()) {
+ t = "Default constructs an instance of " + fn->parent()->name() + ".";
+ } else if (fn->isCCtor()) {
+ t = "Copy constructor.";
+ } else if (fn->isMCtor()) {
+ t = "Move-copy constructor.";
+ } else if (fn->isCAssign()) {
+ t = "Copy-assignment constructor.";
+ } else if (fn->isMAssign()) {
+ t = "Move-assignment constructor.";
+ }
+
+ if (!t.isEmpty())
+ writer->writeTextElement(dbNamespace, "para", t);
+ }
+ } else if (!node->isSharingComment()) {
+ if (fn) {
+ if (!fn->overridesThis().isEmpty())
+ generateReimplementsClause(fn);
+ }
+
+ if (!generateText(node->doc().body(), node)) {
+ if (node->isMarkedReimp())
+ return;
+ }
+
+ if (fn) {
+ if (fn->isQmlSignal())
+ generateAddendum(node, QmlSignalHandler);
+ if (fn->isPrivateSignal())
+ generateAddendum(node, PrivateSignal);
+ if (fn->isInvokable())
+ generateAddendum(node, Invokable);
+ if (fn->hasAssociatedProperties())
+ generateAddendum(node, AssociatedProperties);
+ }
+
+ // Warning generation skipped with respect to Generator::generateBody.
+ }
+
+ generateRequiredLinks(node);
+}
+
+/*!
+ Generates either a link to the project folder for example \a node, or a list
+ of links files/images if 'url.examples config' variable is not defined.
+
+ Does nothing for non-example nodes.
+*/
+void DocBookGenerator::generateRequiredLinks(const Node *node)
+{
+ // From Generator::generateRequiredLinks.
+ if (!node->isExample())
+ return;
+
+ const auto en = static_cast<const ExampleNode *>(node);
+ QString exampleUrl = Config::instance().getString(CONFIG_URL + Config::dot + CONFIG_EXAMPLES);
+
+ if (exampleUrl.isEmpty()) {
+ if (!en->noAutoList()) {
+ generateFileList(en, false); // files
+ generateFileList(en, true); // images
+ }
+ } else {
+ generateLinkToExample(en, exampleUrl);
+ }
+}
+
+/*!
+ The path to the example replaces a placeholder '\1' character if
+ one is found in the \a baseUrl string. If no such placeholder is found,
+ the path is appended to \a baseUrl, after a '/' character if \a baseUrl did
+ not already end in one.
+*/
+void DocBookGenerator::generateLinkToExample(const ExampleNode *en, const QString &baseUrl)
+{
+ // From Generator::generateLinkToExample.
+ QString exampleUrl(baseUrl);
+ QString link;
+#ifndef QT_BOOTSTRAPPED
+ link = QUrl(exampleUrl).host();
+#endif
+ if (!link.isEmpty())
+ link.prepend(" @ ");
+ link.prepend("Example project");
+
+ const QLatin1Char separator('/');
+ const QLatin1Char placeholder('\1');
+ if (!exampleUrl.contains(placeholder)) {
+ if (!exampleUrl.endsWith(separator))
+ exampleUrl += separator;
+ exampleUrl += placeholder;
+ }
+
+ // Construct a path to the example; <install path>/<example name>
+ QStringList path = QStringList()
+ << Config::instance().getString(CONFIG_EXAMPLESINSTALLPATH) << en->name();
+ path.removeAll({});
+
+ writer->writeStartElement(dbNamespace, "para");
+ writer->writeStartElement(dbNamespace, "link");
+ writer->writeAttribute(xlinkNamespace, "href",
+ exampleUrl.replace(placeholder, path.join(separator)));
+ writer->writeCharacters(link);
+ writer->writeEndElement(); // link
+ writer->writeEndElement(); // para
+ newLine();
+}
+
+/*!
+ This function is called when the documentation for an example is
+ being formatted. It outputs a list of files for the example, which
+ can be the example's source files or the list of images used by the
+ example. The images are copied into a subtree of
+ \c{...doc/html/images/used-in-examples/...}
+*/
+void DocBookGenerator::generateFileList(const ExampleNode *en, bool images)
+{
+ // From Generator::generateFileList
+ QString tag;
+ QStringList paths;
+ if (images) {
+ paths = en->images();
+ tag = "Images:";
+ } else { // files
+ paths = en->files();
+ tag = "Files:";
+ }
+ std::sort(paths.begin(), paths.end(), Generator::comparePaths);
+
+ if (paths.isEmpty())
+ return;
+
+ writer->writeStartElement(dbNamespace, "para");
+ writer->writeCharacters(tag);
+ writer->writeEndElement(); // para
+ newLine();
+
+ writer->writeStartElement(dbNamespace, "itemizedlist");
+
+ for (const auto &file : qAsConst(paths)) {
+ if (images) {
+ if (!file.isEmpty())
+ addImageToCopy(en, file);
+ } else {
+ generateExampleFilePage(en, file);
+ }
+
+ writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ writer->writeStartElement(dbNamespace, "para");
+ generateSimpleLink(file, file);
+ writer->writeEndElement(); // para
+ writer->writeEndElement(); // listitem
+ newLine();
+ }
+
+ writer->writeEndElement(); // itemizedlist
+ newLine();
+}
+
+/*!
+ Generate a file with the contents of a C++ or QML source file.
+ */
+void DocBookGenerator::generateExampleFilePage(const Node *node, const QString &file,
+ CodeMarker *marker)
+{
+ Q_UNUSED(marker);
+ // From HtmlGenerator::generateExampleFilePage.
+ if (!node->isExample())
+ return;
+
+ const auto en = static_cast<const ExampleNode *>(node);
+
+ // Store current (active) writer
+ QXmlStreamWriter *currentWriter = writer;
+ writer = startDocument(en, file);
+ generateHeader(en->fullTitle(), en->subtitle(), en);
+
+ Text text;
+ Quoter quoter;
+ Doc::quoteFromFile(en->doc().location(), quoter, file);
+ QString code = quoter.quoteTo(en->location(), QString(), QString());
+ CodeMarker *codeMarker = CodeMarker::markerForFileName(file);
+ text << Atom(codeMarker->atomType(), code);
+ Atom a(codeMarker->atomType(), code);
+ generateText(text, en);
+
+ endDocument();
+ // Restore writer
+ writer = currentWriter;
+}
+
+void DocBookGenerator::generateReimplementsClause(const FunctionNode *fn)
+{
+ // From Generator::generateReimplementsClause, without warning generation.
+ if (!fn->overridesThis().isEmpty()) {
+ if (fn->parent()->isClassNode()) {
+ auto cn = static_cast<ClassNode *>(fn->parent());
+ const FunctionNode *overrides = cn->findOverriddenFunction(fn);
+ if (overrides && !overrides->isPrivate() && !overrides->parent()->isPrivate()) {
+ if (overrides->hasDoc()) {
+ writer->writeStartElement(dbNamespace, "para");
+ writer->writeCharacters("Reimplements: ");
+ QString fullName =
+ overrides->parent()->name() + "::" + overrides->signature(false, true);
+ generateFullName(overrides->parent(), fullName, overrides);
+ writer->writeCharacters(".");
+ return;
+ }
+ }
+ const PropertyNode *sameName = cn->findOverriddenProperty(fn);
+ if (sameName && sameName->hasDoc()) {
+ writer->writeStartElement(dbNamespace, "para");
+ writer->writeCharacters("Reimplements an access function for property: ");
+ QString fullName = sameName->parent()->name() + "::" + sameName->name();
+ generateFullName(sameName->parent(), fullName, overrides);
+ writer->writeCharacters(".");
+ return;
+ }
+ }
+ }
+}
+
+void DocBookGenerator::generateAlsoList(const Node *node, CodeMarker *marker)
+{
+ Q_UNUSED(marker);
+ // From Generator::generateAlsoList.
+ QVector<Text> alsoList = node->doc().alsoList();
+ supplementAlsoList(node, alsoList);
+
+ if (!alsoList.isEmpty()) {
+ writer->writeStartElement(dbNamespace, "para");
+ writer->writeStartElement(dbNamespace, "emphasis");
+ writer->writeCharacters("See also ");
+ writer->writeEndElement(); // emphasis
+ newLine();
+
+ writer->writeStartElement(dbNamespace, "simplelist");
+ writer->writeAttribute("type", "vert");
+ writer->writeAttribute("role", "see-also");
+ for (const Text &text : alsoList) {
+ writer->writeStartElement(dbNamespace, "member");
+ generateText(text, node);
+ writer->writeEndElement(); // member
+ newLine();
+ }
+ writer->writeEndElement(); // simplelist
+ newLine();
+
+ writer->writeEndElement(); // para
+ }
+}
+
+/*!
+ Generate a list of maintainers in the output
+ */
+void DocBookGenerator::generateMaintainerList(const Aggregate *node, CodeMarker *marker)
+{
+ Q_UNUSED(marker);
+ // From Generator::generateMaintainerList.
+ QStringList sl = getMetadataElements(node, "maintainer");
+
+ if (!sl.isEmpty()) {
+ writer->writeStartElement(dbNamespace, "para");
+ writer->writeStartElement(dbNamespace, "emphasis");
+ writer->writeCharacters("Maintained by: ");
+ writer->writeEndElement(); // emphasis
+ newLine();
+
+ writer->writeStartElement(dbNamespace, "simplelist");
+ writer->writeAttribute("type", "vert");
+ writer->writeAttribute("role", "maintainer");
+ for (int i = 0; i < sl.size(); ++i) {
+ writer->writeStartElement(dbNamespace, "member");
+ writer->writeCharacters(sl.at(i));
+ writer->writeEndElement(); // member
+ newLine();
+ }
+ writer->writeEndElement(); // simplelist
+ newLine();
+
+ writer->writeEndElement(); // para
+ }
+}
+
+/*!
+ Open a new file to write XML contents, including the DocBook
+ opening tag.
+ */
+QXmlStreamWriter *DocBookGenerator::startGenericDocument(const Node *node, const QString &fileName)
+{
+ QFile *outFile = openSubPageFile(node, fileName);
+ writer = new QXmlStreamWriter(outFile);
+ writer->setAutoFormatting(false); // We need a precise handling of line feeds.
+
+ writer->writeStartDocument();
+ newLine();
+ writer->writeNamespace(dbNamespace, "db");
+ writer->writeNamespace(xlinkNamespace, "xlink");
+ writer->writeStartElement(dbNamespace, "article");
+ writer->writeAttribute("version", "5.2");
+ if (!naturalLanguage.isEmpty())
+ writer->writeAttribute("xml:lang", naturalLanguage);
+ newLine();
+
+ // Empty the section stack for the new document.
+ sectionLevels.resize(0);
+
+ return writer;
+}
+
+QXmlStreamWriter *DocBookGenerator::startDocument(const Node *node)
+{
+ QString fileName = Generator::fileName(node, fileExtension());
+ return startGenericDocument(node, fileName);
+}
+
+QXmlStreamWriter *DocBookGenerator::startDocument(const ExampleNode *en, const QString &file)
+{
+ QString fileName = linkForExampleFile(file, en);
+ return startGenericDocument(en, fileName);
+}
+
+void DocBookGenerator::endDocument()
+{
+ writer->writeEndElement(); // article
+ writer->writeEndDocument();
+ writer->device()->close();
+ delete writer;
+ writer = nullptr;
+}
+
+/*!
+ Generate a reference page for the C++ class, namespace, or
+ header file documented in \a node.
+ */
+void DocBookGenerator::generateCppReferencePage(Node *node)
+{
+ // Based on HtmlGenerator::generateCppReferencePage.
+ Q_ASSERT(node->isAggregate());
+ const auto aggregate = static_cast<const Aggregate *>(node);
+
+ QString title;
+ QString rawTitle;
+ QString fullTitle;
+ const NamespaceNode *ns = nullptr;
+ if (aggregate->isNamespace()) {
+ rawTitle = aggregate->plainName();
+ fullTitle = aggregate->plainFullName();
+ title = rawTitle + " Namespace";
+ ns = static_cast<const NamespaceNode *>(aggregate);
+ } else if (aggregate->isClass()) {
+ rawTitle = aggregate->plainName();
+ QString templateDecl = node->templateDecl();
+ if (!templateDecl.isEmpty())
+ fullTitle = QString("%1 %2 ").arg(templateDecl, aggregate->typeWord(false));
+ fullTitle += aggregate->plainFullName();
+ title = rawTitle + QLatin1Char(' ') + aggregate->typeWord(true);
+ }
+
+ QString subtitleText;
+ if (rawTitle != fullTitle)
+ subtitleText = fullTitle;
+
+ // Start producing the DocBook file.
+ writer = startDocument(node);
+
+ // Info container.
+ generateHeader(title, subtitleText, aggregate);
+
+ generateRequisites(aggregate);
+ generateStatus(aggregate);
+
+ // Element synopsis.
+ generateDocBookSynopsis(node);
+
+ // Actual content.
+ if (!aggregate->doc().isEmpty()) {
+ startSection(registerRef("details"), "Detailed Description");
+
+ generateBody(aggregate);
+ generateAlsoList(aggregate);
+ generateMaintainerList(aggregate);
+
+ endSection();
+ }
+
+ Sections sections(const_cast<Aggregate *>(aggregate));
+ SectionVector *sectionVector =
+ ns ? &sections.stdDetailsSections() : &sections.stdCppClassDetailsSections();
+ SectionVector::ConstIterator section = sectionVector->constBegin();
+ while (section != sectionVector->constEnd()) {
+ bool headerGenerated = false;
+ NodeVector::ConstIterator member = section->members().constBegin();
+ while (member != section->members().constEnd()) {
+ if ((*member)->access() == Node::Private) { // ### check necessary?
+ ++member;
+ continue;
+ }
+
+ if (!headerGenerated) {
+ // Equivalent to h2
+ startSection(registerRef(section->title().toLower()), section->title());
+ headerGenerated = true;
+ }
+
+ if ((*member)->nodeType() != Node::Class) {
+ // This function starts its own section.
+ generateDetailedMember(*member, aggregate);
+ } else {
+ startSectionBegin();
+ writer->writeCharacters("class ");
+ generateFullName(*member, aggregate);
+ startSectionEnd();
+ generateBrief(*member);
+ endSection();
+ }
+
+ ++member;
+ }
+
+ if (headerGenerated)
+ endSection();
+ ++section;
+ }
+
+ generateObsoleteMembers(sections);
+
+ endDocument();
+}
+
+void DocBookGenerator::generateSynopsisInfo(const QString &key, const QString &value)
+{
+ writer->writeStartElement(dbNamespace, "synopsisinfo");
+ writer->writeAttribute(dbNamespace, "role", key);
+ writer->writeCharacters(value);
+ writer->writeEndElement(); // synopsisinfo
+ newLine();
+}
+
+void DocBookGenerator::generateModifier(const QString &value)
+{
+ writer->writeTextElement(dbNamespace, "modifier", value);
+ newLine();
+}
+
+/*!
+ Generate the metadata for the given \a node in DocBook.
+ */
+void DocBookGenerator::generateDocBookSynopsis(const Node *node)
+{
+ if (!node)
+ return;
+
+ // From Generator::generateStatus, HtmlGenerator::generateRequisites,
+ // Generator::generateThreadSafeness, QDocIndexFiles::generateIndexSection.
+
+ // This function is the only place where DocBook extensions are used.
+ if (config->getBool(CONFIG_DOCBOOKEXTENSIONS))
+ return;
+
+ // Nothing to export in some cases.
+ if (node->isGroup() || node->isGroup() || node->isPropertyGroup() || node->isModule()
+ || node->isJsModule() || node->isQmlModule() || node->isPageNode())
+ return;
+
+ // Cast the node to several subtypes (null pointer if the node is not of the required type).
+ const Aggregate *aggregate =
+ node->isAggregate() ? static_cast<const Aggregate *>(node) : nullptr;
+ const ClassNode *classNode = node->isClass() ? static_cast<const ClassNode *>(node) : nullptr;
+ const FunctionNode *functionNode =
+ node->isFunction() ? static_cast<const FunctionNode *>(node) : nullptr;
+ const PropertyNode *propertyNode =
+ node->isProperty() ? static_cast<const PropertyNode *>(node) : nullptr;
+ const VariableNode *variableNode =
+ node->isVariable() ? static_cast<const VariableNode *>(node) : nullptr;
+ const EnumNode *enumNode = node->isEnumType() ? static_cast<const EnumNode *>(node) : nullptr;
+ const QmlPropertyNode *qpn =
+ node->isQmlProperty() ? static_cast<const QmlPropertyNode *>(node) : nullptr;
+ const QmlTypeNode *qcn = node->isQmlType() ? static_cast<const QmlTypeNode *>(node) : nullptr;
+ // Typedefs are ignored, as they correspond to enums.
+ // Groups and modules are ignored.
+ // Documents are ignored, they have no interesting metadata.
+
+ // Start the synopsis tag.
+ QString synopsisTag = nodeToSynopsisTag(node);
+ writer->writeStartElement(dbNamespace, synopsisTag);
+ newLine();
+
+ // Name and basic properties of each tag (like types and parameters).
+ if (node->isClass()) {
+ writer->writeStartElement(dbNamespace, "ooclass");
+ writer->writeTextElement(dbNamespace, "classname", node->plainName());
+ writer->writeEndElement(); // ooclass
+ newLine();
+ } else if (node->isNamespace()) {
+ writer->writeTextElement(dbNamespace, "namespacename", node->plainName());
+ newLine();
+ } else if (node->isQmlType()) {
+ writer->writeStartElement(dbNamespace, "ooclass");
+ writer->writeTextElement(dbNamespace, "classname", node->plainName());
+ writer->writeEndElement(); // ooclass
+ newLine();
+ if (!qcn->groupNames().isEmpty())
+ writer->writeAttribute("groups", qcn->groupNames().join(QLatin1Char(',')));
+ } else if (node->isProperty()) {
+ writer->writeTextElement(dbNamespace, "modifier", "(Qt property)");
+ newLine();
+ writer->writeTextElement(dbNamespace, "type", propertyNode->dataType());
+ newLine();
+ writer->writeTextElement(dbNamespace, "varname", node->plainName());
+ newLine();
+ } else if (node->isVariable()) {
+ if (variableNode->isStatic()) {
+ writer->writeTextElement(dbNamespace, "modifier", "static");
+ newLine();
+ }
+ writer->writeTextElement(dbNamespace, "type", variableNode->dataType());
+ newLine();
+ writer->writeTextElement(dbNamespace, "varname", node->plainName());
+ newLine();
+ } else if (node->isEnumType()) {
+ writer->writeTextElement(dbNamespace, "enumname", node->plainName());
+ newLine();
+ } else if (node->isQmlProperty()) {
+ QString name = node->name();
+ if (qpn->isAttached())
+ name.prepend(qpn->element() + QLatin1Char('.'));
+
+ writer->writeTextElement(dbNamespace, "type", qpn->dataType());
+ newLine();
+ writer->writeTextElement(dbNamespace, "varname", name);
+ newLine();
+
+ if (qpn->isAttached()) {
+ writer->writeTextElement(dbNamespace, "modifier", "attached");
+ newLine();
+ }
+ if ((const_cast<QmlPropertyNode *>(qpn))->isWritable()) {
+ writer->writeTextElement(dbNamespace, "modifier", "writable");
+ newLine();
+ }
+
+ if (qpn->isReadOnly()) {
+ generateModifier("[read-only]");
+ newLine();
+ }
+ if (qpn->isDefault()) {
+ generateModifier("[default]");
+ newLine();
+ }
+ } else if (node->isFunction()) {
+ if (functionNode->virtualness() != "non")
+ generateModifier("virtual");
+ if (functionNode->isConst())
+ generateModifier("const");
+ if (functionNode->isStatic())
+ generateModifier("static");
+
+ if (!functionNode->isMacro()) {
+ if (functionNode->returnType() == "void")
+ writer->writeEmptyElement(dbNamespace, "void");
+ else
+ writer->writeTextElement(dbNamespace, "type", functionNode->returnType());
+ newLine();
+ }
+ // Remove two characters from the plain name to only get the name
+ // of the method without parentheses.
+ writer->writeTextElement(dbNamespace, "methodname", node->plainName().chopped(2));
+ newLine();
+
+ if (functionNode->isOverload())
+ generateModifier("overload");
+ if (functionNode->isDefault())
+ generateModifier("default");
+ if (functionNode->isFinal())
+ generateModifier("final");
+ if (functionNode->isOverride())
+ generateModifier("override");
+
+ if (!functionNode->isMacro() && functionNode->parameters().isEmpty()) {
+ writer->writeEmptyElement(dbNamespace, "void");
+ newLine();
+ }
+
+ const Parameters &lp = functionNode->parameters();
+ for (int i = 0; i < lp.count(); ++i) {
+ const Parameter &parameter = lp.at(i);
+ writer->writeStartElement(dbNamespace, "methodparam");
+ newLine();
+ writer->writeTextElement(dbNamespace, "type", parameter.type());
+ newLine();
+ writer->writeTextElement(dbNamespace, "parameter", parameter.name());
+ newLine();
+ if (!parameter.defaultValue().isEmpty()) {
+ writer->writeTextElement(dbNamespace, "initializer", parameter.defaultValue());
+ newLine();
+ }
+ writer->writeEndElement(); // methodparam
+ newLine();
+ }
+
+ generateSynopsisInfo("meta", functionNode->metanessString());
+
+ if (functionNode->isOverload())
+ generateSynopsisInfo("overload-number",
+ QString::number(functionNode->overloadNumber()));
+
+ if (functionNode->isRef())
+ generateSynopsisInfo("refness", QString::number(1));
+ else if (functionNode->isRefRef())
+ generateSynopsisInfo("refness", QString::number(2));
+
+ if (functionNode->hasAssociatedProperties()) {
+ QStringList associatedProperties;
+ const NodeList &nodes = functionNode->associatedProperties();
+ for (const Node *n : nodes) {
+ const auto pn = static_cast<const PropertyNode *>(n);
+ associatedProperties << pn->name();
+ }
+ associatedProperties.sort();
+ generateSynopsisInfo("associated-property",
+ associatedProperties.join(QLatin1Char(',')));
+ }
+
+ QString signature = functionNode->signature(false, false);
+ // 'const' is already part of FunctionNode::signature()
+ if (functionNode->isFinal())
+ signature += " final";
+ if (functionNode->isOverride())
+ signature += " override";
+ if (functionNode->isPureVirtual())
+ signature += " = 0";
+ else if (functionNode->isDefault())
+ signature += " = default";
+ generateSynopsisInfo("signature", signature);
+ } else {
+ node->doc().location().warning(tr("Unexpected node type in generateDocBookSynopsis: %1")
+ .arg(node->nodeTypeString()));
+ newLine();
+ }
+
+ // Accessibility status.
+ if (!node->isPageNode() && !node->isCollectionNode()) {
+ switch (node->access()) {
+ case Node::Public:
+ generateSynopsisInfo("access", "public");
+ break;
+ case Node::Protected:
+ generateSynopsisInfo("access", "protected");
+ break;
+ case Node::Private:
+ generateSynopsisInfo("access", "private");
+ break;
+ default:
+ break;
+ }
+ if (node->isAbstract())
+ generateSynopsisInfo("abstract", "true");
+ }
+
+ // Status.
+ switch (node->status()) {
+ case Node::Active:
+ generateSynopsisInfo("status", "active");
+ break;
+ case Node::Preliminary:
+ generateSynopsisInfo("status", "preliminary");
+ break;
+ case Node::Deprecated:
+ generateSynopsisInfo("status", "deprecated");
+ break;
+ case Node::Obsolete:
+ generateSynopsisInfo("status", "obsolete");
+ break;
+ case Node::Internal:
+ generateSynopsisInfo("status", "internal");
+ break;
+ default:
+ generateSynopsisInfo("status", "main");
+ break;
+ }
+
+ // C++ classes and name spaces.
+ if (aggregate) {
+ // Includes.
+ if (!aggregate->includeFiles().isEmpty()) {
+ for (const QString &include : aggregate->includeFiles())
+ generateSynopsisInfo("headers", include);
+ }
+
+ // Since and project.
+ if (!aggregate->since().isEmpty())
+ generateSynopsisInfo("since", formatSince(aggregate));
+
+ if (aggregate->nodeType() == Node::Class || aggregate->nodeType() == Node::Namespace) {
+ // QT variable.
+ if (!aggregate->physicalModuleName().isEmpty()) {
+ const CollectionNode *cn =
+ qdb_->getCollectionNode(aggregate->physicalModuleName(), Node::Module);
+ if (cn && !cn->qtVariable().isEmpty())
+ generateSynopsisInfo("qmake", "QT += " + cn->qtVariable());
+ }
+ }
+
+ if (aggregate->nodeType() == Node::Class) {
+ // Instantiated by.
+ auto *classe = const_cast<ClassNode *>(static_cast<const ClassNode *>(aggregate));
+ if (classe->qmlElement() != nullptr && classe->status() != Node::Internal) {
+ const Node *otherNode = nullptr;
+ Atom a = Atom(Atom::LinkNode, CodeMarker::stringForNode(classe->qmlElement()));
+ QString link = getAutoLink(&a, aggregate, &otherNode);
+
+ writer->writeStartElement(dbNamespace, "synopsisinfo");
+ writer->writeAttribute(dbNamespace, "role", "instantiatedBy");
+ generateSimpleLink(link, classe->qmlElement()->name());
+ writer->writeEndElement(); // synopsisinfo
+ newLine();
+ }
+
+ // Inherits.
+ QVector<RelatedClass>::ConstIterator r;
+ if (!classe->baseClasses().isEmpty()) {
+ writer->writeStartElement(dbNamespace, "synopsisinfo");
+ writer->writeAttribute(dbNamespace, "role", "inherits");
+
+ r = classe->baseClasses().constBegin();
+ int index = 0;
+ while (r != classe->baseClasses().constEnd()) {
+ if ((*r).node_) {
+ generateFullName((*r).node_, classe);
+
+ if ((*r).access_ == Node::Protected) {
+ writer->writeCharacters(" (protected)");
+ } else if ((*r).access_ == Node::Private) {
+ writer->writeCharacters(" (private)");
+ }
+ writer->writeCharacters(comma(index++, classe->baseClasses().count()));
+ }
+ ++r;
+ }
+
+ writer->writeEndElement(); // synopsisinfo
+ newLine();
+ }
+
+ // Inherited by.
+ if (!classe->derivedClasses().isEmpty()) {
+ writer->writeStartElement(dbNamespace, "synopsisinfo");
+ writer->writeAttribute(dbNamespace, "role", "inheritedBy");
+ generateSortedNames(classe, classe->derivedClasses());
+ writer->writeEndElement(); // synopsisinfo
+ newLine();
+ }
+ }
+ }
+
+ // QML types.
+ if (qcn) {
+ // Module name and version (i.e. import).
+ QString logicalModuleVersion;
+ const CollectionNode *collection =
+ qdb_->getCollectionNode(qcn->logicalModuleName(), qcn->nodeType());
+ if (collection)
+ logicalModuleVersion = collection->logicalModuleVersion();
+ else
+ logicalModuleVersion = qcn->logicalModuleVersion();
+
+ generateSynopsisInfo("import",
+ "import " + qcn->logicalModuleName() + QLatin1Char(' ')
+ + logicalModuleVersion);
+
+ // Since and project.
+ if (!qcn->since().isEmpty())
+ generateSynopsisInfo("since", formatSince(qcn));
+
+ // Inherited by.
+ NodeList subs;
+ QmlTypeNode::subclasses(qcn, subs);
+ if (!subs.isEmpty()) {
+ writer->writeTextElement(dbNamespace, "synopsisinfo");
+ writer->writeAttribute(dbNamespace, "role", "inheritedBy");
+ generateSortedQmlNames(qcn, subs);
+ writer->writeEndElement(); // synopsisinfo
+ newLine();
+ }
+
+ // Inherits.
+ QmlTypeNode *base = qcn->qmlBaseNode();
+ while (base && base->isInternal())
+ base = base->qmlBaseNode();
+ if (base) {
+ const Node *otherNode = nullptr;
+ Atom a = Atom(Atom::LinkNode, CodeMarker::stringForNode(base));
+ QString link = getAutoLink(&a, base, &otherNode);
+
+ writer->writeTextElement(dbNamespace, "synopsisinfo");
+ writer->writeAttribute(dbNamespace, "role", "inherits");
+ generateSimpleLink(link, base->name());
+ writer->writeEndElement(); // synopsisinfo
+ newLine();
+ }
+
+ // Instantiates.
+ ClassNode *cn = (const_cast<QmlTypeNode *>(qcn))->classNode();
+ if (cn && (cn->status() != Node::Internal)) {
+ const Node *otherNode = nullptr;
+ Atom a = Atom(Atom::LinkNode, CodeMarker::stringForNode(qcn));
+ QString link = getAutoLink(&a, cn, &otherNode);
+
+ writer->writeTextElement(dbNamespace, "synopsisinfo");
+ writer->writeAttribute(dbNamespace, "role", "instantiates");
+ generateSimpleLink(link, cn->name());
+ writer->writeEndElement(); // synopsisinfo
+ newLine();
+ }
+ }
+
+ // Thread safeness.
+ switch (node->threadSafeness()) {
+ case Node::UnspecifiedSafeness:
+ generateSynopsisInfo("threadsafeness", "unspecified");
+ break;
+ case Node::NonReentrant:
+ generateSynopsisInfo("threadsafeness", "non-reentrant");
+ break;
+ case Node::Reentrant:
+ generateSynopsisInfo("threadsafeness", "reentrant");
+ break;
+ case Node::ThreadSafe:
+ generateSynopsisInfo("threadsafeness", "thread safe");
+ break;
+ default:
+ generateSynopsisInfo("threadsafeness", "unspecified");
+ break;
+ }
+
+ // Module.
+ if (!node->physicalModuleName().isEmpty())
+ generateSynopsisInfo("module", node->physicalModuleName());
+
+ // Group.
+ if (classNode && !classNode->groupNames().isEmpty()) {
+ generateSynopsisInfo("groups", classNode->groupNames().join(QLatin1Char(',')));
+ } else if (qcn && !qcn->groupNames().isEmpty()) {
+ generateSynopsisInfo("groups", qcn->groupNames().join(QLatin1Char(',')));
+ }
+
+ // Properties.
+ if (propertyNode) {
+ for (const Node *fnNode : propertyNode->getters()) {
+ if (fnNode) {
+ const auto funcNode = static_cast<const FunctionNode *>(fnNode);
+ generateSynopsisInfo("getter", funcNode->name());
+ }
+ }
+ for (const Node *fnNode : propertyNode->setters()) {
+ if (fnNode) {
+ const auto funcNode = static_cast<const FunctionNode *>(fnNode);
+ generateSynopsisInfo("setter", funcNode->name());
+ }
+ }
+ for (const Node *fnNode : propertyNode->resetters()) {
+ if (fnNode) {
+ const auto funcNode = static_cast<const FunctionNode *>(fnNode);
+ generateSynopsisInfo("resetter", funcNode->name());
+ }
+ }
+ for (const Node *fnNode : propertyNode->notifiers()) {
+ if (fnNode) {
+ const auto funcNode = static_cast<const FunctionNode *>(fnNode);
+ generateSynopsisInfo("notifier", funcNode->name());
+ }
+ }
+ }
+
+ // Enums and typedefs.
+ if (enumNode) {
+ for (const EnumItem &item : enumNode->items()) {
+ writer->writeStartElement(dbNamespace, "enumitem");
+ newLine();
+ writer->writeAttribute(dbNamespace, "enumidentifier", item.name());
+ newLine();
+ writer->writeAttribute(dbNamespace, "enumvalue", item.value());
+ newLine();
+ writer->writeEndElement(); // enumitem
+ newLine();
+ }
+ }
+
+ writer->writeEndElement(); // nodeToSynopsisTag (like classsynopsis)
+ newLine();
+
+ // The typedef associated to this enum.
+ if (enumNode && enumNode->flagsType()) {
+ writer->writeStartElement(dbNamespace, "typedefsynopsis");
+ newLine();
+
+ writer->writeTextElement(dbNamespace, "typedefname",
+ enumNode->flagsType()->fullDocumentName());
+
+ writer->writeEndElement(); // typedefsynopsis
+ newLine();
+ }
+}
+
+QString taggedNode(const Node *node)
+{
+ // From CodeMarker::taggedNode, but without the tag part (i.e. only the QML specific case
+ // remaining).
+ // TODO: find a better name for this.
+ if (node->nodeType() == Node::QmlType && node->name().startsWith(QLatin1String("QML:")))
+ return node->name().mid(4);
+ return node->name();
+}
+
+/*!
+ Parses a string with method/variable name and (return) type
+ to include type tags.
+ */
+void DocBookGenerator::typified(const QString &string, const Node *relative, bool trailingSpace,
+ bool generateType)
+{
+ // Adapted from CodeMarker::typified and HtmlGenerator::highlightedCode.
+ // Note: CppCodeMarker::markedUpIncludes is not needed for DocBook, as this part is natively
+ // generated as DocBook. Hence, there is no need to reimplement <@headerfile> from
+ // HtmlGenerator::highlightedCode.
+ QString result;
+ QString pendingWord;
+
+ for (int i = 0; i <= string.size(); ++i) {
+ QChar ch;
+ if (i != string.size())
+ ch = string.at(i);
+
+ QChar lower = ch.toLower();
+ if ((lower >= QLatin1Char('a') && lower <= QLatin1Char('z')) || ch.digitValue() >= 0
+ || ch == QLatin1Char('_') || ch == QLatin1Char(':')) {
+ pendingWord += ch;
+ } else {
+ if (!pendingWord.isEmpty()) {
+ bool isProbablyType = (pendingWord != QLatin1String("const"));
+ if (generateType && isProbablyType) {
+ // Flush the current buffer.
+ writer->writeCharacters(result);
+ result.truncate(0);
+
+ // Add the link, logic from HtmlGenerator::highlightedCode.
+ const Node *n = qdb_->findTypeNode(pendingWord, relative, Node::DontCare);
+ QString href;
+ if (!(n && (n->isQmlBasicType() || n->isJsBasicType()))
+ || (relative
+ && (relative->genus() == n->genus() || Node::DontCare == n->genus()))) {
+ href = linkForNode(n, relative);
+ }
+
+ writer->writeStartElement(dbNamespace, "type");
+ if (href.isEmpty())
+ writer->writeCharacters(pendingWord);
+ else
+ generateSimpleLink(href, pendingWord);
+ writer->writeEndElement(); // type
+ } else {
+ result += pendingWord;
+ }
+ }
+ pendingWord.clear();
+
+ switch (ch.unicode()) {
+ case '\0':
+ break;
+ // This only breaks out of the switch, not the loop. This means that the loop
+ // deliberately overshoots by one character.
+ case '&':
+ result += QLatin1String("&amp;");
+ break;
+ case '<':
+ result += QLatin1String("&lt;");
+ break;
+ case '>':
+ result += QLatin1String("&gt;");
+ break;
+ case '\'':
+ result += QLatin1String("&apos;");
+ break;
+ case '"':
+ result += QLatin1String("&quot;");
+ break;
+ default:
+ result += ch;
+ }
+ }
+ }
+
+ if (trailingSpace && string.size()) {
+ if (!string.endsWith(QLatin1Char('*')) && !string.endsWith(QLatin1Char('&')))
+ result += QLatin1Char(' ');
+ }
+
+ writer->writeCharacters(result);
+}
+
+void DocBookGenerator::generateSynopsisName(const Node *node, const Node *relative,
+ bool generateNameLink)
+{
+ // Implements the rewriting of <@link> from HtmlGenerator::highlightedCode, only due to calls to
+ // CodeMarker::linkTag in CppCodeMarker::markedUpSynopsis.
+ QString name = taggedNode(node);
+
+ if (!generateNameLink) {
+ writer->writeCharacters(name);
+ return;
+ }
+
+ writer->writeStartElement(dbNamespace, "emphasis");
+ writer->writeAttribute("role", "bold");
+ generateSimpleLink(linkForNode(node, relative), name);
+ writer->writeEndElement(); // emphasis
+}
+
+void DocBookGenerator::generateParameter(const Parameter &parameter, const Node *relative,
+ bool generateExtra, bool generateType)
+{
+ const QString &pname = parameter.name();
+ const QString &ptype = parameter.type();
+ QString paramName;
+ if (!pname.isEmpty()) {
+ typified(ptype, relative, true, generateType);
+ paramName = pname;
+ } else {
+ paramName = ptype;
+ }
+ if (generateExtra || pname.isEmpty()) {
+ // Look for the _ character in the member name followed by a number (or n):
+ // this is intended to be rendered as a subscript.
+ QRegExp sub("([a-z]+)_([0-9]+|n)");
+
+ writer->writeStartElement(dbNamespace, "emphasis");
+ if (sub.indexIn(paramName) != -1) {
+ writer->writeCharacters(sub.cap(0));
+ writer->writeStartElement(dbNamespace, "sub");
+ writer->writeCharacters(sub.cap(1));
+ writer->writeEndElement(); // sub
+ } else {
+ writer->writeCharacters(paramName);
+ }
+ writer->writeEndElement(); // emphasis
+ }
+
+ const QString &pvalue = parameter.defaultValue();
+ if (generateExtra && !pvalue.isEmpty())
+ writer->writeCharacters(" = " + pvalue);
+}
+
+void DocBookGenerator::generateSynopsis(const Node *node, const Node *relative,
+ Section::Style style)
+{
+ // From HtmlGenerator::generateSynopsis (conditions written as booleans).
+ const bool generateExtra = style != Section::AllMembers;
+ const bool generateType = style != Section::Details;
+ const bool generateNameLink = style != Section::Details;
+
+ // From CppCodeMarker::markedUpSynopsis, reversed the generation of "extra" and "synopsis".
+ const int MaxEnumValues = 6;
+
+ // First generate the extra part if needed (condition from HtmlGenerator::generateSynopsis).
+ if (generateExtra) {
+ if (node->nodeType() == Node::Function) {
+ const auto func = static_cast<const FunctionNode *>(node);
+ if (style != Section::Summary && style != Section::Accessors) {
+ QStringList bracketed;
+ if (func->isStatic()) {
+ bracketed += "static";
+ } else if (!func->isNonvirtual()) {
+ if (func->isFinal())
+ bracketed += "final";
+ if (func->isOverride())
+ bracketed += "override";
+ if (func->isPureVirtual())
+ bracketed += "pure";
+ bracketed += "virtual";
+ }
+
+ if (func->access() == Node::Protected)
+ bracketed += "protected";
+ else if (func->access() == Node::Private)
+ bracketed += "private";
+
+ if (func->isSignal())
+ bracketed += "signal";
+ else if (func->isSlot())
+ bracketed += "slot";
+
+ if (!bracketed.isEmpty())
+ writer->writeCharacters(QLatin1Char('[') + bracketed.join(' ')
+ + QStringLiteral("] "));
+ }
+ }
+
+ if (style == Section::Summary) {
+ QString extra;
+ if (node->isPreliminary())
+ extra = "(preliminary) ";
+ else if (node->isDeprecated())
+ extra = "(deprecated) ";
+ else if (node->isObsolete())
+ extra = "(obsolete) ";
+
+ if (!extra.isEmpty())
+ writer->writeCharacters(extra);
+ }
+ }
+
+ // Then generate the synopsis.
+ if (style == Section::Details) {
+ if (!node->isRelatedNonmember() && !node->isProxyNode() && !node->parent()->name().isEmpty()
+ && !node->parent()->isHeader() && !node->isProperty() && !node->isQmlNode()
+ && !node->isJsNode()) {
+ writer->writeCharacters(taggedNode(node->parent()) + "::");
+ }
+ }
+
+ switch (node->nodeType()) {
+ case Node::Namespace:
+ writer->writeCharacters("namespace ");
+ generateSynopsisName(node, relative, generateNameLink);
+ break;
+ case Node::Class:
+ writer->writeCharacters("class ");
+ generateSynopsisName(node, relative, generateNameLink);
+ break;
+ case Node::Function: {
+ const auto func = (const FunctionNode *)node;
+
+ // First, the part coming before the name.
+ if (style == Section::Summary || style == Section::Accessors) {
+ if (!func->isNonvirtual())
+ writer->writeCharacters(QStringLiteral("virtual "));
+ }
+
+ // Name and parameters.
+ if (style != Section::AllMembers && !func->returnType().isEmpty())
+ typified(func->returnType(), relative, true, generateType);
+ generateSynopsisName(node, relative, generateNameLink);
+
+ if (!func->isMacroWithoutParams()) {
+ writer->writeCharacters(QStringLiteral("("));
+ if (!func->parameters().isEmpty()) {
+ const Parameters &parameters = func->parameters();
+ for (int i = 0; i < parameters.count(); i++) {
+ if (i > 0)
+ writer->writeCharacters(QStringLiteral(", "));
+ generateParameter(parameters.at(i), relative, generateExtra, generateType);
+ }
+ }
+ writer->writeCharacters(QStringLiteral(")"));
+ }
+ if (func->isConst())
+ writer->writeCharacters(QStringLiteral(" const"));
+
+ if (style == Section::Summary || style == Section::Accessors) {
+ // virtual is prepended, if needed.
+ QString synopsis;
+ if (func->isFinal())
+ synopsis += QStringLiteral(" final");
+ if (func->isOverride())
+ synopsis += QStringLiteral(" override");
+ if (func->isPureVirtual())
+ synopsis += QStringLiteral(" = 0");
+ if (func->isRef())
+ synopsis += QStringLiteral(" &");
+ else if (func->isRefRef())
+ synopsis += QStringLiteral(" &&");
+ writer->writeCharacters(synopsis);
+ } else if (style == Section::AllMembers) {
+ if (!func->returnType().isEmpty() && func->returnType() != "void") {
+ writer->writeCharacters(QStringLiteral(" : "));
+ typified(func->returnType(), relative, false, generateType);
+ }
+ } else {
+ QString synopsis;
+ if (func->isRef())
+ synopsis += QStringLiteral(" &");
+ else if (func->isRefRef())
+ synopsis += QStringLiteral(" &&");
+ writer->writeCharacters(synopsis);
+ }
+ } break;
+ case Node::Enum: {
+ const auto enume = static_cast<const EnumNode *>(node);
+ writer->writeCharacters(QStringLiteral("enum "));
+ generateSynopsisName(node, relative, generateNameLink);
+
+ QString synopsis;
+ if (style == Section::Summary) {
+ synopsis += " { ";
+
+ QStringList documentedItems = enume->doc().enumItemNames();
+ if (documentedItems.isEmpty()) {
+ const auto &enumItems = enume->items();
+ for (const auto &item : enumItems)
+ documentedItems << item.name();
+ }
+ const QStringList omitItems = enume->doc().omitEnumItemNames();
+ for (const auto &item : omitItems)
+ documentedItems.removeAll(item);
+
+ if (documentedItems.size() > MaxEnumValues) {
+ // Take the last element and keep it safe, then elide the surplus.
+ const QString last = documentedItems.last();
+ documentedItems = documentedItems.mid(0, MaxEnumValues - 1);
+ documentedItems += "&#x2026;"; // Ellipsis: in HTML, &hellip;.
+ documentedItems += last;
+ }
+ synopsis += documentedItems.join(QLatin1String(", "));
+
+ if (!documentedItems.isEmpty())
+ synopsis += QLatin1Char(' ');
+ synopsis += QLatin1Char('}');
+ }
+ writer->writeCharacters(synopsis);
+ } break;
+ case Node::Typedef: {
+ const auto typedeff = static_cast<const TypedefNode *>(node);
+ if (typedeff->associatedEnum())
+ writer->writeCharacters("flags ");
+ else
+ writer->writeCharacters("typedef ");
+ generateSynopsisName(node, relative, generateNameLink);
+ } break;
+ case Node::Property: {
+ const auto property = static_cast<const PropertyNode *>(node);
+ generateSynopsisName(node, relative, generateNameLink);
+ writer->writeCharacters(" : ");
+ typified(property->qualifiedDataType(), relative, false, generateType);
+ } break;
+ case Node::Variable: {
+ const auto variable = static_cast<const VariableNode *>(node);
+ if (style == Section::AllMembers) {
+ generateSynopsisName(node, relative, generateNameLink);
+ writer->writeCharacters(" : ");
+ typified(variable->dataType(), relative, false, generateType);
+ } else {
+ typified(variable->leftType(), relative, false, generateType);
+ writer->writeCharacters(" ");
+ generateSynopsisName(node, relative, generateNameLink);
+ writer->writeCharacters(variable->rightType());
+ }
+ } break;
+ default:
+ generateSynopsisName(node, relative, generateNameLink);
+ }
+}
+
+void DocBookGenerator::generateEnumValue(const QString &enumValue, const Node *relative)
+{
+ // From CppCodeMarker::markedUpEnumValue, simplifications from Generator::plainCode (removing
+ // <@op>). With respect to CppCodeMarker::markedUpEnumValue, the order of generation of parents
+ // must be reversed so that they are processed in the order
+ if (!relative->isEnumType()) {
+ writer->writeCharacters(enumValue);
+ return;
+ }
+
+ QVector<const Node *> parents;
+ const Node *node = relative->parent();
+ while (node->parent()) {
+ parents.prepend(node);
+ if (node->parent() == relative || node->parent()->name().isEmpty())
+ break;
+ node = node->parent();
+ }
+
+ writer->writeStartElement(dbNamespace, "code");
+ for (auto parent : parents) {
+ generateSynopsisName(parent, relative, true);
+ writer->writeCharacters("::");
+ }
+ writer->writeCharacters(enumValue);
+ writer->writeEndElement(); // code
+}
+
+/*!
+ If the node is an overloaded signal, and a node with an
+ example on how to connect to it
+
+ Someone didn't finish writing this comment, and I don't know what this
+ function is supposed to do, so I have not tried to complete the comment
+ yet.
+ */
+void DocBookGenerator::generateOverloadedSignal(const Node *node)
+{
+ // From Generator::generateOverloadedSignal.
+ QString code = getOverloadedSignalCode(node);
+ if (code.isEmpty())
+ return;
+
+ writer->writeStartElement(dbNamespace, "note");
+ newLine();
+ writer->writeStartElement(dbNamespace, "para");
+ writer->writeCharacters("Signal ");
+ writer->writeTextElement(dbNamespace, "emphasis", node->name());
+ writer->writeCharacters(" is overloaded in this class. To connect to this "
+ "signal by using the function pointer syntax, Qt "
+ "provides a convenient helper for obtaining the "
+ "function pointer as shown in this example:");
+ writer->writeTextElement(dbNamespace, "code", code);
+ writer->writeEndElement(); // para
+ newLine();
+ writer->writeEndElement(); // note
+ newLine();
+}
+
+/*!
+ Generates an addendum note of type \a type for \a node. \a marker
+ is unused in this generator.
+*/
+void DocBookGenerator::generateAddendum(const Node *node, Addendum type, CodeMarker *marker)
+{
+ Q_UNUSED(marker);
+ Q_ASSERT(node && !node->name().isEmpty());
+ writer->writeStartElement(dbNamespace, "note");
+ newLine();
+ switch (type) {
+ case Invokable:
+ writer->writeStartElement(dbNamespace, "para");
+ writer->writeCharacters(
+ "This function can be invoked via the meta-object system and from QML. See ");
+ generateSimpleLink(node->url(), "Q_INVOKABLE");
+ writer->writeCharacters(".");
+ writer->writeEndElement(); // para
+ newLine();
+ break;
+ case PrivateSignal:
+ writer->writeTextElement(dbNamespace, "para",
+ "This is a private signal. It can be used in signal connections but "
+ "cannot be emitted by the user.");
+ break;
+ case QmlSignalHandler:
+ {
+ QString handler(node->name());
+ handler[0] = handler[0].toTitleCase();
+ handler.prepend(QLatin1String("on"));
+ writer->writeStartElement(dbNamespace, "para");
+ writer->writeCharacters("The corresponding handler is ");
+ writer->writeTextElement(dbNamespace, "code", handler);
+ writer->writeCharacters(".");
+ writer->writeEndElement(); // para
+ newLine();
+ break;
+ }
+ case AssociatedProperties:
+ {
+ if (!node->isFunction())
+ return;
+ const FunctionNode *fn = static_cast<const FunctionNode *>(node);
+ NodeList nodes = fn->associatedProperties();
+ if (nodes.isEmpty())
+ return;
+ std::sort(nodes.begin(), nodes.end(), Node::nodeNameLessThan);
+ for (const auto node : qAsConst(nodes)) {
+ QString msg;
+ const auto pn = static_cast<const PropertyNode *>(node);
+ switch (pn->role(fn)) {
+ case PropertyNode::Getter:
+ msg = QStringLiteral("Getter function");
+ break;
+ case PropertyNode::Setter:
+ msg = QStringLiteral("Setter function");
+ break;
+ case PropertyNode::Resetter:
+ msg = QStringLiteral("Resetter function");
+ break;
+ case PropertyNode::Notifier:
+ msg = QStringLiteral("Notifier signal");
+ break;
+ default:
+ continue;
+ }
+ writer->writeCharacters(msg + " for property ");
+ generateSimpleLink(linkForNode(pn, nullptr), pn->name());
+ writer->writeCharacters(". ");
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ writer->writeEndElement(); // note
+ newLine();
+}
+
+void DocBookGenerator::generateDetailedMember(const Node *node, const PageNode *relative)
+{
+ // From HtmlGenerator::generateDetailedMember.
+ writer->writeStartElement(dbNamespace, "section");
+ if (node->isSharedCommentNode()) {
+ const auto scn = reinterpret_cast<const SharedCommentNode *>(node);
+ const QVector<Node *> &collective = scn->collective();
+
+ bool firstFunction = true;
+ for (const Node *n : collective) {
+ if (n->isFunction()) {
+ QString nodeRef = refForNode(n);
+
+ if (firstFunction) {
+ writer->writeAttribute("xml:id", refForNode(collective.at(0)));
+ newLine();
+ writer->writeStartElement(dbNamespace, "title");
+ generateSynopsis(n, relative, Section::Details);
+ writer->writeEndElement(); // title
+ newLine();
+
+ firstFunction = false;
+ } else {
+ writer->writeStartElement(dbNamespace, "bridgehead");
+ writer->writeAttribute("renderas", "sect2");
+ writer->writeAttribute("xml:id", nodeRef);
+ generateSynopsis(n, relative, Section::Details);
+ writer->writeEndElement(); // bridgehead
+ newLine();
+ }
+ }
+ }
+ } else {
+ const EnumNode *etn;
+ QString nodeRef = refForNode(node);
+ if (node->isEnumType() && (etn = static_cast<const EnumNode *>(node))->flagsType()) {
+ writer->writeAttribute("xml:id", nodeRef);
+ newLine();
+ writer->writeStartElement(dbNamespace, "title");
+ generateSynopsis(etn, relative, Section::Details);
+ writer->writeEndElement(); // title
+ newLine();
+ writer->writeStartElement(dbNamespace, "bridgehead");
+ generateSynopsis(etn->flagsType(), relative, Section::Details);
+ writer->writeEndElement(); // bridgehead
+ newLine();
+ } else {
+ writer->writeAttribute("xml:id", nodeRef);
+ newLine();
+ writer->writeStartElement(dbNamespace, "title");
+ generateSynopsis(node, relative, Section::Details);
+ writer->writeEndElement(); // title
+ newLine();
+ }
+ }
+
+ generateDocBookSynopsis(node);
+
+ generateStatus(node);
+ generateBody(node);
+ generateOverloadedSignal(node);
+ generateThreadSafeness(node);
+ generateSince(node);
+
+ if (node->isProperty()) {
+ const auto property = static_cast<const PropertyNode *>(node);
+ Section section(Section::Accessors, Section::Active);
+
+ section.appendMembers(property->getters().toVector());
+ section.appendMembers(property->setters().toVector());
+ section.appendMembers(property->resetters().toVector());
+
+ if (!section.members().isEmpty()) {
+ writer->writeStartElement(dbNamespace, "para");
+ newLine();
+ writer->writeTextElement(dbNamespace, "emphasis", "Access functions:");
+ writer->writeAttribute("role", "bold");
+ newLine();
+ writer->writeEndElement(); // para
+ newLine();
+ generateSectionList(section, node);
+ }
+
+ Section notifiers(Section::Accessors, Section::Active);
+ notifiers.appendMembers(property->notifiers().toVector());
+
+ if (!notifiers.members().isEmpty()) {
+ writer->writeStartElement(dbNamespace, "para");
+ newLine();
+ writer->writeTextElement(dbNamespace, "emphasis", "Notifier signal:");
+ writer->writeAttribute("role", "bold");
+ newLine();
+ writer->writeEndElement(); // para
+ newLine();
+ generateSectionList(notifiers, node);
+ }
+ } else if (node->isEnumType()) {
+ const auto en = static_cast<const EnumNode *>(node);
+
+ if (qflagsHref_.isEmpty()) {
+ Node *qflags = qdb_->findClassNode(QStringList("QFlags"));
+ if (qflags)
+ qflagsHref_ = linkForNode(qflags, nullptr);
+ }
+
+ if (en->flagsType()) {
+ writer->writeStartElement(dbNamespace, "para");
+ writer->writeCharacters("The " + en->flagsType()->name() + " type is a typedef for ");
+ generateSimpleLink(qflagsHref_, "QFlags");
+ writer->writeCharacters("&lt;" + en->name() + "&gt;. ");
+ writer->writeCharacters("It stores an OR combination of " + en->name() + "values.");
+ writer->writeEndElement(); // para
+ newLine();
+ }
+ }
+ generateAlsoList(node);
+ endSection(); // section
+}
+
+void DocBookGenerator::generateSectionList(const Section &section, const Node *relative,
+ Section::Status status)
+{
+ // From HtmlGenerator::generateSectionList, just generating a list (not tables).
+ const NodeVector &members =
+ (status == Section::Obsolete ? section.obsoleteMembers() : section.members());
+ if (!members.isEmpty()) {
+ bool hasPrivateSignals = false;
+ bool isInvokable = false;
+
+ writer->writeStartElement(dbNamespace, "itemizedlist");
+ newLine();
+
+ int i = 0;
+ NodeVector::ConstIterator m = members.constBegin();
+ while (m != members.constEnd()) {
+ if ((*m)->access() == Node::Private) {
+ ++m;
+ continue;
+ }
+
+ writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ writer->writeStartElement(dbNamespace, "para");
+
+ // prefix no more needed.
+ generateSynopsis(*m, relative, section.style());
+ if ((*m)->isFunction()) {
+ const auto fn = static_cast<const FunctionNode *>(*m);
+ if (fn->isPrivateSignal())
+ hasPrivateSignals = true;
+ else if (fn->isInvokable())
+ isInvokable = true;
+ }
+
+ writer->writeEndElement(); // para
+ newLine();
+ writer->writeEndElement(); // listitem
+ newLine();
+
+ i++;
+ ++m;
+ }
+
+ writer->writeEndElement(); // itemizedlist
+ newLine();
+
+ if (hasPrivateSignals)
+ generateAddendum(relative, Generator::PrivateSignal);
+ if (isInvokable)
+ generateAddendum(relative, Generator::Invokable);
+ }
+
+ if (status != Section::Obsolete && section.style() == Section::Summary
+ && !section.inheritedMembers().isEmpty()) {
+ writer->writeStartElement(dbNamespace, "itemizedlist");
+ newLine();
+
+ generateSectionInheritedList(section, relative);
+
+ writer->writeEndElement(); // itemizedlist
+ newLine();
+ }
+}
+
+void DocBookGenerator::generateSectionInheritedList(const Section &section, const Node *relative)
+{
+ // From HtmlGenerator::generateSectionInheritedList.
+ QVector<QPair<Aggregate *, int>>::ConstIterator p = section.inheritedMembers().constBegin();
+ while (p != section.inheritedMembers().constEnd()) {
+ writer->writeStartElement(dbNamespace, "listitem");
+ writer->writeCharacters(QString((*p).second) + " ");
+ if ((*p).second == 1)
+ writer->writeCharacters(section.singular());
+ else
+ writer->writeCharacters(section.plural());
+ writer->writeCharacters(" inherited from ");
+ generateSimpleLink(fileName((*p).first) + '#'
+ + Generator::cleanRef(section.title().toLower()),
+ (*p).first->plainFullName(relative));
+ ++p;
+ }
+}
+
+/*!
+ Generate the DocBook page for an entity that doesn't map
+ to any underlying parsable C++, QML, or Javascript element.
+ */
+void DocBookGenerator::generatePageNode(PageNode *pn)
+{
+ Q_ASSERT(writer == nullptr);
+ // From HtmlGenerator::generatePageNode, remove anything related to TOCs.
+ writer = startDocument(pn);
+
+ generateHeader(pn->fullTitle(), pn->subtitle(), pn);
+ generateBody(pn);
+ generateAlsoList(pn);
+ generateFooter();
+
+ endDocument();
+}
+
+/*!
+ Extract sections of markup text and output them.
+ */
+bool DocBookGenerator::generateQmlText(const Text &text, const Node *relative, CodeMarker *marker,
+ const QString &qmlName)
+{
+ Q_UNUSED(marker);
+ Q_UNUSED(qmlName);
+ // From Generator::generateQmlText.
+ const Atom *atom = text.firstAtom();
+ bool result = false;
+
+ if (atom != nullptr) {
+ initializeTextOutput();
+ while (atom) {
+ if (atom->type() != Atom::QmlText)
+ atom = atom->next();
+ else {
+ atom = atom->next();
+ while (atom && (atom->type() != Atom::EndQmlText)) {
+ int n = 1 + generateAtom(atom, relative);
+ while (n-- > 0)
+ atom = atom->next();
+ }
+ }
+ }
+ result = true;
+ }
+ return result;
+}
+
+/*!
+ Generate the DocBook page for a QML type. \qcn is the QML type.
+ */
+void DocBookGenerator::generateQmlTypePage(QmlTypeNode *qcn)
+{
+ // From HtmlGenerator::generateQmlTypePage.
+ // Start producing the DocBook file.
+ Q_ASSERT(writer == nullptr);
+ writer = startDocument(qcn);
+
+ Generator::setQmlTypeContext(qcn);
+ QString title = qcn->fullTitle();
+ if (qcn->isJsType())
+ title += " JavaScript Type";
+ else
+ title += " QML Type";
+
+ generateHeader(title, qcn->subtitle(), qcn);
+ generateQmlRequisites(qcn);
+
+ startSection(registerRef("details"), "Detailed Description");
+ generateBody(qcn);
+
+ ClassNode *cn = qcn->classNode();
+ if (cn)
+ generateQmlText(cn->doc().body(), cn);
+ generateAlsoList(qcn);
+
+ endSection();
+
+ Sections sections(qcn);
+ for (const auto &section : sections.stdQmlTypeDetailsSections()) {
+ if (!section.isEmpty()) {
+ startSection(registerRef(section.title().toLower()), section.title());
+
+ for (const auto &member : section.members())
+ generateDetailedQmlMember(member, qcn);
+
+ endSection();
+ }
+ }
+
+ generateObsoleteQmlMembers(sections);
+
+ generateFooter();
+ Generator::setQmlTypeContext(nullptr);
+
+ endDocument();
+}
+
+/*!
+ Generate the DocBook page for the QML basic type represented
+ by the QML basic type node \a qbtn.
+ */
+void DocBookGenerator::generateQmlBasicTypePage(QmlBasicTypeNode *qbtn)
+{
+ // From HtmlGenerator::generateQmlBasicTypePage.
+ // Start producing the DocBook file.
+ Q_ASSERT(writer == nullptr);
+ writer = startDocument(qbtn);
+
+ QString htmlTitle = qbtn->fullTitle();
+ if (qbtn->isJsType())
+ htmlTitle += " JavaScript Basic Type";
+ else
+ htmlTitle += " QML Basic Type";
+
+ Sections sections(qbtn);
+ generateHeader(htmlTitle, qbtn->subtitle(), qbtn);
+
+ startSection(registerRef("details"), "Detailed Description");
+
+ generateBody(qbtn);
+ generateAlsoList(qbtn);
+
+ endSection();
+
+ SectionVector::ConstIterator s = sections.stdQmlTypeDetailsSections().constBegin();
+ while (s != sections.stdQmlTypeDetailsSections().constEnd()) {
+ if (!s->isEmpty()) {
+ startSection(registerRef(s->title().toLower()), s->title());
+
+ NodeVector::ConstIterator m = s->members().constBegin();
+ while (m != s->members().constEnd()) {
+ generateDetailedQmlMember(*m, qbtn);
+ ++m;
+ }
+
+ endSection();
+ }
+ ++s;
+ }
+ generateFooter();
+
+ endDocument();
+}
+
+/*!
+ Outputs the DocBook detailed documentation for a section
+ on a QML element reference page.
+ */
+void DocBookGenerator::generateDetailedQmlMember(Node *node, const Aggregate *relative)
+{
+ // From HtmlGenerator::generateDetailedQmlMember, with elements from
+ // CppCodeMarker::markedUpQmlItem and HtmlGenerator::generateQmlItem.
+ std::function<QString(QmlPropertyNode *)> getQmlPropertyTitle = [&](QmlPropertyNode *n) {
+ if (!n->isReadOnlySet() && n->declarativeCppNode())
+ n->markReadOnly(!n->isWritable());
+
+ QString title;
+ if (!n->isWritable())
+ title += "[read-only] ";
+ if (n->isDefault())
+ title += "[default] ";
+
+ // Finalise generation of name, as per CppCodeMarker::markedUpQmlItem.
+ if (n->isAttached())
+ title += n->element() + QLatin1Char('.');
+ title += n->name() + " : " + n->dataType();
+
+ return title;
+ };
+
+ std::function<void(Node *)> generateQmlMethodTitle = [&](Node *node) {
+ generateSynopsis(node, relative, Section::Details);
+ };
+
+ bool generateEndSection = true;
+
+ if (node->isPropertyGroup()) {
+ const auto scn = static_cast<const SharedCommentNode *>(node);
+
+ QString heading;
+ if (!scn->name().isEmpty())
+ heading = scn->name() + " group";
+ else
+ heading = node->name();
+ startSection(refForNode(scn), heading);
+ // This last call creates a title for this section. In other words,
+ // titles are forbidden for the rest of the section.
+
+ const QVector<Node *> sharedNodes = scn->collective();
+ for (const auto &node : sharedNodes) {
+ if (node->isQmlProperty() || node->isJsProperty()) {
+ auto *qpn = static_cast<QmlPropertyNode *>(node);
+
+ writer->writeStartElement(dbNamespace, "bridgehead");
+ writer->writeAttribute("renderas", "sect2");
+ writer->writeAttribute("xml:id", refForNode(qpn));
+ writer->writeCharacters(getQmlPropertyTitle(qpn));
+ writer->writeEndElement(); // bridgehead
+ newLine();
+
+ generateDocBookSynopsis(qpn);
+ }
+ }
+ } else if (node->isQmlProperty() || node->isJsProperty()) {
+ auto qpn = static_cast<QmlPropertyNode *>(node);
+ startSection(refForNode(qpn), getQmlPropertyTitle(qpn));
+ generateDocBookSynopsis(qpn);
+ } else if (node->isSharedCommentNode()) {
+ const auto scn = reinterpret_cast<const SharedCommentNode *>(node);
+ const QVector<Node *> &sharedNodes = scn->collective();
+
+ // In the section, generate a title for the first node, then bridgeheads for
+ // the next ones.
+ int i = 0;
+ for (const auto m : sharedNodes) {
+ // Ignore this element if there is nothing to generate.
+ if (!node->isFunction(Node::QML) && !node->isFunction(Node::JS)
+ && !node->isQmlProperty() && !node->isJsProperty()) {
+ continue;
+ }
+
+ // Complete the section tag.
+ if (i == 0) {
+ writer->writeStartElement(dbNamespace, "section");
+ writer->writeAttribute("xml:id", refForNode(m));
+ newLine();
+ }
+
+ // Write the tag containing the title.
+ writer->writeStartElement(dbNamespace, (i == 0) ? "title" : "bridgehead");
+ if (i > 0)
+ writer->writeAttribute("renderas", "sect2");
+
+ // Write the title.
+ QString title;
+ if (node->isFunction(Node::QML) || node->isFunction(Node::JS))
+ generateQmlMethodTitle(node);
+ else if (node->isQmlProperty() || node->isJsProperty())
+ writer->writeCharacters(getQmlPropertyTitle(static_cast<QmlPropertyNode *>(node)));
+
+ // Complete the title and the synopsis.
+ generateDocBookSynopsis(m);
+ ++i;
+ }
+
+ if (i == 0)
+ generateEndSection = false;
+ } else { // assume the node is a method/signal handler
+ startSectionBegin(refForNode(node));
+ generateQmlMethodTitle(node);
+ startSectionEnd();
+ }
+
+ generateStatus(node);
+ generateBody(node);
+ generateThreadSafeness(node);
+ generateSince(node);
+ generateAlsoList(node);
+
+ if (generateEndSection)
+ endSection();
+}
+
+/*!
+ Recursive writing of DocBook files from the root \a node.
+ */
+void DocBookGenerator::generateDocumentation(Node *node)
+{
+ // Mainly from Generator::generateDocumentation, with parts from
+ // Generator::generateDocumentation and WebXMLGenerator::generateDocumentation.
+ // Don't generate nodes that are already processed, or if they're not
+ // supposed to generate output, ie. external, index or images nodes.
+ if (!node->url().isNull())
+ return;
+ if (node->isIndexNode())
+ return;
+ if (node->isInternal() && !showInternal_)
+ return;
+ if (node->isExternalPage())
+ return;
+
+ if (node->parent()) {
+ if (node->isCollectionNode()) {
+ /*
+ A collection node collects: groups, C++ modules,
+ QML modules or JavaScript modules. Testing for a
+ CollectionNode must be done before testing for a
+ TextPageNode because a CollectionNode is a PageNode
+ at this point.
+
+ Don't output an HTML page for the collection
+ node unless the \group, \module, \qmlmodule or
+ \jsmodule command was actually seen by qdoc in
+ the qdoc comment for the node.
+
+ A key prerequisite in this case is the call to
+ mergeCollections(cn). We must determine whether
+ this group, module, QML module, or JavaScript
+ module has members in other modules. We know at
+ this point that cn's members list contains only
+ members in the current module. Therefore, before
+ outputting the page for cn, we must search for
+ members of cn in the other modules and add them
+ to the members list.
+ */
+ auto cn = static_cast<CollectionNode *>(node);
+ if (cn->wasSeen()) {
+ qdb_->mergeCollections(cn);
+ generateCollectionNode(cn);
+ } else if (cn->isGenericCollection()) {
+ // Currently used only for the module's related orphans page
+ // but can be generalized for other kinds of collections if
+ // other use cases pop up.
+ generateGenericCollectionPage(cn);
+ }
+ } else if (node->isTextPageNode()) { // Pages.
+ generatePageNode(static_cast<PageNode *>(node));
+ } else if (node->isAggregate()) { // Aggregates.
+ if ((node->isClassNode() || node->isHeader() || node->isNamespace())
+ && node->docMustBeGenerated()) {
+ generateCppReferencePage(static_cast<Aggregate *>(node));
+ } else if (node->isQmlType() || node->isJsType()) {
+ generateQmlTypePage(static_cast<QmlTypeNode *>(node));
+ } else if (node->isQmlBasicType() || node->isJsBasicType()) {
+ generateQmlBasicTypePage(static_cast<QmlBasicTypeNode *>(node));
+ } else if (node->isProxyNode()) {
+ generateProxyPage(static_cast<Aggregate *>(node));
+ }
+ }
+ }
+
+ if (node->isAggregate()) {
+ auto *aggregate = static_cast<Aggregate *>(node);
+ for (auto c : aggregate->childNodes()) {
+ if (node->isPageNode() && !node->isPrivate())
+ generateDocumentation(c);
+ }
+ }
+}
+
+void DocBookGenerator::generateProxyPage(Aggregate *aggregate)
+{
+ // Adapted from HtmlGenerator::generateProxyPage.
+ Q_ASSERT(aggregate->isProxyNode());
+
+ // Start producing the DocBook file.
+ Q_ASSERT(writer == nullptr);
+ writer = startDocument(aggregate);
+
+ // Info container.
+ generateHeader(aggregate->plainFullName(), "", aggregate);
+
+ // No element synopsis.
+
+ // Actual content.
+ if (!aggregate->doc().isEmpty()) {
+ startSection(registerRef("details"), "Detailed Description");
+
+ generateBody(aggregate);
+ generateAlsoList(aggregate);
+ generateMaintainerList(aggregate);
+
+ endSection();
+ }
+
+ Sections sections(aggregate);
+ SectionVector *detailsSections = &sections.stdDetailsSections();
+
+ for (const auto &section : qAsConst(*detailsSections)) {
+ if (section.isEmpty())
+ continue;
+
+ startSection(section.title().toLower(), section.title());
+
+ const QVector<Node *> &members = section.members();
+ for (const auto &member : members) {
+ if (!member->isPrivate()) { // ### check necessary?
+ if (!member->isClassNode()) {
+ generateDetailedMember(member, aggregate);
+ } else {
+ startSectionBegin();
+ generateFullName(member, aggregate);
+ startSectionEnd();
+ generateBrief(member);
+ endSection();
+ }
+ }
+ }
+
+ endSection();
+ }
+
+ generateFooter();
+
+ endDocument();
+}
+
+/*!
+ Generate the HTML page for a group, module, or QML module.
+ */
+void DocBookGenerator::generateCollectionNode(CollectionNode *cn)
+{
+ // Adapted from HtmlGenerator::generateCollectionNode.
+ // Start producing the DocBook file.
+ Q_ASSERT(writer == nullptr);
+ writer = startDocument(cn);
+
+ // Info container.
+ generateHeader(cn->fullTitle(), cn->subtitle(), cn);
+
+ // Element synopsis.
+ generateDocBookSynopsis(cn);
+
+ // Actual content.
+ if (cn->isModule()) {
+ // Generate brief text and status for modules.
+ generateBrief(cn);
+ generateStatus(cn);
+ generateSince(cn);
+
+ if (!cn->noAutoList()) {
+ NodeMultiMap nmm;
+ cn->getMemberNamespaces(nmm);
+ if (!nmm.isEmpty()) {
+ startSection(registerRef("namespaces"), "Namespaces");
+ generateAnnotatedList(cn, nmm, "namespaces");
+ endSection();
+ }
+ nmm.clear();
+ cn->getMemberClasses(nmm);
+ if (!nmm.isEmpty()) {
+ startSection(registerRef("classes"), "Classes");
+ generateAnnotatedList(cn, nmm, "classes");
+ endSection();
+ }
+ }
+ }
+
+ Text brief = cn->doc().briefText();
+ bool generatedTitle = false;
+ if (cn->isModule() && !brief.isEmpty()) {
+ startSection(registerRef("details"), "Detailed Description");
+ generatedTitle = true;
+ } else {
+ writeAnchor(registerRef("details"));
+ }
+
+ generateBody(cn);
+ generateAlsoList(cn);
+
+ if (!cn->noAutoList() && (cn->isGroup() || cn->isQmlModule() || cn->isJsModule()))
+ generateAnnotatedList(cn, cn->members(), "members");
+
+ if (generatedTitle)
+ endSection();
+
+ generateFooter();
+
+ endDocument();
+}
+
+/*!
+ Generate the HTML page for a generic collection. This is usually
+ a collection of C++ elements that are related to an element in
+ a different module.
+ */
+void DocBookGenerator::generateGenericCollectionPage(CollectionNode *cn)
+{
+ // Adapted from HtmlGenerator::generateGenericCollectionPage.
+ // TODO: factor out this code to generate a file name.
+ QString name = cn->name().toLower();
+ name.replace(QChar(' '), QString("-"));
+ QString filename = cn->tree()->physicalModuleName() + "-" + name + "." + fileExtension();
+
+ // Start producing the DocBook file.
+ Q_ASSERT(writer == nullptr);
+ writer = startGenericDocument(cn, filename);
+
+ // Info container.
+ generateHeader(cn->fullTitle(), cn->subtitle(), cn);
+
+ // Element synopsis.
+ generateDocBookSynopsis(cn);
+
+ // Actual content.
+ writer->writeStartElement(dbNamespace, "para");
+ writer->writeCharacters("Each function or type documented here is related to a class or "
+ "namespace that is documented in a different module. The reference "
+ "page for that class or namespace will link to the function or type "
+ "on this page.");
+ writer->writeEndElement(); // para
+
+ const CollectionNode *cnc = cn;
+ const QList<Node *> members = cn->members();
+ for (const auto &member : members)
+ generateDetailedMember(member, cnc);
+
+ generateFooter();
+
+ endDocument();
+}
+
+void DocBookGenerator::generateFullName(const Node *node, const Node *relative)
+{
+ // From Generator::appendFullName.
+ writer->writeStartElement(dbNamespace, "link");
+ writer->writeAttribute(xlinkNamespace, "href", fullDocumentLocation(node));
+ writer->writeAttribute(xlinkNamespace, "role", targetType(node));
+ writer->writeCharacters(node->fullName(relative));
+ writer->writeEndElement(); // link
+}
+
+void DocBookGenerator::generateFullName(const Node *apparentNode, const QString &fullName,
+ const Node *actualNode)
+{
+ // From Generator::appendFullName.
+ if (actualNode == nullptr)
+ actualNode = apparentNode;
+ writer->writeStartElement(dbNamespace, "link");
+ writer->writeAttribute(xlinkNamespace, "href", fullDocumentLocation(actualNode));
+ writer->writeAttribute("type", targetType(actualNode));
+ writer->writeCharacters(fullName);
+ writer->writeEndElement(); // link
+}
+
+QT_END_NAMESPACE