diff options
Diffstat (limited to 'src/qdoc/docbookgenerator.cpp')
-rw-r--r-- | src/qdoc/docbookgenerator.cpp | 4216 |
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("<Missing DocBook>"); + writer->writeEndElement(); // emphasis + break; + case Atom::UnknownCommand: + writer->writeStartElement(dbNamespace, "emphasis"); + writer->writeAttribute("role", "bold"); + writer->writeCharacters("<Unknown command>"); + 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 §ions) +{ + // 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 §ions) +{ + // 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 ? §ions.stdDetailsSections() : §ions.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 ¶meter = 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("&"); + break; + case '<': + result += QLatin1String("<"); + break; + case '>': + result += QLatin1String(">"); + break; + case '\'': + result += QLatin1String("'"); + break; + case '"': + result += QLatin1String("""); + 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 ¶meter, 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 ¶meters = 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 += "…"; // Ellipsis: in HTML, …. + 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("<" + en->name() + ">. "); + writer->writeCharacters("It stores an OR combination of " + en->name() + "values."); + writer->writeEndElement(); // para + newLine(); + } + } + generateAlsoList(node); + endSection(); // section +} + +void DocBookGenerator::generateSectionList(const Section §ion, 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 §ion, 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 §ion : 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 = §ions.stdDetailsSections(); + + for (const auto §ion : 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 |