From 286927b27c2df066a590bd8f442ad0439fd9133f Mon Sep 17 00:00:00 2001 From: Thibaut Cuvelier Date: Fri, 15 Nov 2019 02:41:00 +0100 Subject: Introduce XmlGenerator to merge common functions for outputs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HTML, WebXML, and the future DocBook output Fixes: QTBUG-80100 Change-Id: Idf373e3b1d76ab808597dcabd8649ecb5e2df0f7 Reviewed-by: Topi Reiniƶ --- src/qdoc/generator.cpp | 128 ++++++++---- src/qdoc/generator.h | 9 + src/qdoc/htmlgenerator.cpp | 339 +----------------------------- src/qdoc/htmlgenerator.h | 24 +-- src/qdoc/qdoc.pro | 2 + src/qdoc/webxmlgenerator.cpp | 44 ---- src/qdoc/webxmlgenerator.h | 2 - src/qdoc/xmlgenerator.cpp | 478 +++++++++++++++++++++++++++++++++++++++++++ src/qdoc/xmlgenerator.h | 71 +++++++ 9 files changed, 656 insertions(+), 441 deletions(-) create mode 100644 src/qdoc/xmlgenerator.cpp create mode 100644 src/qdoc/xmlgenerator.h diff --git a/src/qdoc/generator.cpp b/src/qdoc/generator.cpp index 19af09209..dde1de496 100644 --- a/src/qdoc/generator.cpp +++ b/src/qdoc/generator.cpp @@ -251,30 +251,49 @@ void Generator::writeOutFileNames() } /*! - 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(). This function does not - store the \a fileName in the \a node as the output file name. + Creates the file named \a fileName in the output directory + and returns a QFile pointing to this file. In particular, + this method deals with errors when opening the file: + the returned QFile is always valid and can be written to. - \sa beginSubPage() + \sa beginFilePage() */ -void Generator::beginFilePage(const Node *node, const QString &fileName) +QFile *Generator::openSubPageFile(const Node *node, const QString &fileName) { QString path = outputDir() + QLatin1Char('/'); if (Generator::useOutputSubdirs() && !node->outputSubdirectory().isEmpty() && - !outputDir().endsWith(node->outputSubdirectory())) + !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("Output 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())); + auto outPath = redirectDocumentationToDevNull_ ? QStringLiteral("/dev/null") : path; + auto outFile = new QFile(outPath); + if (!redirectDocumentationToDevNull_ && outFile->exists()) { + node->location().error( + tr("Output 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())); + } qCDebug(lcQdoc, "Writing: %s", qPrintable(path)); outFileNames_ << fileName; - QTextStream* out = new QTextStream(outFile); + return outFile; +} +/*! + 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(). This function does not + store the \a fileName in the \a node as the output file name. + + \sa beginSubPage() + */ +void Generator::beginFilePage(const Node *node, const QString &fileName) +{ + QFile *outFile = openSubPageFile(node, fileName); + QTextStream* out = new QTextStream(outFile); #ifndef QT_NO_TEXTCODEC if (outputCodec) out->setCodec(outputCodec); @@ -1014,6 +1033,25 @@ void Generator::generateLinkToExample(const ExampleNode *en, generateText(text, nullptr, marker); } +void Generator::addImageToCopy(const ExampleNode *en, const QString &file) +{ + QDir dirInfo; + QString userFriendlyFilePath; + const QString prefix("/images/used-in-examples/"); + QString srcPath = Config::findFile(en->location(), + QStringList(), + exampleDirs, + file, + exampleImgExts, + &userFriendlyFilePath); + outFileNames_ << prefix.mid(1) + userFriendlyFilePath; + userFriendlyFilePath.truncate(userFriendlyFilePath.lastIndexOf('/')); + QString imgOutDir = outDir_ + prefix + userFriendlyFilePath; + if (!dirInfo.mkpath(imgOutDir)) + en->location().fatal(tr("Cannot create output directory '%1'").arg(imgOutDir)); + Config::copyFile(en->location(), srcPath, file, imgOutDir); +} + /*! This function is called when the documentation for an example is being formatted. It outputs a list of files for the example, which @@ -1045,26 +1083,9 @@ void Generator::generateFileList(const ExampleNode *en, CodeMarker *marker, bool QString path; for (const auto &file : qAsConst(paths)) { if (images) { - if (!file.isEmpty()) { - QDir dirInfo; - QString userFriendlyFilePath; - const QString prefix("/images/used-in-examples/"); - QString srcPath = Config::findFile(en->location(), - QStringList(), - exampleDirs, - file, - exampleImgExts, - &userFriendlyFilePath); - outFileNames_ << prefix.mid(1) + userFriendlyFilePath; - userFriendlyFilePath.truncate(userFriendlyFilePath.lastIndexOf('/')); - QString imgOutDir = outDir_ + prefix + userFriendlyFilePath; - if (!dirInfo.mkpath(imgOutDir)) - en->location().fatal(tr("Cannot create output directory '%1'").arg(imgOutDir)); - Config::copyFile(en->location(), srcPath, file, imgOutDir); - } - - } - else { + if (!file.isEmpty()) + addImageToCopy(en, file); + } else { generateExampleFilePage(en, file, marker); } @@ -1476,15 +1497,15 @@ bool Generator::generateText(const Text &text, nonreentrant, and true is returned. If there are no exceptions, the three node lists remain empty and false is returned. */ -static bool hasExceptions(const Node *node, - NodeList &reentrant, - NodeList &threadsafe, - NodeList &nonreentrant) +bool Generator::hasExceptions(const Node *node, + NodeList &reentrant, + NodeList &threadsafe, + NodeList &nonreentrant) { bool result = false; Node::ThreadSafeness ts = node->threadSafeness(); const NodeList &children = static_cast(node)->childNodes(); - for (auto *child : children) { + for (auto child : children) { if (!child->isObsolete()){ switch (child->threadSafeness()) { case Node::Reentrant: @@ -1632,15 +1653,16 @@ void Generator::generateThreadSafeness(const Node *node, CodeMarker *marker) } /*! - If the node is an overloaded signal, add a node with an example on how to connect to it + Returns the string containing an example code of the input node, + if it is an overloaded signal. Otherwise, returns an empty string. */ -void Generator::generateOverloadedSignal(const Node *node, CodeMarker *marker) +QString Generator::getOverloadedSignalCode(const Node *node) { if (!node->isFunction()) - return; - const FunctionNode *func = static_cast(node); + return QString(); + const auto func = static_cast(node); if (!func->isSignal() || !func->hasOverloads()) - return; + return QString(); // Compute a friendly name for the object of that instance. // e.g: "QAbstractSocket" -> "abstractSocket" @@ -1661,6 +1683,22 @@ void Generator::generateOverloadedSignal(const Node *node, CodeMarker *marker) code += func->parameters().generateTypeAndNameList(); code += "){ /* ... */ });"; + return code; +} + +/*! + If the node is an overloaded signal, and a node with an example on how to connect to it + + Someone didn't finish writing this comment, and I don't know what this + function is supposed to do, so I have not tried to complete the comment + yet. + */ +void Generator::generateOverloadedSignal(const Node *node, CodeMarker *marker) +{ + QString code = getOverloadedSignalCode(node); + if (code.isEmpty()) + return; + Text text; text << Atom::ParaLeft << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD) @@ -1674,7 +1712,7 @@ void Generator::generateOverloadedSignal(const Node *node, CodeMarker *marker) "To connect to this signal by using the function pointer syntax, Qt " "provides a convenient helper for obtaining the function pointer as " "shown in this example:" - << Atom(Atom::Code, marker->markedUpCode(code, node, func->location())); + << Atom(Atom::Code, marker->markedUpCode(code, node, node->location())); generateText(text, node, marker); } @@ -2261,7 +2299,7 @@ QString Generator::typeString(const Node *node) case Node::Typedef: return "typedef"; case Node::Function: { - const FunctionNode *fn = static_cast(node); + const auto fn = static_cast(node); switch (fn->metaness()) { case FunctionNode::JsSignal: case FunctionNode::QmlSignal: diff --git a/src/qdoc/generator.h b/src/qdoc/generator.h index f90c82315..fe79fd77e 100644 --- a/src/qdoc/generator.h +++ b/src/qdoc/generator.h @@ -103,6 +103,7 @@ public: static bool useTimestamps() { return useTimestamps_; } protected: + static QFile *openSubPageFile(const Node *node, const QString &fileName); void beginFilePage(const Node *node, const QString &fileName); void endFilePage() { endSubPage(); } // for symmetry void beginSubPage(const Node *node, const QString &fileName); @@ -164,6 +165,7 @@ protected: QString getMetadataElement(const Aggregate *inner, const QString &t); QStringList getMetadataElements(const Aggregate *inner, const QString &t); void generateOverloadedSignal(const Node *node, CodeMarker *marker); + static QString getOverloadedSignalCode(const Node *node); QString indent(int level, const QString &markedCode); QTextStream& out(); QString outFileName(); @@ -178,6 +180,11 @@ protected: void unknownAtom(const Atom *atom); int appendSortedQmlNames(Text &text, const Node *base, const NodeList &subs); + static bool hasExceptions(const Node *node, + NodeList &reentrant, + NodeList &threadsafe, + NodeList &nonreentrant); + QMap editionGroupMap; QMap editionModuleMap; QString naturalLanguage; @@ -201,6 +208,8 @@ protected: void appendSignature(Text &text, const Node *node); void signatureList(const NodeList &nodes, const Node *relative, CodeMarker *marker); + void addImageToCopy(const ExampleNode *en, const QString &file); + private: static Generator *currentGenerator_; static QStringList exampleDirs; diff --git a/src/qdoc/htmlgenerator.cpp b/src/qdoc/htmlgenerator.cpp index 97222ef13..2114d6a8c 100644 --- a/src/qdoc/htmlgenerator.cpp +++ b/src/qdoc/htmlgenerator.cpp @@ -569,25 +569,7 @@ int HtmlGenerator::generateAtom(const Atom *atom, const Node *relative, CodeMark break; } out() << "

"; - if (relative->isProperty() || relative->isVariable()) { - atom = atom->next(); - if (atom != nullptr && 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 = "This "; - if (relative->isProperty()) - str += "property holds "; - else - str += "variable holds "; - str += atom->string().left(1).toLower() + atom->string().mid(1); - const_cast(atom)->setString(str); - } - } - } + rewritePropertyBrief(atom, relative); break; case Atom::BriefRight: if (hasBrief(relative)) @@ -728,13 +710,7 @@ int HtmlGenerator::generateAtom(const Atom *atom, const Node *relative, CodeMark } else if ((idx = atom->string().indexOf(QStringLiteral("bymodule"))) != -1) { QString moduleName = atom->string().mid(idx + 8).trimmed(); - Node::NodeType type = Node::Module; - if (atom->string().startsWith(QLatin1String("qml"))) - type = Node::QmlModule; - else if (atom->string().startsWith(QLatin1String("js"))) - type = Node::JsModule; - else if (atom->string().startsWith(QLatin1String("groups"))) - type = Node::Group; + Node::NodeType type = typeFromString(atom); QDocDatabase *qdb = QDocDatabase::qdocDB(); const CollectionNode *cn = qdb->getCollectionNode(moduleName, type); if (cn) { @@ -908,13 +884,7 @@ int HtmlGenerator::generateAtom(const Atom *atom, const Node *relative, CodeMark out() << " alt=\"\""; out() << " />"; helpProjectWriter->addExtraFile(fileName); - if (relative->isExample()) { - const ExampleNode *cen = static_cast(relative); - if (cen->imageFileName().isEmpty()) { - ExampleNode *en = const_cast(cen); - en->setImageFileName(fileName); - } - } + setImageFileName(relative, fileName); } if (atom->type() == Atom::Image) out() << "

