diff options
Diffstat (limited to 'src/qdoc/xmlgenerator.cpp')
-rw-r--r-- | src/qdoc/xmlgenerator.cpp | 478 |
1 files changed, 478 insertions, 0 deletions
diff --git a/src/qdoc/xmlgenerator.cpp b/src/qdoc/xmlgenerator.cpp new file mode 100644 index 000000000..05cd1b963 --- /dev/null +++ b/src/qdoc/xmlgenerator.cpp @@ -0,0 +1,478 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Thibaut Cuvelier +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + xmlgenerator.cpp +*/ + +#include "xmlgenerator.h" +#include "qdocdatabase.h" + +QT_BEGIN_NAMESPACE + +/*! + Do not display \brief for QML/JS types, document and collection nodes + */ +bool XmlGenerator::hasBrief(const Node *node) +{ + return !(node->isQmlType() + || node->isPageNode() + || node->isCollectionNode() + || node->isJsType()); +} + +/*! + Determines whether the list atom should be shown with three columns + (constant-value-description). + */ +bool XmlGenerator::isThreeColumnEnumValueTable(const Atom *atom) +{ + while (atom && !(atom->type() == Atom::ListRight && atom->string() == ATOM_LIST_VALUE)) { + if (atom->type() == Atom::ListItemLeft && !matchAhead(atom, Atom::ListItemRight)) + return true; + atom = atom->next(); + } + return false; +} + +/*! + Header offset depending on the type of the node + */ +int XmlGenerator::hOffset(const Node *node) +{ + switch (node->nodeType()) { + case Node::Namespace: + case Node::Class: + case Node::Struct: + case Node::Union: + case Node::Module: + return 2; + case Node::QmlModule: + case Node::QmlBasicType: + case Node::QmlType: + case Node::Page: + return 1; + case Node::Enum: + case Node::Typedef: + case Node::Function: + case Node::Property: + default: + return 3; + } +} + +/*! + Rewrites the brief of this node depending on its first word. + Only for properties and variables (does nothing otherwise). + */ +void XmlGenerator::rewritePropertyBrief(const Atom *atom, const Node *relative) +{ + if (relative->nodeType() == Node::Property || + relative->nodeType() == Node::Variable) { + atom = atom->next(); + if (atom && atom->type() == Atom::String) { + QString firstWord = + atom->string().toLower().section(' ', 0, 0, QString::SectionSkipEmpty); + if (firstWord == QLatin1String("the") + || firstWord == QLatin1String("a") + || firstWord == QLatin1String("an") + || firstWord == QLatin1String("whether") + || firstWord == QLatin1String("which")) { + QString str = QLatin1String("This ") + + QLatin1String(relative->nodeType() == Node::Property ? + "property" : "variable") + + QLatin1String(" holds ") + + atom->string().left(1).toLower() + + atom->string().mid(1); + const_cast<Atom *>(atom)->setString(str); + } + } + } +} + +/*! + Returns the type of this atom as an enumeration. + */ +Node::NodeType XmlGenerator::typeFromString(const Atom *atom) +{ + const auto &name = atom->string(); + if (name.startsWith(QLatin1String("qml"))) + return Node::QmlModule; + else if (name.startsWith(QLatin1String("js"))) + return Node::JsModule; + else if (name.startsWith(QLatin1String("groups"))) + return Node::Group; + else + return Node::Module; +} + +/*! + For images shown in examples, set the image file to the one it + will have once the documentation is generated. + */ +void XmlGenerator::setImageFileName(const Node *relative, const QString &fileName) +{ + if (relative->isExample()) { + const auto cen = static_cast<const ExampleNode *>(relative); + if (cen->imageFileName().isEmpty()) { + auto *en = const_cast<ExampleNode*>(cen); + en->setImageFileName(fileName); + } + } +} + +/*! + Handles the differences in lists between list tags and since tags, and + returns the content of the list entry \a atom (first member of the pair). + It also returns the number of items to skip ahead (second member of the pair). + */ +QPair<QString, int> XmlGenerator::getAtomListValue(const Atom *atom) +{ + const Atom *lookAhead = atom->next(); + if (!lookAhead) + return QPair<QString, int>(QString(), 1); + + QString t = lookAhead->string(); + lookAhead = lookAhead->next(); + if (!lookAhead || lookAhead->type() != Atom::ListTagRight) + return QPair<QString, int>(QString(), 1); + + lookAhead = lookAhead->next(); + int skipAhead; + if (lookAhead && lookAhead->type() == Atom::SinceTagLeft) { + lookAhead = lookAhead->next(); + Q_ASSERT(lookAhead && lookAhead->type() == Atom::String); + t += QLatin1String(" (since "); + if (lookAhead->string().at(0).isDigit()) + t += QLatin1String("Qt "); + t += lookAhead->string() + QLatin1String(")"); + skipAhead = 4; + } else { + skipAhead = 1; + } + return QPair<QString, int>(t, skipAhead); +} + +/*! + Parses the table attributes from the given \a atom. + This method returns a pair containing the width (%) and + the attribute for this table (either "generic" or + "borderless"). + */ +QPair<QString, QString> XmlGenerator::getTableWidthAttr(const Atom *atom) +{ + QString p0, p1; + QString attr = "generic"; + QString width; + if (atom->count() > 0) { + p0 = atom->string(0); + if (atom->count() > 1) + p1 = atom->string(1); + } + if (!p0.isEmpty()) { + if (p0 == QLatin1String("borderless")) + attr = p0; + else if (p0.contains(QLatin1Char('%'))) + width = p0; + } + if (!p1.isEmpty()) { + if (p1 == QLatin1String("borderless")) + attr = p1; + else if (p1.contains(QLatin1Char('%'))) + width = p1; + } + return QPair<QString, QString>(width, attr); +} + +/*! + Registers an anchor reference and returns a unique + and cleaned copy of the reference (the one that should be + used in the output). + To ensure unicity throughout the document, this method + uses the \a refMap cache. + */ +QString XmlGenerator::registerRef(const QString &ref) +{ + QString clean = Generator::cleanRef(ref); + + for (;;) { + QString &prevRef = refMap[clean.toLower()]; + if (prevRef.isEmpty()) { + prevRef = ref; + break; + } else if (prevRef == ref) { + break; + } + clean += QLatin1Char('x'); + } + return clean; +} + +/*! + Generates a clean and unique reference for the given \a node. + This reference may depend on the type of the node (typedef, + QML signal, etc.) + */ +QString XmlGenerator::refForNode(const Node *node) +{ + QString ref; + switch (node->nodeType()) { + case Node::Enum: + ref = node->name() + "-enum"; + break; + case Node::Typedef: { + const auto tdn = static_cast<const TypedefNode *>(node); + if (tdn->associatedEnum()) + return refForNode(tdn->associatedEnum()); + ref = node->name() + "-typedef"; + } + break; + case Node::Function: { + const auto fn = static_cast<const FunctionNode *>(node); + switch (fn->metaness()) { + case FunctionNode::JsSignal: + case FunctionNode::QmlSignal: + ref = fn->name() + "-signal"; + break; + case FunctionNode::JsSignalHandler: + case FunctionNode::QmlSignalHandler: + ref = fn->name() + "-signal-handler"; + break; + case FunctionNode::JsMethod: + case FunctionNode::QmlMethod: + ref = fn->name() + "-method"; + if (fn->overloadNumber() != 0) + ref += QLatin1Char('-') + QString::number(fn->overloadNumber()); + break; + default: + if (fn->hasOneAssociatedProperty() && fn->doc().isEmpty()) { + return refForNode(fn->firstAssociatedProperty()); + } else { + ref = fn->name(); + if (fn->overloadNumber() != 0) + ref += QLatin1Char('-') + QString::number(fn->overloadNumber()); + } + break; + } + } + break; + case Node::JsProperty: + case Node::QmlProperty: + if (node->isAttached()) + ref = node->name() + "-attached-prop"; + else + ref = node->name() + "-prop"; + break; + case Node::Property: + ref = node->name() + "-prop"; + break; + case Node::Variable: + ref = node->name() + "-var"; + break; + case Node::SharedComment: + if (node->isPropertyGroup()) + ref = node->name() + "-prop"; + break; + default: + break; + } + return registerRef(ref); +} + +/*! + Construct the link string for the \a node and return it. + The \a relative node is used to decide whether the link + we are generating is in the same file as the target. + Note the relative node can be 0, which pretty much + guarantees that the link and the target aren't in the + same file. + */ +QString XmlGenerator::linkForNode(const Node *node, const Node *relative) +{ + if (node == nullptr) + return QString(); + if (!node->url().isEmpty()) + return node->url(); + if (fileBase(node).isEmpty()) + return QString(); + if (node->isPrivate()) + return QString(); + + QString fn = fileName(node); + if (node && node->parent() + && (node->parent()->isQmlType() || node->parent()->isJsType()) + && node->parent()->isAbstract()) { + if (Generator::qmlTypeContext()) { + if (Generator::qmlTypeContext()->inherits(node->parent())) { + fn = fileName(Generator::qmlTypeContext()); + } else if (node->parent()->isInternal()) { + node->doc().location().warning( + tr("Cannot link to property in internal type '%1'").arg(node->parent()->name())); + return QString(); + } + } + } + + QString link = fn; + + if (!node->isPageNode() || node->isPropertyGroup()) { + QString ref = refForNode(node); + if (relative && fn == fileName(relative) && ref == refForNode(relative)) + return QString(); + + link += QLatin1Char('#'); + link += ref; + } + + /* + If the output is going to subdirectories, then if the + two nodes will be output to different directories, then + the link must go up to the parent directory and then + back down into the other subdirectory. + */ + if (node && relative && (node != relative)) { + if (useOutputSubdirs() && !node->isExternalPage() && + node->outputSubdirectory() != relative->outputSubdirectory()) { + if (link.startsWith(QString(node->outputSubdirectory() + QLatin1Char('/')))) { + link.prepend(QString("../")); + } else { + link.prepend(QString("../" + node->outputSubdirectory() + QLatin1Char('/'))); + } + } + } + return link; +} + +/*! + This function is called for links, i.e. for words that + are marked with the qdoc link command. For autolinks + that are not marked with the qdoc link command, the + getAutoLink() function is called + + It returns the string for a link found by using the data + in the \a atom to search the database. It also sets \a node + to point to the target node for that link. \a relative points + to the node holding the qdoc comment where the link command + was found. + */ +QString XmlGenerator::getLink(const Atom *atom, const Node *relative, const Node **node) +{ + const QString &t = atom->string(); + if (t.at(0) == QChar('h')) { + if (t.startsWith("http:") || t.startsWith("https:")) + return t; + } else if (t.at(0) == QChar('f')) { + if (t.startsWith("file:") || t.startsWith("ftp:")) + return t; + } else if (t.at(0) == QChar('m')) { + if (t.startsWith("mailto:")) + return t; + } + return getAutoLink(atom, relative, node); +} + +/*! + This function is called for autolinks, i.e. for words that + are not marked with the qdoc link command that qdoc has + reason to believe should be links. For links marked with + the qdoc link command, the getLink() function is called. + + It returns the string for a link found by using the data + in the \a atom to search the database. It also sets \a node + to point to the target node for that link. \a relative points + to the node holding the qdoc comment where the link command + was found. + */ +QString XmlGenerator::getAutoLink(const Atom *atom, const Node *relative, const Node **node) +{ + QString ref; + + *node = qdb_->findNodeForAtom(atom, relative, ref); + if (!(*node)) + return QString(); + + QString link = (*node)->url(); + if (link.isEmpty()) + link = linkForNode(*node, relative); + if (!ref.isEmpty()) { + int hashtag = link.lastIndexOf(QChar('#')); + if (hashtag != -1) + link.truncate(hashtag); + link += QLatin1Char('#') + ref; + } + return link; +} + +const QPair<QString, QString> XmlGenerator::anchorForNode(const Node *node) +{ + QPair<QString, QString> anchorPair; + + anchorPair.first = Generator::fileName(node); + if (node->isTextPageNode()) + anchorPair.second = node->title(); + + return anchorPair; +} + +/*! + Returns a string describing the \a node type. + */ +QString XmlGenerator::targetType(const Node *node) +{ + if (!node) + return QStringLiteral("external"); + + switch (node->nodeType()) { + case Node::Namespace: + return QStringLiteral("namespace"); + case Node::Class: + case Node::Struct: + case Node::Union: + return QStringLiteral("class"); + case Node::Page: + case Node::Example: + return QStringLiteral("page"); + case Node::Enum: + return QStringLiteral("enum"); + case Node::Typedef: + return QStringLiteral("typedef"); + case Node::Property: + return QStringLiteral("property"); + case Node::Function: + return QStringLiteral("function"); + case Node::Variable: + return QStringLiteral("variable"); + case Node::Module: + return QStringLiteral("module"); + default: + break; + } + return QString(); +} + +QT_END_NAMESPACE |