qdoc: Generate cross-module links-to-links pages

The cross-module link report is modified so that each module
listed in the table is a link to a subpage on which all the
links from the current module to that module are listed. To
check that these links go to the correct place, click on one
to be taken to the actual link. The actual link is marked
with red asterisks. Click on that link to check that the link
goes to the correct page. Repeat this process for all the
links in the table.

Change-Id: Ifddf7108ed7ef090c4063909fdbd10dac1f2566b
Task-number: QTBUG-41850
Reviewed-by: Topi Reiniö <topi.reinio@digia.com>
This commit is contained in:
Martin Smith 2014-10-28 14:25:44 +01:00
parent 894b579e34
commit 17472a2ba3
10 changed files with 182 additions and 66 deletions

View File

@ -62,6 +62,11 @@ a:link {
text-align: left; text-align: left;
} }
a.qa-mark:target:before {
content: "***";
color: #ff0000;
}
a:hover { a:hover {
color: #44a51c; color: #44a51c;
text-align: left; text-align: left;

View File

@ -43,6 +43,11 @@ links
----------- -----------
*/ */
a.qa-mark:target:before {
content: "***";
color: #ff0000;
}
.flags { .flags {
text-decoration: none; text-decoration: none;
text-height: 24px; text-height: 24px;

View File

@ -107,6 +107,8 @@ QT_BEGIN_NAMESPACE
\value ListItemLeft \value ListItemLeft
\value ListItemRight \value ListItemRight
\value ListRight \value ListRight
\value NavAutoLink
\value NavLink
\value Nop \value Nop
\value Note \value Note
\value ParaLeft \value ParaLeft
@ -193,6 +195,8 @@ static const struct {
{ "ListItemLeft", Atom::ListItemLeft }, { "ListItemLeft", Atom::ListItemLeft },
{ "ListItemRight", Atom::ListItemRight }, { "ListItemRight", Atom::ListItemRight },
{ "ListRight", Atom::ListRight }, { "ListRight", Atom::ListRight },
{ "NavAutoLink", Atom::NavAutoLink },
{ "NavLink", Atom::NavLink },
{ "Nop", Atom::Nop }, { "Nop", Atom::Nop },
{ "NoteLeft", Atom::NoteLeft }, { "NoteLeft", Atom::NoteLeft },
{ "NoteRight", Atom::NoteRight }, { "NoteRight", Atom::NoteRight },

View File

@ -57,7 +57,7 @@ public:
BriefRight, BriefRight,
C, C,
CaptionLeft, CaptionLeft,
CaptionRight, // 10 CaptionRight,
Code, Code,
CodeBad, CodeBad,
CodeNew, CodeNew,
@ -67,7 +67,7 @@ public:
DivLeft, DivLeft,
DivRight, DivRight,
EndQmlText, EndQmlText,
FootnoteLeft, // 20 FootnoteLeft,
FootnoteRight, FootnoteRight,
FormatElse, FormatElse,
FormatEndif, FormatEndif,
@ -77,7 +77,7 @@ public:
GeneratedList, GeneratedList,
GuidLink, GuidLink,
HR, HR,
Image, // 30 Image,
ImageText, ImageText,
ImportantLeft, ImportantLeft,
ImportantRight, ImportantRight,
@ -87,7 +87,7 @@ public:
LegaleseLeft, LegaleseLeft,
LegaleseRight, LegaleseRight,
LineBreak, LineBreak,
Link, // 40 Link,
LinkNode, LinkNode,
ListLeft, ListLeft,
ListItemNumber, ListItemNumber,
@ -96,8 +96,10 @@ public:
ListItemLeft, ListItemLeft,
ListItemRight, ListItemRight,
ListRight, ListRight,
NavAutoLink,
NavLink,
Nop, Nop,
NoteLeft, // 50 NoteLeft,
NoteRight, NoteRight,
ParaLeft, ParaLeft,
ParaRight, ParaRight,
@ -107,7 +109,7 @@ public:
QuotationRight, QuotationRight,
RawString, RawString,
SectionLeft, SectionLeft,
SectionRight, // 60 SectionRight,
SectionHeadingLeft, SectionHeadingLeft,
SectionHeadingRight, SectionHeadingRight,
SidebarLeft, SidebarLeft,
@ -117,7 +119,7 @@ public:
SnippetIdentifier, SnippetIdentifier,
SnippetLocation, SnippetLocation,
String, String,
TableLeft, // 70 TableLeft,
TableRight, TableRight,
TableHeaderLeft, TableHeaderLeft,
TableHeaderRight, TableHeaderRight,
@ -127,7 +129,7 @@ public:
TableItemRight, TableItemRight,
TableOfContents, TableOfContents,
Target, Target,
UnhandledFormat, // 80 UnhandledFormat,
UnknownCommand, UnknownCommand,
Last = UnknownCommand Last = UnknownCommand
}; };

View File

@ -1991,7 +1991,7 @@ void DocParser::append(const QString &string)
Atom::Type lastType = priv->text.lastAtom()->type(); Atom::Type lastType = priv->text.lastAtom()->type();
if ((lastType == Atom::Code) && priv->text.lastAtom()->string().endsWith(QLatin1String("\n\n"))) if ((lastType == Atom::Code) && priv->text.lastAtom()->string().endsWith(QLatin1String("\n\n")))
priv->text.lastAtom()->chopString(); priv->text.lastAtom()->chopString();
priv->text << Atom(string); priv->text << Atom(string); // The Atom type is Link.
} }
void DocParser::append(Atom::Type type, const QString& p1, const QString& p2) void DocParser::append(Atom::Type type, const QString& p1, const QString& p2)
@ -2008,7 +2008,7 @@ void DocParser::append(const QString& p1, const QString& p2)
if ((lastType == Atom::Code) && priv->text.lastAtom()->string().endsWith(QLatin1String("\n\n"))) if ((lastType == Atom::Code) && priv->text.lastAtom()->string().endsWith(QLatin1String("\n\n")))
priv->text.lastAtom()->chopString(); priv->text.lastAtom()->chopString();
if (p2.isEmpty()) if (p2.isEmpty())
priv->text << Atom(p1); priv->text << Atom(p1); // The Atom type is Link.
else else
priv->text << LinkAtom(p1, p2); priv->text << LinkAtom(p1, p2);
} }

