summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThibaut Cuvelier <cuvelier.thibaut@gmail.com>2019-11-15 02:41:00 +0100
committerTopi Reinio <topi.reinio@qt.io>2019-12-02 12:33:15 +0100
commit286927b27c2df066a590bd8f442ad0439fd9133f (patch)
tree393227400c457ed5d2f899febe364ac25222e61d
parent1dfff8b60398bf2643d2b8f2f369cbf9659ae414 (diff)
Introduce XmlGenerator to merge common functions for outputs
HTML, WebXML, and the future DocBook output Fixes: QTBUG-80100 Change-Id: Idf373e3b1d76ab808597dcabd8649ecb5e2df0f7 Reviewed-by: Topi Reiniƶ <topi.reinio@qt.io>
-rw-r--r--src/qdoc/generator.cpp128
-rw-r--r--src/qdoc/generator.h9
-rw-r--r--src/qdoc/htmlgenerator.cpp339
-rw-r--r--src/qdoc/htmlgenerator.h24
-rw-r--r--src/qdoc/qdoc.pro2
-rw-r--r--src/qdoc/webxmlgenerator.cpp44
-rw-r--r--src/qdoc/webxmlgenerator.h2
-rw-r--r--src/qdoc/xmlgenerator.cpp478
-rw-r--r--src/qdoc/xmlgenerator.h71
9 files changed, 656 insertions, 441 deletions
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<const Aggregate *>(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<const FunctionNode *>(node);
+ return QString();
+ const auto func = static_cast<const FunctionNode *>(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<const FunctionNode *>(node);
+ const auto fn = static_cast<const FunctionNode *>(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<QString, QStringList> editionGroupMap;
QMap<QString, QStringList> 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() << "<p>";
- 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 *>(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<const ExampleNode *>(relative);
- if (cen->imageFileName().isEmpty()) {
- ExampleNode *en = const_cast<ExampleNode *>(cen);
- en->setImageFileName(fileName);
- }
- }
+ setImageFileName(relative, fileName);
}
if (atom->type() == Atom::Image)
out() << "</p>";
@@ -1082,24 +1052,9 @@ int HtmlGenerator::generateAtom(const Atom *atom, const Node *relative, CodeMark
out() << "<dt>";
}
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<QString, int> pair = getAtomListValue(atom);
+ skipAhead = pair.second;
+ QString t = protectEnc(plainCode(marker->markedUpEnumValue(pair.first, relative)));
out() << "<tr><td class=\"topAlign\"><code>" << t << "</code>";
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<QString, QString> pair = getTableWidthAttr(atom);
+ QString attr = pair.second;
+ QString width = pair.first;
+
if (in_para) {
out() << "</p>\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() << "<div class=\"table\"><table class=\"" << attr << '"';
if (!width.isEmpty())
out() << " width=\"" << width << '"';
@@ -3745,23 +3685,6 @@ void HtmlGenerator::generateLink(const Atom *atom, CodeMarker *marker)
}
}
-QString HtmlGenerator::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;
-}
-
QString HtmlGenerator::protectEnc(const QString &string)
{
#ifndef QT_NO_TEXTCODEC
@@ -3829,199 +3752,6 @@ QString HtmlGenerator::fileName(const Node *node)
return Generator::fileName(node);
}
-QString HtmlGenerator::refForNode(const Node *node)
-{
- QString ref;
- switch (node->nodeType()) {
- case Node::Enum:
- ref = node->name() + "-enum";
- break;
- case Node::Typedef:
- {
- const TypedefNode *tdn = static_cast<const TypedefNode *>(node);
- if (tdn->associatedEnum())
- return refForNode(tdn->associatedEnum());
- else
- ref = node->name() + "-typedef";
- }
- break;
- case Node::Function:
- {
- const FunctionNode *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);
-}
-
-/*!
- 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<QString,QString> HtmlGenerator::anchorForNode(const Node *node)
-{
- QPair<QString,QString> anchorPair;
-
- anchorPair.first = Generator::fileName(node);
- if (node->isPageNode()) {
- const PageNode *pn = static_cast<const PageNode *>(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 <QtCore/qhash.h>
#include <QtCore/qregexp.h>
@@ -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<QString> tags;
};
- const QPair<QString,QString> 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<QString, QString> 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<QString,QString> WebXMLGenerator::anchorForNode(const Node *node)
-{
- QPair<QString,QString> 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<QString,QString> 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 *>(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
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 <QtCore/qmap.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class XmlGenerator : public Generator
+{
+public:
+ explicit XmlGenerator() = default;
+
+protected:
+ QHash<QString, QString> 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<QString, int> getAtomListValue(const Atom *atom);
+ static QPair<QString, QString> 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<QString, QString> anchorForNode(const Node *node);
+
+ static QString targetType(const Node *node);
+};
+
+QT_END_NAMESPACE
+
+#endif //XMLGENERATOR_H