diff options
Diffstat (limited to 'src/qdoc/generator.cpp')
-rw-r--r-- | src/qdoc/generator.cpp | 2171 |
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("&"), + gt(">"), + lt("<"), + 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 ¶m, + 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 |