View File

@ -327,7 +327,10 @@ void HtmlGenerator::generateQAPage()
<< "class=\"even\"><th class=\"tblConst\">Destination Module</th>" << "class=\"even\"><th class=\"tblConst\">Destination Module</th>"
<< "<th class=\"tblval\">Link Count</th></tr>\n"; << "<th class=\"tblval\">Link Count</th></tr>\n";
for (int i = 0; i< strings.size(); ++i) { for (int i = 0; i< strings.size(); ++i) {
out() << "<tr><td class=\"topAlign\"><tt>" << strings.at(i) QString fileName = generateLinksToLinksPage(strings.at(i), marker);
out() << "<tr><td class=\"topAlign\"><tt>"
<< "<a href=\"" << fileName << "\">"
<< strings.at(i) << "</a>"
<< "</tt></td><td class=\"topAlign\"><tt>" << counts.at(i) << "</tt></td><td class=\"topAlign\"><tt>" << counts.at(i)
<< "</tt></td></tr>\n"; << "</tt></td></tr>\n";
} }
@ -339,9 +342,41 @@ void HtmlGenerator::generateQAPage()
out() << "<p>" << protectEnc(t) << "</p>\n"; out() << "<p>" << protectEnc(t) << "</p>\n";
out() << "<p>" << protectEnc(depends) << "</p>\n"; out() << "<p>" << protectEnc(depends) << "</p>\n";
} }
generateFooter();
endSubPage(); endSubPage();
} }
/*!
*/
QString HtmlGenerator::generateLinksToLinksPage(const QString& module, CodeMarker* marker)
{
NamespaceNode* node = qdb_->primaryTreeRoot();
QString fileName = "aaa-links-to-" + module + ".html";
beginSubPage(node, fileName);
QString title = "Links from " + defaultModuleName() + " to " + module;
generateHeader(title, node, marker);
generateTitle(title, Text(), SmallSubTitle, node, marker);
out() << "<p>This is the complete list of links from " << defaultModuleName()
<< " to " << module << ". ";
out() << "Click on a link to go directly to the actual link in the docs. ";
out() << "Then click on that link to check whether it goes to the correct place.</p>\n";
TargetList* tlist = qdb_->getTargetList(module);
if (tlist) {
out() << "<table class=\"alignedsummary\">\n";
foreach (TargetLoc* t, *tlist) {
// e.g.: <a name="link-8421"></a><a href="layout.html">Layout Management</a>
out() << "<tr><td class=\"memItemLeft leftAlign topAlign\">";
out() << "<a href=\"" << t->fileName_ << "#" << t->target_ << "\">";
out() << t->text_ << "</a>";
out() << "</td></tr>\n";
}
out() << "</table>\n";
}
generateFooter();
endSubPage();
return fileName;
}
/*! /*!
Generate html from an instance of Atom. Generate html from an instance of Atom.
*/ */
@ -360,6 +395,7 @@ int HtmlGenerator::generateAtom(const Atom *atom, const Node *relative, CodeMark
case Atom::AbstractRight: case Atom::AbstractRight:
break; break;
case Atom::AutoLink: case Atom::AutoLink:
case Atom::NavAutoLink:
if (!inLink_ && !inContents_ && !inSectionHeading_) { if (!inLink_ && !inContents_ && !inSectionHeading_) {
const Node *node = 0; const Node *node = 0;
QString link = getAutoLink(atom, relative, &node); QString link = getAutoLink(atom, relative, &node);
@ -374,6 +410,11 @@ int HtmlGenerator::generateAtom(const Atom *atom, const Node *relative, CodeMark
if (link.isEmpty()) if (link.isEmpty())
out() << protectEnc(atom->string()); out() << protectEnc(atom->string());
else { else {
if (Generator::writeQaPages() && node && (atom->type() != Atom::NavAutoLink)) {
QString text = atom->string();
QString target = qdb_->getNewLinkTarget(node, outFileName(), text);
out() << "<a id=\"" << Doc::canonicalTitle(target) << "\" class=\"qa-mark\"></a>";
}
beginLink(link, node, relative); beginLink(link, node, relative);
generateLink(atom, marker); generateLink(atom, marker);
endLink(); endLink();
@ -856,6 +897,7 @@ int HtmlGenerator::generateAtom(const Atom *atom, const Node *relative, CodeMark
out() << "<br/>"; out() << "<br/>";
break; break;
case Atom::Link: case Atom::Link:
case Atom::NavLink:
{ {
inObsoleteLink = false; inObsoleteLink = false;
const Node *node = 0; const Node *node = 0;
@ -864,6 +906,17 @@ int HtmlGenerator::generateAtom(const Atom *atom, const Node *relative, CodeMark
relative->doc().location().warning(tr("Can't link to '%1'").arg(atom->string())); relative->doc().location().warning(tr("Can't link to '%1'").arg(atom->string()));
} }
else { else {
if (Generator::writeQaPages() && node && (atom->type() != Atom::NavLink)) {
QString text = atom->next()->next()->string();
QString target = qdb_->getNewLinkTarget(node, outFileName(), text);
out() << "<a id=\"" << Doc::canonicalTitle(target) << "\" class=\"qa-mark\"></a>";
}
/*
mws saw this on 17/10/2014.
Is this correct? Setting node to 0 means the
following test always fails. Did we decide to
no longer warn about linking to obsolete things?
*/
node = 0; node = 0;
if (node && node->status() == Node::Obsolete) { if (node && node->status() == Node::Obsolete) {
if ((relative->parent() != node) && !relative->isObsolete()) { if ((relative->parent() != node) && !relative->isObsolete()) {
@ -1698,11 +1751,11 @@ void HtmlGenerator::generateNavigationBar(const QString &title,
return; return;
if (!homepage.isEmpty()) if (!homepage.isEmpty())
navigationbar << Atom(Atom::ListItemLeft) navigationbar << Atom(Atom::ListItemLeft)
<< Atom(Atom::AutoLink, homepage) << Atom(Atom::NavAutoLink, homepage)
<< Atom(Atom::ListItemRight); << Atom(Atom::ListItemRight);
if (!landingpage.isEmpty() && landingpage != title) if (!landingpage.isEmpty() && landingpage != title)
navigationbar << Atom(Atom::ListItemLeft) navigationbar << Atom(Atom::ListItemLeft)
<< Atom(Atom::AutoLink, landingpage) << Atom(Atom::NavAutoLink, landingpage)
<< Atom(Atom::ListItemRight); << Atom(Atom::ListItemRight);
if (node->isClass()) { if (node->isClass()) {
@ -1711,7 +1764,7 @@ void HtmlGenerator::generateNavigationBar(const QString &title,
if (!cppclassespage.isEmpty()) if (!cppclassespage.isEmpty())
navigationbar << Atom(Atom::ListItemLeft) navigationbar << Atom(Atom::ListItemLeft)
<< Atom(Atom::Link, cppclassespage) << Atom(Atom::NavLink, cppclassespage)
<< Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
<< Atom(Atom::String, QLatin1String("C++ Classes")) << Atom(Atom::String, QLatin1String("C++ Classes"))
<< Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK)
@ -1725,7 +1778,7 @@ void HtmlGenerator::generateNavigationBar(const QString &title,
else if (node->isQmlType() || node->isQmlBasicType()) { else if (node->isQmlType() || node->isQmlBasicType()) {
if (!qmltypespage.isEmpty()) if (!qmltypespage.isEmpty())
navigationbar << Atom(Atom::ListItemLeft) navigationbar << Atom(Atom::ListItemLeft)
<< Atom(Atom::Link, qmltypespage) << Atom(Atom::NavLink, qmltypespage)
<< Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
<< Atom(Atom::String, QLatin1String("QML Types")) << Atom(Atom::String, QLatin1String("QML Types"))
<< Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK)
@ -1737,7 +1790,7 @@ void HtmlGenerator::generateNavigationBar(const QString &title,
else { else {
if (node->isExampleFile()) { if (node->isExampleFile()) {
navigationbar << Atom(Atom::ListItemLeft) navigationbar << Atom(Atom::ListItemLeft)
<< Atom(Atom::Link, node->parent()->name()) << Atom(Atom::NavLink, node->parent()->name())
<< Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
<< Atom(Atom::String, node->parent()->title()) << Atom(Atom::String, node->parent()->title())
<< Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK)
@ -3711,36 +3764,7 @@ QString HtmlGenerator::getLink(const Atom *atom, const Node *relative, const Nod
if (t.startsWith("mailto:")) if (t.startsWith("mailto:"))
return t; return t;
} }
return getAutoLink(atom, relative, node);
QString ref;
*node = qdb_->findNodeForAtom(atom, relative, ref);
if (!(*node))
return QString();
if (Generator::writeQaPages())
qdb_->incrementLinkCount(*node);
QString url = (*node)->url();
if (!url.isEmpty()) {
if (ref.isEmpty())
return url;
int hashtag = url.lastIndexOf(QChar('#'));
if (hashtag != -1)
url.truncate(hashtag);
return url + "#" + ref;
}
/*
Given that *node is not null, we now cconstruct a link
to the page that *node represents, and then if we found
a target on that page, we connect the target to the link
with '#'.
*/
QString link = linkForNode(*node, relative);
if (*node && (*node)->subType() == Node::Image)
link = "images/used-in-examples/" + link;
if (!ref.isEmpty())
link += QLatin1Char('#') + ref;
return link;
} }
/*! /*!
@ -3758,28 +3782,25 @@ QString HtmlGenerator::getLink(const Atom *atom, const Node *relative, const Nod
QString HtmlGenerator::getAutoLink(const Atom *atom, const Node *relative, const Node** node) QString HtmlGenerator::getAutoLink(const Atom *atom, const Node *relative, const Node** node)
{ {
QString ref; QString ref;
QString link;
*node = qdb_->findNodeForAtom(atom, relative, ref); *node = qdb_->findNodeForAtom(atom, relative, ref);
if (!(*node)) if (!(*node))
return QString(); return QString();
if (Generator::writeQaPages()) QString link = (*node)->url();
qdb_->incrementLinkCount(*node); if (link.isEmpty()) {
link = linkForNode(*node, relative);
QString url = (*node)->url(); if ((*node)->subType() == Node::Image)
if (!url.isEmpty()) { link = "images/used-in-examples/" + link;
if (ref.isEmpty()) if (!ref.isEmpty())
return url; link += QLatin1Char('#') + ref;
int hashtag = url.lastIndexOf(QChar('#')); }
if (hashtag != -1) else if (!ref.isEmpty()) {
url.truncate(hashtag); int hashtag = link.lastIndexOf(QChar('#'));
return url + "#" + ref; if (hashtag != -1)
link.truncate(hashtag);
link += "#" + ref;
} }
link = linkForNode(*node, relative);
if (!ref.isEmpty())
link += QLatin1Char('#') + ref;
return link; return link;
} }

View File

@ -90,6 +90,7 @@ public:
protected: protected:
virtual void generateQAPage(); virtual void generateQAPage();
QString generateLinksToLinksPage(const QString& module, CodeMarker* marker);
virtual int generateAtom(const Atom *atom, virtual int generateAtom(const Atom *atom,
const Node *relative, const Node *relative,
CodeMarker *marker); CodeMarker *marker);

View File

@ -392,6 +392,11 @@ class QDocDatabase
QString getLinkCounts(QStringList& strings, QVector<int>& counts) { QString getLinkCounts(QStringList& strings, QVector<int>& counts) {
return forest_.getLinkCounts(strings, counts); return forest_.getLinkCounts(strings, counts);
} }
QString getNewLinkTarget(const Node* t, const QString& fileName, QString& text) {
return primaryTree()->getNewLinkTarget(t, fileName, text);
}
TargetList* getTargetList(const QString& t) { return primaryTree()->getTargetList(t); }
QStringList getTargetListKeys() { return primaryTree()->getTargetListKeys(); }
private: private:
friend class QDocIndexFiles; friend class QDocIndexFiles;

View File

@ -71,10 +71,14 @@ Tree::Tree(const QString& module, QDocDatabase* qdb)
linkCount_(0), linkCount_(0),
module_(module), module_(module),
qdb_(qdb), qdb_(qdb),
root_(0, QString()) root_(0, QString()),
targetListMap_(0)
{ {
root_.setModuleName(module_); root_.setModuleName(module_);
root_.setTree(this); root_.setTree(this);
if (Generator::writeQaPages()) {
targetListMap_ = new TargetListMap;
}
} }
/*! /*!
@ -100,6 +104,18 @@ Tree::~Tree()
} }
nodesByTargetRef_.clear(); nodesByTargetRef_.clear();
nodesByTargetTitle_.clear(); nodesByTargetTitle_.clear();
if (Generator::writeQaPages() && targetListMap_) {
TargetListMap::iterator i = targetListMap_->begin();
while (i != targetListMap_->end()) {
TargetList* tlist = i.value();
if (tlist) {
foreach (TargetLoc* tloc, *tlist)
delete tloc;
}
delete tlist;
++i;
}
}
} }
/* API members */ /* API members */
@ -1399,4 +1415,45 @@ const Node* Tree::checkForCollision(const QString& name)
return findNode(QStringList(name), 0, 0, Node::DontCare); return findNode(QStringList(name), 0, 0, Node::DontCare);
} }
/*!
Generate a target of the form link-nnn, where the nnn is
the current link count for this tree. This target string
is returned. It will be output as an HTML anchor just before
an HTML link to the node \a t.
The node \a t
*/
QString Tree::getNewLinkTarget(const Node* t, const QString& fileName, QString& text)
{
QString target;
if (t) {
Tree* tree = t->tree();
incrementLinkCount();
if (tree != this)
tree->incrementLinkCount();
target = QString("qa-target-%1").arg(-(linkCount()));
QString moduleName = tree->moduleName();
TargetLoc* tloc = new TargetLoc(target, fileName, text);
TargetList* tList = 0;
TargetListMap::iterator i = targetListMap_->find(moduleName);
if (i == targetListMap_->end()) {
tList = new TargetList;
i = targetListMap_->insert(moduleName, tList);
}
else
tList = i.value();
tList->append(tloc);
}
return target;
}
/*!
Look up the target list for the specified \a module
and return a pointer to it.
*/
TargetList* Tree::getTargetList(const QString& module)
{
return targetListMap_->value(module);
}
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -67,10 +67,22 @@ struct TargetRec
Type type_; Type type_;
}; };
struct TargetLoc
{
public:
TargetLoc(const QString& t, const QString& fileName, const QString& text)
: target_(t), fileName_(fileName), text_(text) { }
QString target_;
QString fileName_;
QString text_;
};
typedef QMultiMap<QString, TargetRec*> TargetMap; typedef QMultiMap<QString, TargetRec*> TargetMap;
typedef QMultiMap<QString, DocNode*> DocNodeMultiMap; typedef QMultiMap<QString, DocNode*> DocNodeMultiMap;
typedef QMap<QString, QmlClassNode*> QmlTypeMap; typedef QMap<QString, QmlClassNode*> QmlTypeMap;
typedef QMultiMap<QString, const ExampleNode*> ExampleNodeMap; typedef QMultiMap<QString, const ExampleNode*> ExampleNodeMap;
typedef QVector<TargetLoc*> TargetList;
typedef QMap<QString, TargetList*> TargetListMap;
class Tree class Tree
{ {
@ -193,18 +205,21 @@ class Tree
bool docsHaveBeenGenerated() const { return docsHaveBeenGenerated_; } bool docsHaveBeenGenerated() const { return docsHaveBeenGenerated_; }
void setTreeHasBeenAnalyzed() { treeHasBeenAnalyzed_ = true; } void setTreeHasBeenAnalyzed() { treeHasBeenAnalyzed_ = true; }
void setdocsHaveBeenGenerated() { docsHaveBeenGenerated_ = true; } void setdocsHaveBeenGenerated() { docsHaveBeenGenerated_ = true; }
QString getNewLinkTarget(const Node* t, const QString& fileName, QString& text);
TargetList* getTargetList(const QString& module);
QStringList getTargetListKeys() { return targetListMap_->keys(); }
public: public:
const QString& moduleName() const { return module_; } const QString& moduleName() const { return module_; }
const QString& indexFileName() const { return indexFileName_; } const QString& indexFileName() const { return indexFileName_; }
void incrementLinkCount() { --linkCount_; } long incrementLinkCount() { return --linkCount_; }
void clearLinkCount() { linkCount_ = 0; } void clearLinkCount() { linkCount_ = 0; }
int linkCount() const { return linkCount_; } long linkCount() const { return linkCount_; }
private: private:
bool treeHasBeenAnalyzed_; bool treeHasBeenAnalyzed_;
bool docsHaveBeenGenerated_; bool docsHaveBeenGenerated_;
int linkCount_; long linkCount_;
QString module_; QString module_;
QString indexFileName_; QString indexFileName_;
QDocDatabase* qdb_; QDocDatabase* qdb_;
@ -218,6 +233,7 @@ private:
CNMap qmlModules_; CNMap qmlModules_;
QmlTypeMap qmlTypeMap_; QmlTypeMap qmlTypeMap_;
ExampleNodeMap exampleNodeMap_; ExampleNodeMap exampleNodeMap_;
TargetListMap* targetListMap_;
}; };
QT_END_NAMESPACE QT_END_NAMESPACE