"; @@ -1082,24 +1052,9 @@ int HtmlGenerator::generateAtom(const Atom *atom, const Node *relative, CodeMark out() << "
"; } else { // (atom->string() == ATOM_LIST_VALUE) - const Atom *lookAhead = atom->next(); - QString t = lookAhead->string(); - lookAhead = lookAhead->next(); - Q_ASSERT(lookAhead->type() == Atom::ListTagRight); - lookAhead = lookAhead->next(); - if (lookAhead && lookAhead->type() == Atom::SinceTagLeft) { - lookAhead = lookAhead->next(); - Q_ASSERT(lookAhead && lookAhead->type() == Atom::String); - t = t + QLatin1String(" (since "); - if (lookAhead->string().at(0).isDigit()) - t = t + QLatin1String("Qt "); - t = t + lookAhead->string() + QLatin1String(")"); - skipAhead = 4; - } - else { - skipAhead = 1; - } - t = protectEnc(plainCode(marker->markedUpEnumValue(t, relative))); + QPair pair = getAtomListValue(atom); + skipAhead = pair.second; + QString t = protectEnc(plainCode(marker->markedUpEnumValue(pair.first, relative))); out() << "" << t << ""; if (relative->isEnumType()) { @@ -1215,30 +1170,15 @@ int HtmlGenerator::generateAtom(const Atom *atom, const Node *relative, CodeMark break; case Atom::TableLeft: { - QString p1, p2; - QString attr = "generic"; - QString width; + QPair pair = getTableWidthAttr(atom); + QString attr = pair.second; + QString width = pair.first; + if (in_para) { out() << "

