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