summaryrefslogtreecommitdiff
path: root/src/qdoc/generator.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qdoc/generator.cpp')
-rw-r--r--src/qdoc/generator.cpp2171
1 files changed, 2171 insertions, 0 deletions
diff --git a/src/qdoc/generator.cpp b/src/qdoc/generator.cpp
new file mode 100644
index 000000000..3ce5bf99d
--- /dev/null
+++ b/src/qdoc/generator.cpp
@@ -0,0 +1,2171 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL21$
+** 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 http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** As a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*
+ generator.cpp
+*/
+#include <qdir.h>
+#include <qdebug.h>
+#include "codemarker.h"
+#include "config.h"
+#include "doc.h"
+#include "editdistance.h"
+#include "generator.h"
+#include "openedlist.h"
+#include "quoter.h"
+#include "separator.h"
+#include "tokenizer.h"
+#include "qdocdatabase.h"
+
+QT_BEGIN_NAMESPACE
+
+Generator* Generator::currentGenerator_;
+QStringList Generator::exampleDirs;
+QStringList Generator::exampleImgExts;
+QMap<QString, QMap<QString, QString> > Generator::fmtLeftMaps;
+QMap<QString, QMap<QString, QString> > Generator::fmtRightMaps;
+QList<Generator *> Generator::generators;
+QStringList Generator::imageDirs;
+QStringList Generator::imageFiles;
+QMap<QString, QStringList> Generator::imgFileExts;
+QString Generator::outDir_;
+QString Generator::outSubdir_;
+QStringList Generator::outFileNames_;
+QSet<QString> Generator::outputFormats;
+QHash<QString, QString> Generator::outputPrefixes;
+QHash<QString, QString> Generator::outputSuffixes;
+QString Generator::project_;
+QStringList Generator::scriptDirs;
+QStringList Generator::scriptFiles;
+QString Generator::sinceTitles[] =
+{
+ " New Namespaces",
+ " New Classes",
+ " New Member Functions",
+ " New Functions in Namespaces",
+ " New Global Functions",
+ " New Macros",
+ " New Enum Types",
+ " New Typedefs",
+ " New Properties",
+ " New Variables",
+ " New QML Types",
+ " New QML Properties",
+ " New QML Signals",
+ " New QML Signal Handlers",
+ " New QML Methods",
+ ""
+};
+QStringList Generator::styleDirs;
+QStringList Generator::styleFiles;
+bool Generator::debugging_ = false;
+bool Generator::noLinkErrors_ = false;
+bool Generator::autolinkErrors_ = false;
+bool Generator::redirectDocumentationToDevNull_ = false;
+Generator::QDocPass Generator::qdocPass_ = Generator::Neither;
+bool Generator::qdocSingleExec_ = false;
+bool Generator::qdocWriteQaPages_ = false;
+bool Generator::useOutputSubdirs_ = true;
+QmlTypeNode* Generator::qmlTypeContext_ = 0;
+
+void Generator::startDebugging(const QString& message)
+{
+ debugging_ = true;
+ qDebug() << "START DEBUGGING:" << message;
+}
+
+void Generator::stopDebugging(const QString& message)
+{
+ debugging_ = false;
+ qDebug() << "STOP DEBUGGING:" << message;
+}
+
+/*!
+ Prints \a message as an aid to debugging the release version.
+ */
+void Generator::debug(const QString& message)
+{
+ if (debugging())
+ qDebug() << " DEBUG:" << message;
+}
+
+/*!
+ Constructs the generator base class. Prepends the newly
+ constructed generator to the list of output generators.
+ Sets a pointer to the QDoc database singleton, which is
+ available to the generator subclasses.
+ */
+Generator::Generator()
+ : amp("&amp;"),
+ gt("&gt;"),
+ lt("&lt;"),
+ quot("&quot;"),
+ tag("</?@[^>]*>"),
+ inLink_(false),
+ inContents_(false),
+ inSectionHeading_(false),
+ inTableHeader_(false),
+ threeColumnEnumValueTable_(true),
+ showInternal_(false),
+ singleExec_(false),
+ numTableRows_(0)
+{
+ qdb_ = QDocDatabase::qdocDB();
+ generators.prepend(this);
+}
+
+/*!
+ Destroys the generator after removing it from the list of
+ output generators.
+ */
+Generator::~Generator()
+{
+ generators.removeAll(this);
+}
+
+void Generator::appendFullName(Text& text,
+ const Node *apparentNode,
+ const Node *relative,
+ const Node *actualNode)
+{
+ if (actualNode == 0)
+ actualNode = apparentNode;
+ text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode))
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
+ << Atom(Atom::String, apparentNode->plainFullName(relative))
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
+}
+
+void Generator::appendFullName(Text& text,
+ const Node *apparentNode,
+ const QString& fullName,
+ const Node *actualNode)
+{
+ if (actualNode == 0)
+ actualNode = apparentNode;
+ text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode))
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
+ << Atom(Atom::String, fullName)
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
+}
+
+void Generator::appendFullNames(Text& text, const NodeList& nodes, const Node* relative)
+{
+ NodeList::ConstIterator n = nodes.constBegin();
+ int index = 0;
+ while (n != nodes.constEnd()) {
+ appendFullName(text,*n,relative);
+ text << comma(index++,nodes.count());
+ ++n;
+ }
+}
+
+void Generator::appendSortedNames(Text& text, const ClassNode* cn, const QList<RelatedClass>& rc)
+{
+ QList<RelatedClass>::ConstIterator r;
+ QMap<QString,Text> classMap;
+ int index = 0;
+
+ r = rc.constBegin();
+ while (r != rc.constEnd()) {
+ ClassNode* rcn = (*r).node_;
+ if (rcn && rcn->access() == Node::Public &&
+ rcn->status() != Node::Internal &&
+ !rcn->doc().isEmpty()) {
+ Text className;
+ appendFullName(className, rcn, cn);
+ classMap[className.toString().toLower()] = className;
+ }
+ ++r;
+ }
+
+ QStringList classNames = classMap.keys();
+ classNames.sort();
+
+ foreach (const QString &className, classNames) {
+ text << classMap[className];
+ text << comma(index++, classNames.count());
+ }
+}
+
+void Generator::appendSortedQmlNames(Text& text, const Node* base, const NodeList& subs)
+{
+ QMap<QString,Text> classMap;
+ int index = 0;
+
+ for (int i = 0; i < subs.size(); ++i) {
+ Text t;
+ if (!base->isQtQuickNode() || !subs[i]->isQtQuickNode() ||
+ (base->logicalModuleName() == subs[i]->logicalModuleName())) {
+ appendFullName(t, subs[i], base);
+ classMap[t.toString().toLower()] = t;
+ }
+ }
+
+ QStringList names = classMap.keys();
+ names.sort();
+
+ foreach (const QString &name, names) {
+ text << classMap[name];
+ text << comma(index++, names.count());
+ }
+}
+
+/*!
+ For debugging qdoc.
+ */
+void Generator::writeOutFileNames()
+{
+ QFile files("outputlist.txt");
+ if (!files.open(QFile::WriteOnly))
+ return;
+ QTextStream filesout(&files);
+ foreach (const QString &file, outFileNames_) {
+ filesout << file << "\n";
+ }
+}
+
+/*!
+ Creates the file named \a fileName in the output directory.
+ Attaches a QTextStream to the created file, which is written
+ to all over the place using out().
+ */
+void Generator::beginSubPage(const Aggregate* node, const QString& fileName)
+{
+ QString path = outputDir() + QLatin1Char('/');
+ if (Generator::useOutputSubdirs() && !node->outputSubdirectory().isEmpty() &&
+ !outputDir().endsWith(node->outputSubdirectory()))
+ path += node->outputSubdirectory() + QLatin1Char('/');
+ path += fileName;
+
+ QFile* outFile = new QFile(redirectDocumentationToDevNull_ ? QStringLiteral("/dev/null") : path);
+ if (!redirectDocumentationToDevNull_ && outFile->exists())
+ node->location().error(tr("HTML file already exists; overwriting %1").arg(outFile->fileName()));
+ if (!outFile->open(QFile::WriteOnly))
+ node->location().fatal(tr("Cannot open output file '%1'").arg(outFile->fileName()));
+ Generator::debug("Writing: " + path);
+ outFileNames_ << fileName;
+ QTextStream* out = new QTextStream(outFile);
+
+#ifndef QT_NO_TEXTCODEC
+ if (outputCodec)
+ out->setCodec(outputCodec);
+#endif
+ outStreamStack.push(out);
+ const_cast<Aggregate*>(node)->setOutputFileName(fileName);
+}
+
+/*!
+ Flush the text stream associated with the subpage, and
+ then pop it off the text stream stack and delete it.
+ This terminates output of the subpage.
+ */
+void Generator::endSubPage()
+{
+ outStreamStack.top()->flush();
+ delete outStreamStack.top()->device();
+ delete outStreamStack.pop();
+}
+
+QString Generator::fileBase(const Node *node) const
+{
+ if (node->relates())
+ node = node->relates();
+ else if (!node->isAggregate())
+ node = node->parent();
+ if (node->type() == Node::QmlPropertyGroup) {
+ node = node->parent();
+ }
+
+ if (node->hasFileNameBase())
+ return node->fileNameBase();
+
+ QString base;
+ if (node->isDocumentNode()) {
+ base = node->name();
+ if (base.endsWith(".html") && !node->isExampleFile())
+ base.truncate(base.length() - 5);
+
+ if (node->isExample() || node->isExampleFile()) {
+ QString modPrefix(node->physicalModuleName());
+ if (modPrefix.isEmpty()) {
+ modPrefix = project_;
+ }
+ base.prepend(modPrefix.toLower() + QLatin1Char('-'));
+ }
+ if (node->isExample()) {
+ base.append(QLatin1String("-example"));
+ }
+ }
+ else if (node->isQmlType() || node->isQmlBasicType() ||
+ node->isJsType() || node->isJsBasicType()) {
+ base = node->name();
+ /*
+ To avoid file name conflicts in the html directory,
+ we prepend a prefix (by default, "qml-") and an optional suffix
+ to the file name. The suffix, if one exists, is appended to the
+ module name.
+ */
+ if (!node->logicalModuleName().isEmpty()) {
+ base.prepend(node->logicalModuleName()
+ + outputSuffix(node)
+ + QLatin1Char('-'));
+ }
+ base.prepend(outputPrefix(node));
+ }
+ else if (node->isCollectionNode()) {
+ base = node->name() + outputSuffix(node);
+ if (base.endsWith(".html"))
+ base.truncate(base.length() - 5);
+
+ if (node->isQmlModule()) {
+ base.append("-qmlmodule");
+ }
+ else if (node->isJsModule()) {
+ base.append("-jsmodule");
+ }
+ else if (node->isModule()) {
+ base.append("-module");
+ }
+ // Why not add "-group" for group pages?
+ }
+ else {
+ const Node *p = node;
+ forever {
+ const Node *pp = p->parent();
+ base.prepend(p->name());
+ if (!pp || pp->name().isEmpty() || pp->isDocumentNode())
+ break;
+ base.prepend(QLatin1Char('-'));
+ p = pp;
+ }
+ }
+
+ // the code below is effectively equivalent to:
+ // base.replace(QRegExp("[^A-Za-z0-9]+"), " ");
+ // base = base.trimmed();
+ // base.replace(QLatin1Char(' '), QLatin1Char('-'));
+ // base = base.toLower();
+ // as this function accounted for ~8% of total running time
+ // we optimize a bit...
+
+ QString res;
+ // +5 prevents realloc in fileName() below
+ res.reserve(base.size() + 5);
+ bool begun = false;
+ for (int i = 0; i != base.size(); ++i) {
+ QChar c = base.at(i);
+ uint u = c.unicode();
+ if (u >= 'A' && u <= 'Z')
+ u += 'a' - 'A';
+ if ((u >= 'a' && u <= 'z') || (u >= '0' && u <= '9')) {
+ res += QLatin1Char(u);
+ begun = true;
+ }
+ else if (begun) {
+ res += QLatin1Char('-');
+ begun = false;
+ }
+ }
+ while (res.endsWith(QLatin1Char('-')))
+ res.chop(1);
+ Node* n = const_cast<Node*>(node);
+ n->setFileNameBase(res);
+ return res;
+}
+
+/*!
+ If the \a node has a URL, return the URL as the file name.
+ Otherwise, construct the file name from the fileBase() and
+ the fileExtension(), and return the constructed name.
+ */
+QString Generator::fileName(const Node* node) const
+{
+ if (!node->url().isEmpty())
+ return node->url();
+
+ QString name = fileBase(node);
+ name += QLatin1Char('.');
+ name += fileExtension();
+ return name;
+}
+
+QString Generator::cleanRef(const QString& ref)
+{
+ QString clean;
+
+ if (ref.isEmpty())
+ return clean;
+
+ clean.reserve(ref.size() + 20);
+ const QChar c = ref[0];
+ const uint u = c.unicode();
+
+ if ((u >= 'a' && u <= 'z') ||
+ (u >= 'A' && u <= 'Z') ||
+ (u >= '0' && u <= '9')) {
+ clean += c;
+ } else if (u == '~') {
+ clean += "dtor.";
+ } else if (u == '_') {
+ clean += "underscore.";
+ } else {
+ clean += QLatin1Char('A');
+ }
+
+ for (int i = 1; i < (int) ref.length(); i++) {
+ const QChar c = ref[i];
+ const uint u = c.unicode();
+ if ((u >= 'a' && u <= 'z') ||
+ (u >= 'A' && u <= 'Z') ||
+ (u >= '0' && u <= '9') || u == '-' ||
+ u == '_' || u == ':' || u == '.') {
+ clean += c;
+ } else if (c.isSpace()) {
+ clean += QLatin1Char('-');
+ } else if (u == '!') {
+ clean += "-not";
+ } else if (u == '&') {
+ clean += "-and";
+ } else if (u == '<') {
+ clean += "-lt";
+ } else if (u == '=') {
+ clean += "-eq";
+ } else if (u == '>') {
+ clean += "-gt";
+ } else if (u == '#') {
+ clean += QLatin1Char('#');
+ } else {
+ clean += QLatin1Char('-');
+ clean += QString::number((int)u, 16);
+ }
+ }
+ return clean;
+}
+
+QMap<QString, QString>& Generator::formattingLeftMap()
+{
+ return fmtLeftMaps[format()];
+}
+
+QMap<QString, QString>& Generator::formattingRightMap()
+{
+ return fmtRightMaps[format()];
+}
+
+/*!
+ Returns the full document location.
+ */
+QString Generator::fullDocumentLocation(const Node *node, bool useSubdir)
+{
+ if (!node)
+ return QString();
+ if (!node->url().isEmpty())
+ return node->url();
+
+ QString parentName;
+ QString anchorRef;
+ QString fdl;
+
+ /*
+ If the useSubdir parameter is set, then the output is
+ being sent to subdirectories of the output directory.
+ Prepend the subdirectory name + '/' to the result.
+ */
+ if (useSubdir) {
+ fdl = node->outputSubdirectory();
+ if (!fdl.isEmpty())
+ fdl.append(QLatin1Char('/'));
+ }
+ if (node->isNamespace()) {
+
+ // The root namespace has no name - check for this before creating
+ // an attribute containing the location of any documentation.
+
+ if (!fileBase(node).isEmpty())
+ parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
+ else
+ return QString();
+ }
+ else if (node->isQmlType() || node->isQmlBasicType() ||
+ node->isJsType() || node->isJsBasicType()) {
+ QString fb = fileBase(node);
+ if (fb.startsWith(outputPrefix(node)))
+ return fb + QLatin1Char('.') + currentGenerator()->fileExtension();
+ else {
+ QString mq;
+ if (!node->logicalModuleName().isEmpty()) {
+ mq = node->logicalModuleName().replace(QChar('.'),QChar('-'));
+ mq = mq.toLower() + QLatin1Char('-');
+ }
+ return fdl + outputPrefix(node) + mq + fileBase(node) +
+ QLatin1Char('.') + currentGenerator()->fileExtension();
+ }
+ }
+ else if (node->isDocumentNode() || node->isCollectionNode()) {
+ parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
+ }
+ else if (fileBase(node).isEmpty())
+ return QString();
+
+ Node *parentNode = 0;
+
+ if ((parentNode = node->relates())) {
+ parentName = fullDocumentLocation(node->relates());
+ }
+ else if ((parentNode = node->parent())) {
+ if (parentNode->isQmlPropertyGroup() || parentNode->isJsPropertyGroup()) {
+ parentNode = parentNode->parent();
+ parentName = fullDocumentLocation(parentNode);
+ }
+ else {
+ parentName = fullDocumentLocation(node->parent());
+ }
+ }
+
+ switch (node->type()) {
+ case Node::Class:
+ case Node::Namespace:
+ parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
+ break;
+ case Node::Function:
+ {
+ const FunctionNode *fn = static_cast<const FunctionNode *>(node);
+
+ if (fn->metaness() == FunctionNode::Dtor)
+ anchorRef = "#dtor." + fn->name().mid(1);
+
+ else if (fn->hasOneAssociatedProperty() && fn->doc().isEmpty())
+ return fullDocumentLocation(fn->firstAssociatedProperty());
+
+ else if (fn->overloadNumber() > 0)
+ anchorRef = QLatin1Char('#') + cleanRef(fn->name())
+ + QLatin1Char('-') + QString::number(fn->overloadNumber());
+ else
+ anchorRef = QLatin1Char('#') + cleanRef(fn->name());
+ break;
+ }
+ /*
+ Use node->name() instead of fileBase(node) as
+ the latter returns the name in lower-case. For
+ HTML anchors, we need to preserve the case.
+ */
+ case Node::Enum:
+ anchorRef = QLatin1Char('#') + node->name() + "-enum";
+ break;
+ case Node::Typedef:
+ {
+ const TypedefNode *tdef = static_cast<const TypedefNode *>(node);
+ if (tdef->associatedEnum()) {
+ return fullDocumentLocation(tdef->associatedEnum());
+ }
+ anchorRef = QLatin1Char('#') + node->name() + "-typedef";
+ break;
+ }
+ case Node::Property:
+ anchorRef = QLatin1Char('#') + node->name() + "-prop";
+ break;
+ case Node::QmlProperty:
+ if (node->isAttached())
+ anchorRef = QLatin1Char('#') + node->name() + "-attached-prop";
+ else
+ anchorRef = QLatin1Char('#') + node->name() + "-prop";
+ break;
+ case Node::QmlSignal:
+ anchorRef = QLatin1Char('#') + node->name() + "-signal";
+ break;
+ case Node::QmlSignalHandler:
+ anchorRef = QLatin1Char('#') + node->name() + "-signal-handler";
+ break;
+ case Node::QmlMethod:
+ anchorRef = QLatin1Char('#') + node->name() + "-method";
+ break;
+ case Node::Variable:
+ anchorRef = QLatin1Char('#') + node->name() + "-var";
+ break;
+ case Node::QmlType:
+ case Node::Document:
+ case Node::Group:
+ case Node::Module:
+ case Node::QmlModule:
+ {
+ parentName = fileBase(node);
+ parentName.replace(QLatin1Char('/'), QLatin1Char('-')).replace(QLatin1Char('.'), QLatin1Char('-'));
+ parentName += QLatin1Char('.') + currentGenerator()->fileExtension();
+ }
+ break;
+ default:
+ break;
+ }
+
+ // Various objects can be compat (deprecated) or obsolete.
+ // Is this even correct?
+ if (!node->isClass() && !node->isNamespace()) {
+ switch (node->status()) {
+ case Node::Compat:
+ parentName.replace(QLatin1Char('.') + currentGenerator()->fileExtension(),
+ "-compat." + currentGenerator()->fileExtension());
+ break;
+ case Node::Obsolete:
+ parentName.replace(QLatin1Char('.') + currentGenerator()->fileExtension(),
+ "-obsolete." + currentGenerator()->fileExtension());
+ break;
+ default:
+ ;
+ }
+ }
+
+ return fdl + parentName.toLower() + anchorRef;
+}
+
+void Generator::generateAlsoList(const Node *node, CodeMarker *marker)
+{
+ QList<Text> alsoList = node->doc().alsoList();
+ supplementAlsoList(node, alsoList);
+
+ if (!alsoList.isEmpty()) {
+ Text text;
+ text << Atom::ParaLeft
+ << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
+ << "See also "
+ << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
+
+ for (int i = 0; i < alsoList.size(); ++i)
+ text << alsoList.at(i) << separator(i, alsoList.size());
+
+ text << Atom::ParaRight;
+ generateText(text, node, marker);
+ }
+}
+
+int Generator::generateAtom(const Atom * /* atom */,
+ const Node * /* relative */,
+ CodeMarker * /* marker */)
+{
+ return 0;
+}
+
+const Atom *Generator::generateAtomList(const Atom *atom,
+ const Node *relative,
+ CodeMarker *marker,
+ bool generate,
+ int &numAtoms)
+{
+ while (atom) {
+ if (atom->type() == Atom::FormatIf) {
+ int numAtoms0 = numAtoms;
+ bool rightFormat = canHandleFormat(atom->string());
+ atom = generateAtomList(atom->next(),
+ relative,
+ marker,
+ generate && rightFormat,
+ numAtoms);
+ if (!atom)
+ return 0;
+
+ if (atom->type() == Atom::FormatElse) {
+ ++numAtoms;
+ atom = generateAtomList(atom->next(),
+ relative,
+ marker,
+ generate && !rightFormat,
+ numAtoms);
+ if (!atom)
+ return 0;
+ }
+
+ 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,
+ marker,
+ generate,
+ numAtoms);
+ }
+ atom = atom->next();
+ }
+ }
+ else if (atom->type() == Atom::FormatElse ||
+ atom->type() == Atom::FormatEndif) {
+ return atom;
+ }
+ else {
+ int n = 1;
+ if (generate) {
+ n += generateAtom(atom, relative, marker);
+ numAtoms += n;
+ }
+ while (n-- > 0)
+ atom = atom->next();
+ }
+ }
+ return 0;
+}
+
+/*!
+ Generate the body of the documentation from the qdoc comment
+ found with the entity represented by the \a node.
+ */
+void Generator::generateBody(const Node *node, CodeMarker *marker)
+{
+ bool quiet = false;
+
+ if (node->type() == Node::Document) {
+ const DocumentNode *dn = static_cast<const DocumentNode *>(node);
+ if ((dn->docSubtype() == Node::File) || (dn->docSubtype() == Node::Image)) {
+ quiet = true;
+ }
+ }
+ if (node->doc().isEmpty()) {
+ if (!node->isWrapper() && !quiet && !node->isReimplemented()) { // ### might be unnecessary
+ node->location().warning(tr("No documentation for '%1'").arg(node->plainFullName()));
+ }
+ }
+ else {
+ if (node->type() == Node::Function) {
+ const FunctionNode *func = static_cast<const FunctionNode *>(node);
+ if (func->reimplementedFrom() != 0)
+ generateReimplementedFrom(func, marker);
+ }
+
+ if (!generateText(node->doc().body(), node, marker)) {
+ if (node->isReimplemented())
+ return;
+ }
+
+ if (node->type() == Node::Enum) {
+ const EnumNode *enume = (const EnumNode *) node;
+
+ QSet<QString> definedItems;
+ QList<EnumItem>::ConstIterator it = enume->items().constBegin();
+ while (it != enume->items().constEnd()) {
+ definedItems.insert((*it).name());
+ ++it;
+ }
+
+ QSet<QString> documentedItems = enume->doc().enumItemNames().toSet();
+ QSet<QString> allItems = definedItems + documentedItems;
+ if (allItems.count() > definedItems.count() ||
+ allItems.count() > documentedItems.count()) {
+ QSet<QString>::ConstIterator a = allItems.constBegin();
+ while (a != allItems.constEnd()) {
+ if (!definedItems.contains(*a)) {
+ QString details;
+ QString best = nearestName(*a, definedItems);
+ if (!best.isEmpty() && !documentedItems.contains(best))
+ details = tr("Maybe you meant '%1'?").arg(best);
+
+ node->doc().location().warning(tr("No such enum item '%1' in %2")
+ .arg(*a).arg(node->plainFullName()), details);
+ if (*a == "Void")
+ qDebug() << "VOID:" << node->name() << definedItems;
+ }
+ else if (!documentedItems.contains(*a)) {
+ node->doc().location().warning(tr("Undocumented enum item '%1' in %2")
+ .arg(*a).arg(node->plainFullName()));
+ }
+ ++a;
+ }
+ }
+ }
+ else if (node->type() == Node::Function) {
+ const FunctionNode *func = static_cast<const FunctionNode *>(node);
+ QSet<QString> definedParams;
+ QVector<Parameter>::ConstIterator p = func->parameters().constBegin();
+ while (p != func->parameters().constEnd()) {
+ if ((*p).name().isEmpty() && (*p).dataType() != QLatin1String("...")
+ && (*p).dataType() != QLatin1String("void")
+ && func->name() != QLatin1String("operator++")
+ && func->name() != QLatin1String("operator--")) {
+ node->doc().location().warning(tr("Missing parameter name"));
+ }
+ else {
+ definedParams.insert((*p).name());
+ }
+ ++p;
+ }
+
+ QSet<QString> documentedParams = func->doc().parameterNames();
+ QSet<QString> allParams = definedParams + documentedParams;
+ if (allParams.count() > definedParams.count()
+ || allParams.count() > documentedParams.count()) {
+ QSet<QString>::ConstIterator a = allParams.constBegin();
+ while (a != allParams.constEnd()) {
+ if (!definedParams.contains(*a)) {
+ QString details;
+ QString best = nearestName(*a, definedParams);
+ if (!best.isEmpty())
+ details = tr("Maybe you meant '%1'?").arg(best);
+
+ node->doc().location().warning(
+ tr("No such parameter '%1' in %2").arg(*a).arg(node->plainFullName()),
+ details);
+ }
+ else if (!(*a).isEmpty() && !documentedParams.contains(*a)) {
+ bool needWarning = (func->status() > Node::Obsolete);
+ if (func->overloadNumber() > 0) {
+ FunctionNode *primaryFunc = func->parent()->findFunctionNode(func->name(), QString());
+ if (primaryFunc) {
+ foreach (const Parameter &param,
+ primaryFunc->parameters()) {
+ if (param.name() == *a) {
+ needWarning = false;
+ break;
+ }
+ }
+ }
+ }
+ if (needWarning && !func->isReimplemented())
+ node->doc().location().warning(
+ tr("Undocumented parameter '%1' in %2")
+ .arg(*a).arg(node->plainFullName()));
+ }
+ ++a;
+ }
+ }
+ /*
+ Something like this return value check should
+ be implemented at some point.
+ */
+ if (func->status() > Node::Obsolete && func->returnType() == "bool"
+ && func->reimplementedFrom() == 0 && !func->isOverload()) {
+ QString body = func->doc().body().toString();
+ if (!body.contains("return", Qt::CaseInsensitive))
+ node->doc().location().warning(tr("Undocumented return value"));
+ }
+ }
+ }
+
+ if (node->isDocumentNode()) {
+ const DocumentNode *dn = static_cast<const DocumentNode *>(node);
+ if (dn->isExample()) {
+ generateExampleFiles(dn, marker);
+ }
+ else if (dn->docSubtype() == Node::File) {
+ Text text;
+ Quoter quoter;
+ Doc::quoteFromFile(dn->doc().location(), quoter, dn->name());
+ QString code = quoter.quoteTo(dn->location(), QString(), QString());
+ CodeMarker *codeMarker = CodeMarker::markerForFileName(dn->name());
+ text << Atom(codeMarker->atomType(), code);
+ generateText(text, dn, codeMarker);
+ }
+ }
+}
+
+void Generator::generateClassLikeNode(Aggregate* /* classe */, CodeMarker* /* marker */)
+{
+}
+
+void Generator::generateExampleFiles(const DocumentNode *dn, CodeMarker *marker)
+{
+ if (dn->childNodes().isEmpty())
+ return;
+ generateFileList(dn, marker, Node::File, QString("Files:"));
+ generateFileList(dn, marker, Node::Image, QString("Images:"));
+}
+
+void Generator::generateDocumentNode(DocumentNode* /* dn */, CodeMarker* /* marker */)
+{
+}
+
+void Generator::generateCollectionNode(CollectionNode* , CodeMarker* )
+{
+}
+
+/*!
+ This function is called when the documentation for an
+ example is being formatted. It outputs the list of source
+ files comprising the example, and the list of images used
+ by the example. The images are copied into a subtree of
+ \c{...doc/html/images/used-in-examples/...}
+ */
+void Generator::generateFileList(const DocumentNode* dn,
+ CodeMarker* marker,
+ Node::DocSubtype subtype,
+ const QString& tag)
+{
+ int count = 0;
+ Text text;
+ OpenedList openedList(OpenedList::Bullet);
+
+ text << Atom::ParaLeft << tag << Atom::ParaRight
+ << Atom(Atom::ListLeft, openedList.styleString());
+
+ foreach (const Node* child, dn->childNodes()) {
+ if (child->docSubtype() == subtype) {
+ ++count;
+ QString file = child->name();
+ if (subtype == Node::Image) {
+ if (!file.isEmpty()) {
+ QDir dirInfo;
+ QString userFriendlyFilePath;
+ const QString prefix("/images/used-in-examples/");
+ QString srcPath = Config::findFile(dn->location(),
+ QStringList(),
+ exampleDirs,
+ file,
+ exampleImgExts,
+ userFriendlyFilePath);
+ outFileNames_ << prefix.mid(1) + userFriendlyFilePath;
+ userFriendlyFilePath.truncate(userFriendlyFilePath.lastIndexOf('/'));
+ QString imgOutDir = outDir_ + prefix + userFriendlyFilePath;
+ if (!dirInfo.mkpath(imgOutDir))
+ dn->location().fatal(tr("Cannot create output directory '%1'").arg(imgOutDir));
+ Config::copyFile(dn->location(), srcPath, file, imgOutDir);
+ }
+
+ }
+
+ openedList.next();
+ text << Atom(Atom::ListItemNumber, openedList.numberString())
+ << Atom(Atom::ListItemLeft, openedList.styleString())
+ << Atom::ParaLeft
+ << Atom(Atom::Link, file)
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
+ << file
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK)
+ << Atom::ParaRight
+ << Atom(Atom::ListItemRight, openedList.styleString());
+ }
+ }
+ text << Atom(Atom::ListRight, openedList.styleString());
+ if (count > 0)
+ generateText(text, dn, marker);
+}
+
+void Generator::generateInheritedBy(const ClassNode *classe, CodeMarker *marker)
+{
+ if (!classe->derivedClasses().isEmpty()) {
+ Text text;
+ text << Atom::ParaLeft
+ << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
+ << "Inherited by: "
+ << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
+
+ appendSortedNames(text, classe, classe->derivedClasses());
+ text << Atom::ParaRight;
+ generateText(text, classe, marker);
+ }
+}
+
+void Generator::generateInherits(const ClassNode *classe, CodeMarker *marker)
+{
+ QList<RelatedClass>::ConstIterator r;
+ int index;
+
+ if (!classe->baseClasses().isEmpty()) {
+ Text text;
+ text << Atom::ParaLeft
+ << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
+ << "Inherits: "
+ << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
+
+ r = classe->baseClasses().constBegin();
+ index = 0;
+ while (r != classe->baseClasses().constEnd()) {
+ if ((*r).node_) {
+ text << Atom(Atom::LinkNode, CodeMarker::stringForNode((*r).node_))
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
+ << Atom(Atom::String, (*r).signature_)
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
+
+ if ((*r).access_ == Node::Protected) {
+ text << " (protected)";
+ }
+ else if ((*r).access_ == Node::Private) {
+ text << " (private)";
+ }
+ text << separator(index++, classe->baseClasses().count());
+ }
+ ++r;
+ }
+ text << Atom::ParaRight;
+ generateText(text, classe, marker);
+ }
+}
+
+/*!
+ Recursive writing of HTML files from the root \a node.
+ */
+void Generator::generateAggregate(Aggregate* node)
+{
+ if (!node->url().isNull())
+ return;
+ if (node->isIndexNode())
+ return;
+ if (node->isInternal() && !showInternal_)
+ return;
+
+ if (node->isDocumentNode()) {
+ DocumentNode* docNode = static_cast<DocumentNode*>(node);
+ if (docNode->docSubtype() == Node::ExternalPage)
+ return;
+ if (docNode->docSubtype() == Node::Image)
+ return;
+ if (docNode->docSubtype() == Node::Page) {
+ if (node->count() > 0)
+ qDebug("PAGE %s HAS CHILDREN", qPrintable(docNode->title()));
+ }
+ }
+ else if (node->isQmlPropertyGroup() || node->isJsPropertyGroup())
+ return;
+
+ /*
+ Obtain a code marker for the source file.
+ */
+ CodeMarker *marker = CodeMarker::markerForFileName(node->location().filePath());
+
+ if (node->parent() != 0) {
+ if ((node->isNamespace() && node->status() != Node::Intermediate)
+ || node->isClass()) {
+ beginSubPage(node, fileName(node));
+ generateClassLikeNode(node, marker);
+ endSubPage();
+ }
+ if (node->isQmlType() || node->isJsType()) {
+ beginSubPage(node, fileName(node));
+ QmlTypeNode* qcn = static_cast<QmlTypeNode*>(node);
+ generateQmlTypePage(qcn, marker);
+ endSubPage();
+ }
+ else if (node->isDocumentNode()) {
+ beginSubPage(node, fileName(node));
+ generateDocumentNode(static_cast<DocumentNode*>(node), marker);
+ endSubPage();
+ }
+ else if (node->isQmlBasicType() || node->isJsBasicType()) {
+ beginSubPage(node, fileName(node));
+ QmlBasicTypeNode* qbtn = static_cast<QmlBasicTypeNode*>(node);
+ generateQmlBasicTypePage(qbtn, marker);
+ endSubPage();
+ }
+ else if (node->isCollectionNode()) {
+ /*
+ A collection node collects: groups, C++ modules,
+ QML modules or JavaScript modules.
+
+ 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.
+ */
+ CollectionNode* cn = static_cast<CollectionNode*>(node);
+ if (cn->wasSeen()) {
+ qdb_->mergeCollections(cn);
+ beginSubPage(node, fileName(node));
+ generateCollectionNode(cn, marker);
+ endSubPage();
+ }
+ }
+ }
+
+ int i = 0;
+ while (i < node->childNodes().count()) {
+ Node *c = node->childNodes().at(i);
+ if (c->isAggregate() && c->access() != Node::Private) {
+ generateAggregate((Aggregate*)c);
+ }
+ ++i;
+ }
+}
+
+/*!
+ Generate a list of maintainers in the output
+ */
+void Generator::generateMaintainerList(const Aggregate* node, CodeMarker* marker)
+{
+ QStringList sl = getMetadataElements(node,"maintainer");
+
+ if (!sl.isEmpty()) {
+ Text text;
+ text << Atom::ParaLeft
+ << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
+ << "Maintained by: "
+ << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
+
+ for (int i = 0; i < sl.size(); ++i)
+ text << sl.at(i) << separator(i, sl.size());
+
+ text << Atom::ParaRight;
+ generateText(text, node, marker);
+ }
+}
+
+/*!
+ Output the "Inherit by" list for the QML element,
+ if it is inherited by any other elements.
+ */
+void Generator::generateQmlInheritedBy(const QmlTypeNode* qcn,
+ CodeMarker* marker)
+{
+ if (qcn) {
+ NodeList subs;
+ QmlTypeNode::subclasses(qcn->name(),subs);
+ if (!subs.isEmpty()) {
+ Text text;
+ text << Atom::ParaLeft << "Inherited by ";
+ appendSortedQmlNames(text,qcn,subs);
+ text << Atom::ParaRight;
+ generateText(text, qcn, marker);
+ }
+ }
+}
+
+/*!
+ */
+void Generator::generateQmlInherits(QmlTypeNode* , CodeMarker* )
+{
+ // stub.
+}
+
+/*!
+ Extract sections of markup text surrounded by \e qmltext
+ and \e endqmltext and output them.
+ */
+bool Generator::generateQmlText(const Text& text,
+ const Node *relative,
+ CodeMarker *marker,
+ const QString& /* qmlName */ )
+{
+ const Atom* atom = text.firstAtom();
+ bool result = false;
+
+ if (atom != 0) {
+ 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, marker);
+ while (n-- > 0)
+ atom = atom->next();
+ }
+ }
+ }
+ result = true;
+ }
+ return result;
+}
+
+void Generator::generateReimplementedFrom(const FunctionNode *func,
+ CodeMarker *marker)
+{
+ if (func->reimplementedFrom() != 0) {
+ const FunctionNode *from = func->reimplementedFrom();
+ if (from->access() != Node::Private &&
+ from->parent()->access() != Node::Private) {
+ Text text;
+ text << Atom::ParaLeft << "Reimplemented from ";
+ QString fullName = from->parent()->name() + "::" + from->name() + "()";
+ appendFullName(text, from->parent(), fullName, from);
+ text << "." << Atom::ParaRight;
+ generateText(text, func, marker);
+ }
+ }
+}
+
+void Generator::generateSince(const Node *node, CodeMarker *marker)
+{
+ if (!node->since().isEmpty()) {
+ Text text;
+ text << Atom::ParaLeft
+ << "This "
+ << typeString(node);
+ if (node->type() == Node::Enum)
+ text << " was introduced or modified in ";
+ else
+ text << " was introduced in ";
+
+ QStringList since = node->since().split(QLatin1Char(' '));
+ if (since.count() == 1) {
+ // If there is only one argument, assume it is the Qt version number.
+ text << " Qt " << since[0];
+ } else {
+ // Otherwise, reconstruct the <project> <version> string.
+ text << " " << since.join(' ');
+ }
+
+ text << "." << Atom::ParaRight;
+ generateText(text, node, marker);
+ }
+}
+
+void Generator::generateStatus(const Node *node, CodeMarker *marker)
+{
+ Text text;
+
+ switch (node->status()) {
+ case Node::Active:
+ // Do nothing.
+ break;
+ case Node::Preliminary:
+ text << Atom::ParaLeft
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
+ << "This "
+ << typeString(node)
+ << " is under development and is subject to change."
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
+ << Atom::ParaRight;
+ break;
+ case Node::Deprecated:
+ text << Atom::ParaLeft;
+ if (node->isAggregate())
+ text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
+ text << "This " << typeString(node) << " is deprecated.";
+ if (node->isAggregate())
+ text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
+ text << Atom::ParaRight;
+ break;
+ case Node::Obsolete:
+ text << Atom::ParaLeft;
+ if (node->isAggregate())
+ text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
+ text << "This " << typeString(node) << " is obsolete.";
+ if (node->isAggregate())
+ text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
+ text << " It is provided to keep old source code working. "
+ << "We strongly advise against "
+ << "using it in new code." << Atom::ParaRight;
+ break;
+ case Node::Compat:
+ // reimplemented in HtmlGenerator subclass
+ if (node->isAggregate()) {
+ text << Atom::ParaLeft
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
+ << "This "
+ << typeString(node)
+ << " is part of the Qt compatibility layer."
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
+ << " It is provided to keep old source code working. "
+ << "We strongly advise against using it in new code."
+ << Atom::ParaRight;
+ }
+ break;
+ case Node::Internal:
+ default:
+ break;
+ }
+ generateText(text, node, marker);
+}
+
+/*!
+ Generates a bold line that says:
+ "The signal is private, not emitted by the user.
+ The function is public so the user can pass it to connect()."
+ */
+void Generator::generatePrivateSignalNote(const Node* node, CodeMarker* marker)
+{
+ Text text;
+ text << Atom::ParaLeft
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
+ << "Note: "
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
+ << "This is a private signal. It can be used in signal connections but cannot be emitted by the user."
+ << Atom::ParaRight;
+ generateText(text, node, marker);
+}
+
+/*!
+ Generate the documentation for \a relative. i.e. \a relative
+ is the node that reporesentas the entity where a qdoc comment
+ was found, and \a text represents the qdoc comment.
+ */
+bool Generator::generateText(const Text& text,
+ const Node *relative,
+ CodeMarker *marker)
+{
+ bool result = false;
+ if (text.firstAtom() != 0) {
+ int numAtoms = 0;
+ initializeTextOutput();
+ generateAtomList(text.firstAtom(),
+ relative,
+ marker,
+ true,
+ numAtoms);
+ result = true;
+ }
+ return result;
+}
+
+void Generator::generateThreadSafeness(const Node *node, CodeMarker *marker)
+{
+ Text text;
+ Node::ThreadSafeness threadSafeness = node->threadSafeness();
+
+ Text rlink;
+ rlink << Atom(Atom::Link,"reentrant")
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
+ << "reentrant"
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
+
+ Text tlink;
+ tlink << Atom(Atom::Link,"thread-safe")
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
+ << "thread-safe"
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
+
+ switch (threadSafeness) {
+ case Node::UnspecifiedSafeness:
+ break;
+ case Node::NonReentrant:
+ text << Atom::ParaLeft
+ << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
+ << "Warning:"
+ << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD)
+ << " This "
+ << typeString(node)
+ << " is not "
+ << rlink
+ << "."
+ << Atom::ParaRight;
+ break;
+ case Node::Reentrant:
+ case Node::ThreadSafe:
+ text << Atom::ParaLeft
+ << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
+ << "Note:"
+ << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD)
+ << " ";
+
+ if (node->isAggregate()) {
+ const Aggregate* innerNode = static_cast<const Aggregate*>(node);
+ text << "All functions in this "
+ << typeString(node)
+ << " are ";
+ if (threadSafeness == Node::ThreadSafe)
+ text << tlink;
+ else
+ text << rlink;
+
+ bool exceptions = false;
+ NodeList reentrant;
+ NodeList threadsafe;
+ NodeList nonreentrant;
+ NodeList::ConstIterator c = innerNode->childNodes().constBegin();
+ while (c != innerNode->childNodes().constEnd()) {
+
+ if ((*c)->status() != Node::Obsolete){
+ switch ((*c)->threadSafeness()) {
+ case Node::Reentrant:
+ reentrant.append(*c);
+ if (threadSafeness == Node::ThreadSafe)
+ exceptions = true;
+ break;
+ case Node::ThreadSafe:
+ threadsafe.append(*c);
+ if (threadSafeness == Node::Reentrant)
+ exceptions = true;
+ break;
+ case Node::NonReentrant:
+ nonreentrant.append(*c);
+ exceptions = true;
+ break;
+ default:
+ break;
+ }
+ }
+ ++c;
+ }
+ if (!exceptions)
+ text << ".";
+ else if (threadSafeness == Node::Reentrant) {
+ if (nonreentrant.isEmpty()) {
+ if (!threadsafe.isEmpty()) {
+ text << ", but ";
+ appendFullNames(text,threadsafe,innerNode);
+ singularPlural(text,threadsafe);
+ text << " also " << tlink << ".";
+ }
+ else
+ text << ".";
+ }
+ else {
+ text << ", except for ";
+ appendFullNames(text,nonreentrant,innerNode);
+ text << ", which";
+ singularPlural(text,nonreentrant);
+ text << " nonreentrant.";
+ if (!threadsafe.isEmpty()) {
+ text << " ";
+ appendFullNames(text,threadsafe,innerNode);
+ singularPlural(text,threadsafe);
+ text << " " << tlink << ".";
+ }
+ }
+ }
+ else { // thread-safe
+ if (!nonreentrant.isEmpty() || !reentrant.isEmpty()) {
+ text << ", except for ";
+ if (!reentrant.isEmpty()) {
+ appendFullNames(text,reentrant,innerNode);
+ text << ", which";
+ singularPlural(text,reentrant);
+ text << " only " << rlink;
+ if (!nonreentrant.isEmpty())
+ text << ", and ";
+ }
+ if (!nonreentrant.isEmpty()) {
+ appendFullNames(text,nonreentrant,innerNode);
+ text << ", which";
+ singularPlural(text,nonreentrant);
+ text << " nonreentrant.";
+ }
+ text << ".";
+ }
+ }
+ }
+ else {
+ text << "This " << typeString(node) << " is ";
+ if (threadSafeness == Node::ThreadSafe)
+ text << tlink;
+ else
+ text << rlink;
+ text << ".";
+ }
+ text << Atom::ParaRight;
+ }
+ generateText(text,node,marker);
+}
+
+/*!
+ If the node is an overloaded signal, and a node with an example on how to connect to it
+ */
+void Generator::generateOverloadedSignal(const Node* node, CodeMarker* marker)
+{
+ if (node->type() != Node::Function)
+ return;
+ const FunctionNode *func = static_cast<const FunctionNode *>(node);
+ if (func->metaness() != FunctionNode::Signal)
+ return;
+ if (node->parent()->overloads(node->name()).count() <= 1)
+ return;
+
+
+ // Compute a friendly name for the object of that instance.
+ // e.g: "QAbstractSocket" -> "abstractSocket"
+ QString objectName = node->parent()->name();
+ if (objectName.size() >= 2) {
+ if (objectName[0] == 'Q')
+ objectName = objectName.mid(1);
+ objectName[0] = objectName[0].toLower();
+ }
+
+
+ // We have an overloaded signal, show an example
+ QString code = "connect(" + objectName + ", static_cast<" + func->returnType()
+ + QLatin1Char('(') + func->parent()->name() + "::*)(";
+ for (int i = 0; i < func->parameters().size(); ++i) {
+ if (i != 0)
+ code += ", ";
+ const Parameter &p = func->parameters().at(i);
+ code += p.dataType() + p.rightType();
+ }
+
+ code += QLatin1Char(')');
+ if (func->isConst())
+ code += " const";
+ code += ">(&" + func->parent()->name() + "::" + func->name() + "),\n [=](";
+
+ for (int i = 0; i < func->parameters().size(); ++i) {
+ if (i != 0)
+ code += ", ";
+ const Parameter &p = func->parameters().at(i);
+ code += p.dataType();
+ if (code[code.size()-1].isLetterOrNumber())
+ code += QLatin1Char(' ');
+ code += p.name() + p.rightType();
+ }
+
+ code += "){ /* ... */ });";
+
+ Text text;
+ text << Atom::ParaLeft
+ << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
+ << "Note:"
+ << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD)
+ << "Signal "
+ << Atom(Atom::FormattingLeft,ATOM_FORMATTING_ITALIC)
+ << node->name()
+ << Atom(Atom::FormattingRight,ATOM_FORMATTING_ITALIC)
+ << " is overloaded in this class. "
+ "To connect to this one using the function pointer syntax, you must "
+ "specify the signal type in a static cast, as shown in this example:"
+ << Atom(Atom::Code, marker->markedUpCode(code, node, func->location()));
+
+ generateText(text, node, marker);
+}
+
+
+/*!
+ Traverses the database recursivly to generate all the documentation.
+ */
+void Generator::generateDocs()
+{
+ generateAggregate(qdb_->primaryTreeRoot());
+}
+
+Generator *Generator::generatorForFormat(const QString& format)
+{
+ QList<Generator *>::ConstIterator g = generators.constBegin();
+ while (g != generators.constEnd()) {
+ if ((*g)->format() == format)
+ return *g;
+ ++g;
+ }
+ return 0;
+}
+
+/*!
+ Looks up the tag \a t in the map of metadata values for the
+ current topic in \a inner. If a value for the tag is found,
+ the value is returned.
+
+ \note If \a t is found in the metadata map, it is erased.
+ i.e. Once you call this function for a particular \a t,
+ you consume \a t.
+ */
+QString Generator::getMetadataElement(const Aggregate* inner, const QString& t)
+{
+ QString s;
+ QStringMultiMap& metaTagMap = const_cast<QStringMultiMap&>(inner->doc().metaTagMap());
+ QStringMultiMap::iterator i = metaTagMap.find(t);
+ if (i != metaTagMap.end()) {
+ s = i.value();
+ metaTagMap.erase(i);
+ }
+ return s;
+}
+
+/*!
+ Looks up the tag \a t in the map of metadata values for the
+ current topic in \a inner. If values for the tag are found,
+ they are returned in a string list.
+
+ \note If \a t is found in the metadata map, all the pairs
+ having the key \a t are erased. i.e. Once you call this
+ function for a particular \a t, you consume \a t.
+ */
+QStringList Generator::getMetadataElements(const Aggregate* inner, const QString& t)
+{
+ QStringList s;
+ QStringMultiMap& metaTagMap = const_cast<QStringMultiMap&>(inner->doc().metaTagMap());
+ s = metaTagMap.values(t);
+ if (!s.isEmpty())
+ metaTagMap.remove(t);
+ return s;
+}
+
+/*!
+ Returns a relative path name for an image.
+ */
+QString Generator::imageFileName(const Node *relative, const QString& fileBase)
+{
+ QString userFriendlyFilePath;
+ QString filePath = Config::findFile(relative->doc().location(),
+ imageFiles,
+ imageDirs,
+ fileBase,
+ imgFileExts[format()],
+ userFriendlyFilePath);
+
+ if (filePath.isEmpty())
+ return QString();
+
+ QString path = Config::copyFile(relative->doc().location(),
+ filePath,
+ userFriendlyFilePath,
+ outputDir() + QLatin1String("/images"));
+ int images_slash = path.lastIndexOf("images/");
+ QString relImagePath;
+ if (images_slash != -1)
+ relImagePath = path.mid(images_slash);
+ return relImagePath;
+}
+
+QString Generator::indent(int level, const QString& markedCode)
+{
+ if (level == 0)
+ return markedCode;
+
+ QString t;
+ int column = 0;
+
+ int i = 0;
+ while (i < (int) markedCode.length()) {
+ if (markedCode.at(i) == QLatin1Char('\n')) {
+ column = 0;
+ }
+ else {
+ if (column == 0) {
+ for (int j = 0; j < level; j++)
+ t += QLatin1Char(' ');
+ }
+ column++;
+ }
+ t += markedCode.at(i++);
+ }
+ return t;
+}
+
+void Generator::initialize(const Config &config)
+{
+
+ if (config.getBool(QString("HTML.nosubdirs")))
+ resetUseOutputSubdirs();
+
+ outFileNames_.clear();
+ outputFormats = config.getOutputFormats();
+ redirectDocumentationToDevNull_ = config.getBool(CONFIG_REDIRECTDOCUMENTATIONTODEVNULL);
+ if (!outputFormats.isEmpty()) {
+ outDir_ = config.getOutputDir();
+ if (outDir_.isEmpty()) {
+ config.lastLocation().fatal(tr("No output directory specified in "
+ "configuration file or on the command line"));
+ }
+ else {
+ outSubdir_ = outDir_.mid(outDir_.lastIndexOf('/') + 1);
+ }
+
+ QDir dirInfo;
+ if (dirInfo.exists(outDir_)) {
+ if (!generating() && Generator::useOutputSubdirs()) {
+ if (!Config::removeDirContents(outDir_))
+ config.lastLocation().error(tr("Cannot empty output directory '%1'").arg(outDir_));
+ }
+ }
+ else {
+ if (!dirInfo.mkpath(outDir_))
+ config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_));
+ }
+
+ if (!dirInfo.exists(outDir_ + "/images") && !dirInfo.mkdir(outDir_ + "/images"))
+ config.lastLocation().fatal(tr("Cannot create images directory '%1'").arg(outDir_ + "/images"));
+ }
+
+ imageFiles = config.getCanonicalPathList(CONFIG_IMAGES);
+ imageDirs = config.getCanonicalPathList(CONFIG_IMAGEDIRS);
+ scriptFiles = config.getCanonicalPathList(CONFIG_SCRIPTS);
+ scriptDirs = config.getCanonicalPathList(CONFIG_SCRIPTDIRS);
+ styleFiles = config.getCanonicalPathList(CONFIG_STYLES);
+ styleDirs = config.getCanonicalPathList(CONFIG_STYLEDIRS);
+ exampleDirs = config.getCanonicalPathList(CONFIG_EXAMPLEDIRS);
+ exampleImgExts = config.getStringList(CONFIG_EXAMPLES + Config::dot + CONFIG_IMAGEEXTENSIONS);
+
+ QString imagesDotFileExtensions = CONFIG_IMAGES + Config::dot + CONFIG_FILEEXTENSIONS;
+ QSet<QString> formats = config.subVars(imagesDotFileExtensions);
+ QSet<QString>::ConstIterator f = formats.constBegin();
+ while (f != formats.constEnd()) {
+ imgFileExts[*f] = config.getStringList(imagesDotFileExtensions + Config::dot + *f);
+ ++f;
+ }
+
+ QList<Generator *>::ConstIterator g = generators.constBegin();
+ while (g != generators.constEnd()) {
+ if (outputFormats.contains((*g)->format())) {
+ currentGenerator_ = (*g);
+ (*g)->initializeGenerator(config);
+ QStringList extraImages = config.getCanonicalPathList((*g)->format() +
+ Config::dot +
+ CONFIG_EXTRAIMAGES, true);
+ QStringList::ConstIterator e = extraImages.constBegin();
+ while (e != extraImages.constEnd()) {
+ QString filePath = *e;
+ if (!filePath.isEmpty())
+ Config::copyFile(config.lastLocation(), filePath, filePath,
+ (*g)->outputDir() + "/images");
+ ++e;
+ }
+
+ // Documentation template handling
+ QStringList scripts = config.getCanonicalPathList((*g)->format()+Config::dot+CONFIG_SCRIPTS, true);
+ if (!scripts.isEmpty()) {
+ QDir dirInfo;
+ if (!dirInfo.exists(outDir_ + "/scripts") && !dirInfo.mkdir(outDir_ + "/scripts")) {
+ config.lastLocation().fatal(tr("Cannot create scripts directory '%1'")
+ .arg(outDir_ + "/scripts"));
+ }
+ else {
+ e = scripts.constBegin();
+ while (e != scripts.constEnd()) {
+ QString filePath = *e;
+ if (!filePath.isEmpty())
+ Config::copyFile(config.lastLocation(), filePath, filePath,
+ (*g)->outputDir() + "/scripts");
+ ++e;
+ }
+ }
+ }
+
+ QStringList styles = config.getCanonicalPathList((*g)->format()+Config::dot+CONFIG_STYLESHEETS, true);
+ if (!styles.isEmpty()) {
+ QDir dirInfo;
+ if (!dirInfo.exists(outDir_ + "/style") && !dirInfo.mkdir(outDir_ + "/style")) {
+ config.lastLocation().fatal(tr("Cannot create style directory '%1'")
+ .arg(outDir_ + "/style"));
+ }
+ else {
+ e = styles.constBegin();
+ while (e != styles.constEnd()) {
+ QString filePath = *e;
+ if (!filePath.isEmpty())
+ Config::copyFile(config.lastLocation(), filePath, filePath,
+ (*g)->outputDir() + "/style");
+ ++e;
+ }
+ }
+ }
+ }
+ ++g;
+ }
+
+ QRegExp secondParamAndAbove("[\2-\7]");
+ QSet<QString> formattingNames = config.subVars(CONFIG_FORMATTING);
+ QSet<QString>::ConstIterator n = formattingNames.constBegin();
+ while (n != formattingNames.constEnd()) {
+ QString formattingDotName = CONFIG_FORMATTING + Config::dot + *n;
+ QSet<QString> formats = config.subVars(formattingDotName);
+ QSet<QString>::ConstIterator f = formats.constBegin();
+ while (f != formats.constEnd()) {
+ QString def = config.getString(formattingDotName + Config::dot + *f);
+ if (!def.isEmpty()) {
+ int numParams = Config::numParams(def);
+ int numOccs = def.count("\1");
+ if (numParams != 1) {
+ config.lastLocation().warning(tr("Formatting '%1' must "
+ "have exactly one "
+ "parameter (found %2)")
+ .arg(*n).arg(numParams));
+ }
+ else if (numOccs > 1) {
+ config.lastLocation().fatal(tr("Formatting '%1' must "
+ "contain exactly one "
+ "occurrence of '\\1' "
+ "(found %2)")
+ .arg(*n).arg(numOccs));
+ }
+ else {
+ int paramPos = def.indexOf("\1");
+ fmtLeftMaps[*f].insert(*n, def.left(paramPos));
+ fmtRightMaps[*f].insert(*n, def.mid(paramPos + 1));
+ }
+ }
+ ++f;
+ }
+ ++n;
+ }
+
+ project_ = config.getString(CONFIG_PROJECT);
+
+ outputPrefixes.clear();
+ QStringList items = config.getStringList(CONFIG_OUTPUTPREFIXES);
+ if (!items.isEmpty()) {
+ foreach (const QString &prefix, items)
+ outputPrefixes[prefix] = config.getString(CONFIG_OUTPUTPREFIXES + Config::dot + prefix);
+ }
+ else {
+ outputPrefixes[QLatin1String("QML")] = QLatin1String("qml-");
+ outputPrefixes[QLatin1String("JS")] = QLatin1String("js-");
+ }
+
+ outputSuffixes.clear();
+ items = config.getStringList(CONFIG_OUTPUTSUFFIXES);
+ if (!items.isEmpty()) {
+ foreach (const QString &suffix, items)
+ outputSuffixes[suffix] = config.getString(CONFIG_OUTPUTSUFFIXES + Config::dot + suffix);
+ }
+
+ noLinkErrors_ = config.getBool(CONFIG_NOLINKERRORS);
+ autolinkErrors_ = config.getBool(CONFIG_AUTOLINKERRORS);
+}
+
+/*!
+ Appends each directory path in \a moreImageDirs to the
+ list of image directories.
+ */
+void Generator::augmentImageDirs(QSet<QString>& moreImageDirs)
+{
+ if (moreImageDirs.isEmpty())
+ return;
+ QSet<QString>::const_iterator i = moreImageDirs.begin();
+ while (i != moreImageDirs.end()) {
+ imageDirs.append(*i);
+ ++i;
+ }
+}
+
+/*!
+ Sets the generator's pointer to the Config instance.
+ */
+void Generator::initializeGenerator(const Config& config)
+{
+ config_ = &config;
+ showInternal_ = config.getBool(CONFIG_SHOWINTERNAL);
+ singleExec_ = config.getBool(CONFIG_SINGLEEXEC);
+}
+
+bool Generator::matchAhead(const Atom *atom, Atom::AtomType expectedAtomType)
+{
+ return atom->next() != 0 && atom->next()->type() == expectedAtomType;
+}
+
+/*!
+ Used for writing to the current output stream. Returns a
+ reference to the current output stream, which is then used
+ with the \c {<<} operator for writing.
+ */
+QTextStream &Generator::out()
+{
+ return *outStreamStack.top();
+}
+
+QString Generator::outFileName()
+{
+ return QFileInfo(static_cast<QFile*>(out().device())->fileName()).fileName();
+}
+
+QString Generator::outputPrefix(const Node *node)
+{
+ // Prefix is applied to QML and JS types
+ if (node->isQmlType() || node->isQmlBasicType())
+ return outputPrefixes[QLatin1String("QML")];
+ if (node->isJsType() || node->isJsBasicType())
+ return outputPrefixes[QLatin1String("JS")];
+ return QString();
+}
+
+QString Generator::outputSuffix(const Node *node)
+{
+ // Suffix is applied to QML and JS types, as
+ // well as module pages.
+ if (node->isQmlModule() || node->isQmlType() || node->isQmlBasicType())
+ return outputSuffixes[QLatin1String("QML")];
+ if (node->isJsModule() || node->isJsType() || node->isJsBasicType())
+ return outputSuffixes[QLatin1String("JS")];
+ return QString();
+}
+
+bool Generator::parseArg(const QString& src,
+ const QString& tag,
+ int* pos,
+ int n,
+ QStringRef* contents,
+ QStringRef* par1,
+ bool debug)
+{
+#define SKIP_CHAR(c) \
+ if (debug) \
+ qDebug() << "looking for " << c << " at " << QString(src.data() + i, n - i); \
+ if (i >= n || src[i] != c) { \
+ if (debug) \
+ qDebug() << " char '" << c << "' not found"; \
+ return false; \
+} \
+ ++i;
+
+
+#define SKIP_SPACE \
+ while (i < n && src[i] == ' ') \
+ ++i;
+
+ int i = *pos;
+ int j = i;
+
+ // assume "<@" has been parsed outside
+ //SKIP_CHAR('<');
+ //SKIP_CHAR('@');
+
+ if (tag != QStringRef(&src, i, tag.length())) {
+ if (0 && debug)
+ qDebug() << "tag " << tag << " not found at " << i;
+ return false;
+ }
+
+ if (debug)
+ qDebug() << "haystack:" << src << "needle:" << tag << "i:" <<i;
+
+ // skip tag
+ i += tag.length();
+
+ // parse stuff like: linkTag("(<@link node=\"([^\"]+)\">).*(</@link>)");
+ if (par1) {
+ SKIP_SPACE;
+ // read parameter name
+ j = i;
+ while (i < n && src[i].isLetter())
+ ++i;
+ if (src[i] == '=') {
+ if (debug)
+ qDebug() << "read parameter" << QString(src.data() + j, i - j);
+ SKIP_CHAR('=');
+ SKIP_CHAR('"');
+ // skip parameter name
+ j = i;
+ while (i < n && src[i] != '"')
+ ++i;
+ *par1 = QStringRef(&src, j, i - j);
+ SKIP_CHAR('"');
+ SKIP_SPACE;
+ } else {
+ if (debug)
+ qDebug() << "no optional parameter found";
+ }
+ }
+ SKIP_SPACE;
+ SKIP_CHAR('>');
+
+ // find contents up to closing "</@tag>
+ j = i;
+ for (; true; ++i) {
+ if (i + 4 + tag.length() > n)
+ return false;
+ if (src[i] != '<')
+ continue;
+ if (src[i + 1] != '/')
+ continue;
+ if (src[i + 2] != '@')
+ continue;
+ if (tag != QStringRef(&src, i + 3, tag.length()))
+ continue;
+ if (src[i + 3 + tag.length()] != '>')
+ continue;
+ break;
+ }
+
+ *contents = QStringRef(&src, j, i - j);
+
+ i += tag.length() + 4;
+
+ *pos = i;
+ if (debug)
+ qDebug() << " tag " << tag << " found: pos now: " << i;
+ return true;
+#undef SKIP_CHAR
+}
+
+QString Generator::plainCode(const QString& markedCode)
+{
+ QString t = markedCode;
+ t.replace(tag, QString());
+ t.replace(quot, QLatin1String("\""));
+ t.replace(gt, QLatin1String(">"));
+ t.replace(lt, QLatin1String("<"));
+ t.replace(amp, QLatin1String("&"));
+ return t;
+}
+
+void Generator::setImageFileExtensions(const QStringList& extensions)
+{
+ imgFileExts[format()] = extensions;
+}
+
+void Generator::singularPlural(Text& text, const NodeList& nodes)
+{
+ if (nodes.count() == 1)
+ text << " is";
+ else
+ text << " are";
+}
+
+int Generator::skipAtoms(const Atom *atom, Atom::AtomType type) const
+{
+ int skipAhead = 0;
+ atom = atom->next();
+ while (atom != 0 && atom->type() != type) {
+ skipAhead++;
+ atom = atom->next();
+ }
+ return skipAhead;
+}
+
+/*!
+ Resets the variables used during text output.
+ */
+void Generator::initializeTextOutput()
+{
+ inLink_ = false;
+ inContents_ = false;
+ inSectionHeading_ = false;
+ inTableHeader_ = false;
+ numTableRows_ = 0;
+ threeColumnEnumValueTable_ = true;
+ link_.clear();
+ sectionNumber_.clear();
+}
+
+void Generator::supplementAlsoList(const Node *node, QList<Text> &alsoList)
+{
+ if (node->type() == Node::Function) {
+ const FunctionNode *func = static_cast<const FunctionNode *>(node);
+ if (func->overloadNumber() == 0) {
+ QString alternateName;
+ const FunctionNode *alternateFunc = 0;
+
+ if (func->name().startsWith("set") && func->name().size() >= 4) {
+ alternateName = func->name()[3].toLower();
+ alternateName += func->name().mid(4);
+ alternateFunc = func->parent()->findFunctionNode(alternateName, QString());
+
+ if (!alternateFunc) {
+ alternateName = "is" + func->name().mid(3);
+ alternateFunc = func->parent()->findFunctionNode(alternateName, QString());
+ if (!alternateFunc) {
+ alternateName = "has" + func->name().mid(3);
+ alternateFunc = func->parent()->findFunctionNode(alternateName, QString());
+ }
+ }
+ }
+ else if (!func->name().isEmpty()) {
+ alternateName = "set";
+ alternateName += func->name()[0].toUpper();
+ alternateName += func->name().mid(1);
+ alternateFunc = func->parent()->findFunctionNode(alternateName, QString());
+ }
+
+ if (alternateFunc && alternateFunc->access() != Node::Private) {
+ int i;
+ for (i = 0; i < alsoList.size(); ++i) {
+ if (alsoList.at(i).toString().contains(alternateName))
+ break;
+ }
+
+ if (i == alsoList.size()) {
+ alternateName += "()";
+
+ Text also;
+ also << Atom(Atom::Link, alternateName)
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
+ << alternateName
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
+ alsoList.prepend(also);
+ }
+ }
+ }
+ }
+}
+
+void Generator::terminate()
+{
+ QList<Generator *>::ConstIterator g = generators.constBegin();
+ while (g != generators.constEnd()) {
+ if (outputFormats.contains((*g)->format()))
+ (*g)->terminateGenerator();
+ ++g;
+ }
+
+ fmtLeftMaps.clear();
+ fmtRightMaps.clear();
+ imgFileExts.clear();
+ imageFiles.clear();
+ imageDirs.clear();
+ outDir_.clear();
+}
+
+void Generator::terminateGenerator()
+{
+}
+
+/*!
+ Trims trailing whitespace off the \a string and returns
+ the trimmed string.
+ */
+QString Generator::trimmedTrailing(const QString& string, const QString &prefix, const QString &suffix)
+{
+ QString trimmed = string;
+ while (trimmed.length() > 0 && trimmed[trimmed.length() - 1].isSpace())
+ trimmed.truncate(trimmed.length() - 1);
+
+ trimmed.append(suffix);
+ trimmed.prepend(prefix);
+ return trimmed;
+}
+
+QString Generator::typeString(const Node *node)
+{
+ switch (node->type()) {
+ case Node::Namespace:
+ return "namespace";
+ case Node::Class:
+ return "class";
+ case Node::QmlType:
+ return "type";
+ case Node::QmlBasicType:
+ return "type";
+ case Node::Document:
+ return "documentation";
+ case Node::Enum:
+ return "enum";
+ case Node::Typedef:
+ return "typedef";
+ case Node::Function:
+ return "function";
+ case Node::Property:
+ return "property";
+ case Node::QmlPropertyGroup:
+ return "property group";
+ case Node::QmlProperty:
+ return "QML property";
+ case Node::QmlSignal:
+ return "QML signal";
+ case Node::QmlSignalHandler:
+ return "QML signal handler";
+ case Node::QmlMethod:
+ return "QML method";
+ default:
+ return "documentation";
+ }
+}
+
+void Generator::unknownAtom(const Atom *atom)
+{
+ Location::internalError(tr("unknown atom type '%1' in %2 generator")
+ .arg(atom->typeString()).arg(format()));
+}
+
+QT_END_NAMESPACE