\n"; in_para = false; } - if (atom->count() > 0) { - p1 = atom->string(0); - if (atom->count() > 1) - p2 = atom->string(1); - } - if (!p1.isEmpty()) { - if (p1 == QLatin1String("borderless")) - attr = p1; - else if (p1.contains(QLatin1Char('%'))) - width = p1; - } - if (!p2.isEmpty()) { - if (p2 == QLatin1String("borderless")) - attr = p2; - else if (p2.contains(QLatin1Char('%'))) - width = p2; - } + out() << "
nodeType()) { - case Node::Enum: - ref = node->name() + "-enum"; - break; - case Node::Typedef: - { - const TypedefNode *tdn = static_cast(node); - if (tdn->associatedEnum()) - return refForNode(tdn->associatedEnum()); - else - ref = node->name() + "-typedef"; - } - break; - case Node::Function: - { - const FunctionNode *fn = static_cast(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); -} - -/*! - 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 HtmlGenerator::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 HtmlGenerator::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; -} - -/*! - Construct the link string for the \a node and return it. - The \a relative node is use to decide 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 HtmlGenerator::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; -} - void HtmlGenerator::generateFullName(const Node *apparentNode, const Node *relative, const Node *actualNode) { if (actualNode == nullptr) @@ -4134,53 +3864,6 @@ void HtmlGenerator::generateDetailedMember(const Node *node, generateExtractionMark(node, EndMark); } -int HtmlGenerator::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; - } -} - -bool HtmlGenerator::isThreeColumnEnumValueTable(const Atom *atom) -{ - while (atom != nullptr && !(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; -} - - -const QPair HtmlGenerator::anchorForNode(const Node *node) -{ - QPair anchorPair; - - anchorPair.first = Generator::fileName(node); - if (node->isPageNode()) { - const PageNode *pn = static_cast(node); - anchorPair.second = pn->title(); - } - - return anchorPair; -} - #ifdef GENERATE_MAC_REFS /* No longer valid. diff --git a/src/qdoc/htmlgenerator.h b/src/qdoc/htmlgenerator.h index c1564c5b0..36385e8f0 100644 --- a/src/qdoc/htmlgenerator.h +++ b/src/qdoc/htmlgenerator.h @@ -35,7 +35,7 @@ #include "codemarker.h" #include "config.h" -#include "generator.h" +#include "xmlgenerator.h" #include #include @@ -45,7 +45,7 @@ QT_BEGIN_NAMESPACE class HelpProjectWriter; -class HtmlGenerator : public Generator +class HtmlGenerator : public XmlGenerator { Q_DECLARE_TR_FUNCTIONS(QDoc::HtmlGenerator) @@ -80,17 +80,12 @@ protected: void generateCollectionNode(CollectionNode *cn, CodeMarker *marker) override; void generateGenericCollectionPage(CollectionNode *cn, CodeMarker *marker) override; QString fileExtension() const override; - virtual QString refForNode(const Node *node); - virtual QString linkForNode(const Node *node, const Node *relative); void generateManifestFile(const QString &manifest, const QString &element); void readManifestMetaContent(const Config &config); void generateKeywordAnchors(const Node *node); void generateAssociatedPropertyNotes(FunctionNode *fn); - QString getLink(const Atom *atom, const Node *relative, const Node **node); - QString getAutoLink(const Atom *atom, const Node *relative, const Node **node); - private: enum SubTitleSize { SmallSubTitle, LargeSubTitle }; enum ExtractionMarkType { @@ -107,7 +102,6 @@ private: QSet tags; }; - const QPair anchorForNode(const Node *node); void generateNavigationBar(const QString &title, const Node *node, CodeMarker *marker, @@ -185,12 +179,8 @@ private: void generateDetailedMember(const Node *node, const PageNode *relative, CodeMarker *marker); void generateLink(const Atom *atom, CodeMarker *marker); - inline bool hasBrief(const Node *node); - QString registerRef(const QString &ref); QString fileBase(const Node *node) const override; QString fileName(const Node *node); - static int hOffset(const Node *node); - static bool isThreeColumnEnumValueTable(const Atom *atom); #ifdef GENERATE_MAC_REFS void generateMacRef(const Node *node, CodeMarker *marker); #endif @@ -202,7 +192,6 @@ private: QXmlStreamWriter &xmlWriter(); - QHash refMap; int codeIndent; QString codePrefix; QString codeSuffix; @@ -250,15 +239,6 @@ public: static QString divNavTop; }; -// Do not display \brief for QML/JS types, document and collection nodes -inline bool HtmlGenerator::hasBrief(const Node *node) -{ - return !(node->isQmlType() - || node->isPageNode() - || node->isCollectionNode() - || node->isJsType()); -} - #define HTMLGENERATOR_ADDRESS "address" #define HTMLGENERATOR_FOOTER "footer" #define HTMLGENERATOR_GENERATEMACREFS "generatemacrefs" // ### document me diff --git a/src/qdoc/qdoc.pro b/src/qdoc/qdoc.pro index df9123b09..c38c50b16 100644 --- a/src/qdoc/qdoc.pro +++ b/src/qdoc/qdoc.pro @@ -58,6 +58,7 @@ HEADERS += atom.h \ text.h \ tokenizer.h \ tree.h \ + xmlgenerator.h \ webxmlgenerator.h \ qdoccommandlineparser.h \ utilities.h @@ -90,6 +91,7 @@ SOURCES += atom.cpp \ text.cpp \ tokenizer.cpp \ tree.cpp \ + xmlgenerator.cpp \ yyindent.cpp \ webxmlgenerator.cpp \ qdoccommandlineparser.cpp \ diff --git a/src/qdoc/webxmlgenerator.cpp b/src/qdoc/webxmlgenerator.cpp index be19eb435..73f6f6268 100644 --- a/src/qdoc/webxmlgenerator.cpp +++ b/src/qdoc/webxmlgenerator.cpp @@ -811,39 +811,6 @@ void WebXMLGenerator::endLink(QXmlStreamWriter &writer) { } } -QString WebXMLGenerator::targetType(const Node *node) -{ - if (!node) - return "external"; - - switch (node->nodeType()) { - case Node::Namespace: - return "namespace"; - case Node::Class: - case Node::Struct: - case Node::Union: - return "class"; - case Node::Page: - case Node::Example: - return "page"; - case Node::Enum: - return "enum"; - case Node::Typedef: - return "typedef"; - case Node::Property: - return "property"; - case Node::Function: - return "function"; - case Node::Variable: - return "variable"; - case Node::Module: - return "module"; - default: - break; - } - return QString(); -} - void WebXMLGenerator::generateRelations(QXmlStreamWriter &writer, const Node *node) { if (node && !node->links().empty()) { @@ -921,15 +888,4 @@ void WebXMLGenerator::generateAnnotatedList(QXmlStreamWriter &writer, writer.writeEndElement(); // table } -const QPair WebXMLGenerator::anchorForNode(const Node *node) -{ - QPair anchorPair; - - anchorPair.first = fullDocumentLocation(node); - if (node->isTextPageNode()) - anchorPair.second = node->title(); - - return anchorPair; -} - QT_END_NAMESPACE diff --git a/src/qdoc/webxmlgenerator.h b/src/qdoc/webxmlgenerator.h index 320d8169e..565be82c9 100644 --- a/src/qdoc/webxmlgenerator.h +++ b/src/qdoc/webxmlgenerator.h @@ -64,14 +64,12 @@ protected: private: - const QPair anchorForNode(const Node *node); void generateAnnotatedList(QXmlStreamWriter &writer, const Node *relative, const NodeMap &nodeMap); void generateAnnotatedList(QXmlStreamWriter &writer, const Node *relative, const NodeList &nodeList); void generateRelations(QXmlStreamWriter &writer, const Node *node); void startLink(QXmlStreamWriter &writer, const Atom *atom, const Node *node, const QString &link); void endLink(QXmlStreamWriter &writer); - QString targetType(const Node *node); bool inLink; bool inContents; 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)->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(relative); + if (cen->imageFileName().isEmpty()) { + auto *en = const_cast(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 XmlGenerator::getAtomListValue(const Atom *atom) +{ + const Atom *lookAhead = atom->next(); + if (!lookAhead) + return QPair(QString(), 1); + + QString t = lookAhead->string(); + lookAhead = lookAhead->next(); + if (!lookAhead || lookAhead->type() != Atom::ListTagRight) + return QPair(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(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 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(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(node); + if (tdn->associatedEnum()) + return refForNode(tdn->associatedEnum()); + ref = node->name() + "-typedef"; + } + break; + case Node::Function: { + const auto fn = static_cast(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 XmlGenerator::anchorForNode(const Node *node) +{ + QPair 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 diff --git a/src/qdoc/xmlgenerator.h b/src/qdoc/xmlgenerator.h new file mode 100644 index 000000000..4bf1ca938 --- /dev/null +++ b/src/qdoc/xmlgenerator.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef XMLGENERATOR_H +#define XMLGENERATOR_H + +#include "node.h" +#include "generator.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class XmlGenerator : public Generator +{ +public: + explicit XmlGenerator() = default; + +protected: + QHash refMap; + + static bool hasBrief(const Node *node); + static bool isThreeColumnEnumValueTable(const Atom *atom); + static int hOffset(const Node *node); + + static void rewritePropertyBrief(const Atom *atom, const Node *relative); + static Node::NodeType typeFromString(const Atom *atom); + static void setImageFileName(const Node *relative, const QString &fileName); + static QPair getAtomListValue(const Atom *atom); + static QPair getTableWidthAttr(const Atom *atom); + + QString registerRef(const QString &ref); + QString refForNode(const Node *node); + QString linkForNode(const Node *node, const Node *relative); + QString getLink(const Atom *atom, const Node *relative, const Node **node); + QString getAutoLink(const Atom *atom, const Node *relative, const Node** node); + + const QPair anchorForNode(const Node *node); + + static QString targetType(const Node *node); +}; + +QT_END_NAMESPACE + +#endif //XMLGENERATOR_H -- cgit v1.2.3