qdoc: Give documenter more control of linking

This update enables using the module name as the parameter
in square brackets for the \l command. You will use this
when your link goes to the wrong page. e.g. Suppose this
link command went to a page in QtGui instead of the page
where it is meant to go in QtQuick:

\l { mytarget } { the text for my link }

When a link goes to a page in the wrong module, it means
the target exists in more than one module and because qdoc
searches the modules in sequence and stops when it finds a
match, it might match the wrong target. This would be a
collision in the single tree version of qdoc, but now qdoc
builds a separate tree for each module. Since you know
which module you want your link to go to, put the module
name in square brackets as the first parameter, like this:

\l [QtQuick] { mytarget } { the text for my link }

Now qdoc will only search for mytarget in the tree for
the QtQuick module.

The \target command can now be used anywhere. It has not
been tested in all possible locations, but it works in
the places where people have asked why it doesn't work there.

There will be a further update to complete this task for
implementing the other types of parameters that can be in
the square brackets.

Task-number: QTBUG-39221
Change-Id: I2db4fdd0319ff272ec1d2fa9dc396f14599d80f9
Reviewed-by: Martin Smith <martin.smith@digia.com>
This commit is contained in:
Martin Smith 2014-06-10 12:08:37 +02:00
parent b6ba4ac00d
commit d8062f117b
18 changed files with 565 additions and 445 deletions

View File

@ -374,22 +374,51 @@ void Atom::dump() const
contains some search parameters.
*/
LinkAtom::LinkAtom(const QString& p1, const QString& p2)
: Atom(Link, p1), qml_(false), goal_(Node::NoType), domain_(0)
: Atom(p1), genus_(DontCare), goal_(Node::NoType), domain_(0)
{
QStringList params = p2.toLower().split(QLatin1Char(' '));
foreach (const QString& p, params) {
if (p == "qml")
qml_ = true;
else {
if (!domain_) {
domain_ = QDocDatabase::qdocDB()->findTree(p);
if (domain_)
continue;
}
if (goal_ == Node::NoType)
goal_ = Node::goal(p);
if (!domain_) {
domain_ = QDocDatabase::qdocDB()->findTree(p);
if (domain_)
continue;
}
if (goal_ == Node::NoType) {
goal_ = Node::goal(p);
if (goal_ != Node::NoType)
continue;
}
if (p == "qml")
genus_ = QML;
else if (p == "cpp")
genus_ = CPP;
}
}
/*!
Standard copy constructor of LinkAtom \a t.
*/
LinkAtom::LinkAtom(const LinkAtom& t)
: Atom(Link, t.string()),
genus_(t.genus_),
goal_(t.goal_),
domain_(t.domain_)
{
// nothing
}
/*!
Special copy constructor of LinkAtom \a t, where
where the new LinkAtom will not be the first one
in the list.
*/
LinkAtom::LinkAtom(Atom* previous, const LinkAtom& t)
: Atom(previous, Link, t.string()),
genus_(t.genus_),
goal_(t.goal_),
domain_(t.domain_)
{
previous->next_ = this;
}
QT_END_NAMESPACE

View File

@ -44,10 +44,12 @@
#include <qstringlist.h>
#include "node.h"
#include <qdebug.h>
QT_BEGIN_NAMESPACE
class Tree;
class LinkAtom;
class Atom
{
@ -63,7 +65,7 @@ public:
BriefRight,
C,
CaptionLeft,
CaptionRight,
CaptionRight, // 10
Code,
CodeBad,
CodeNew,
@ -73,7 +75,7 @@ public:
DivLeft,
DivRight,
EndQmlText,
FootnoteLeft,
FootnoteLeft, // 20
FootnoteRight,
FormatElse,
FormatEndif,
@ -83,7 +85,7 @@ public:
GeneratedList,
GuidLink,
HR,
Image,
Image, // 30
ImageText,
ImportantLeft,
ImportantRight,
@ -93,7 +95,7 @@ public:
LegaleseLeft,
LegaleseRight,
LineBreak,
Link,
Link, // 40
LinkNode,
ListLeft,
ListItemNumber,
@ -103,7 +105,7 @@ public:
ListItemRight,
ListRight,
Nop,
NoteLeft,
NoteLeft, // 50
NoteRight,
ParaLeft,
ParaRight,
@ -113,7 +115,7 @@ public:
QuotationRight,
RawString,
SectionLeft,
SectionRight,
SectionRight, // 60
SectionHeadingLeft,
SectionHeadingRight,
SidebarLeft,
@ -123,7 +125,7 @@ public:
SnippetIdentifier,
SnippetLocation,
String,
TableLeft,
TableLeft, // 70
TableRight,
TableHeaderLeft,
TableHeaderRight,
@ -133,11 +135,21 @@ public:
TableItemRight,
TableOfContents,
Target,
UnhandledFormat,
UnhandledFormat, // 80
UnknownCommand,
Last = UnknownCommand
};
enum NodeGenus { DontCare, CPP, QML };
friend class LinkAtom;
Atom(const QString& string)
: next_(0), type_(Link)
{
strs << string;
}
Atom(Type type, const QString& string = "")
: next_(0), type_(type)
{
@ -186,8 +198,10 @@ public:
const QString& string(int i) const { return strs[i]; }
int count() const { return strs.size(); }
void dump() const;
const QStringList& strings() const { return strs; }
virtual bool qml() const { return false; }
virtual bool isLinkAtom() const { return false; }
virtual NodeGenus genus() const { return DontCare; }
virtual bool specifiesDomain() const { return false; }
virtual Tree* domain() const { return 0; }
virtual Node::Type goal() const { return Node::NoType; }
@ -202,15 +216,18 @@ class LinkAtom : public Atom
{
public:
LinkAtom(const QString& p1, const QString& p2);
LinkAtom(const LinkAtom& t);
LinkAtom(Atom* previous, const LinkAtom& t);
virtual ~LinkAtom() { }
virtual bool qml() const { return qml_; }
virtual bool isLinkAtom() const { return true; }
virtual NodeGenus genus() const { return genus_; }
virtual bool specifiesDomain() const { return (domain_ != 0); }
virtual Tree* domain() const { return domain_; }
virtual Node::Type goal() const { return goal_; }
protected:
bool qml_;
NodeGenus genus_;
Node::Type goal_;
Tree* domain_;
};

View File

@ -765,7 +765,7 @@ int DitaXmlGenerator::generateAtom(const Atom *atom,
case Atom::AutoLink:
if (!noLinks && !inLink_ && !inContents_ && !inSectionHeading_) {
const Node* node = 0;
QString link = getLink(atom, relative, &node);
QString link = getAutoLink(atom, relative, &node);
if (!link.isEmpty()) {
beginLink(link);
generateLink(atom, marker);
@ -1308,13 +1308,11 @@ int DitaXmlGenerator::generateAtom(const Atom *atom,
case Atom::Link:
{
const Node *node = 0;
QString myLink = getLink(atom, relative, &node);
//if (myLink.isEmpty())
//myLink = getCollisionLink(atom);
if (myLink.isEmpty() && !noLinkErrors())
QString link = getLink(atom, relative, &node);
if (link.isEmpty() && !noLinkErrors())
relative->doc().location().warning(tr("Can't link to '%1'").arg(atom->string()));
else if (!inSectionHeading_)
beginLink(myLink);
beginLink(link);
skipAhead = 1;
}
break;
@ -3722,6 +3720,129 @@ QString DitaXmlGenerator::fileName(const Node* node)
return Generator::fileName(node);
}
/*!
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, qdoc
calls getAutoLink().
Return the link represented by the \a atom, and set \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 DitaXmlGenerator::getLink(const Atom *atom, const Node *relative, const Node** node)
{
if (atom->string().contains(QLatin1Char(':')) && (atom->string().startsWith("file:") ||
atom->string().startsWith("http:") ||
atom->string().startsWith("https:") ||
atom->string().startsWith("ftp:") ||
atom->string().startsWith("mailto:"))) {
return atom->string(); // It's some kind of protocol.
}
QString ref;
QString link;
QStringList path = atom->string().split("#");
QString first = path.first().trimmed();
*node = 0;
if (first.isEmpty())
*node = relative; // search for a target on the current page.
else {
if (first.endsWith(".html")) { // The target is an html file.
*node = qdb_->findNodeByNameAndType(QStringList(first), Node::Document);
}
else if (first.endsWith("()")) { // The target is a C++ function or QML method.
*node = qdb_->resolveFunctionTarget(first, relative);
}
else {
*node = qdb_->resolveTarget(first, relative);
if (!(*node))
*node = qdb_->findDocNodeByTitle(first);
if (!(*node)) {
*node = qdb_->findUnambiguousTarget(first, ref);
if (*node && !(*node)->url().isEmpty() && !ref.isEmpty()) {
QString final = (*node)->url() + "#" + ref;
return final;
}
}
}
}
if (!(*node))
return link; // empty
if (!(*node)->url().isEmpty())
return (*node)->url();
if (!path.isEmpty()) {
ref = qdb_->findTarget(path.first(), *node);
if (ref.isEmpty())
return link; // empty
}
/*
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 '#'.
*/
link = linkForNode(*node, relative);
if (*node && (*node)->subType() == Node::Image)
link = "images/used-in-examples/" + link;
if (!ref.isEmpty())
link += QLatin1Char('#') + ref;
return link;
}
/*!
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, qdoc calls getLink().
Return the link represented by the \a atom, and set \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 DitaXmlGenerator::getAutoLink(const Atom *atom, const Node *relative, const Node** node)
{
QString ref;
QString link;
QString target = atom->string().trimmed();
*node = 0;
if (target.endsWith("()")) { // The target is a C++ function or QML method.
*node = qdb_->resolveFunctionTarget(target, relative);
}
else {
*node = qdb_->resolveTarget(target, relative);
if (!(*node)) {
*node = qdb_->findDocNodeByTitle(target);
}
if (!(*node)) {
*node = qdb_->findUnambiguousTarget(target, ref);
if (*node && !(*node)->url().isEmpty() && !ref.isEmpty()) {
QString final = (*node)->url() + "#" + ref;
return final;
}
}
}
if (!(*node))
return link; // empty
if (!(*node)->url().isEmpty())
return (*node)->url();
link = linkForNode(*node, relative);
if (!ref.isEmpty())
link += QLatin1Char('#') + ref;
return link;
}
QString DitaXmlGenerator::linkForNode(const Node* node, const Node* relative)
{
if (node == 0 || node == relative)
@ -3832,103 +3953,6 @@ const QPair<QString,QString> DitaXmlGenerator::anchorForNode(const Node* node)
return anchorPair;
}
QString DitaXmlGenerator::getLink(const Atom* atom, const Node* relative, const Node** node)
{
QString link;
*node = 0;
inObsoleteLink = false;
if (atom->string().contains(QLatin1Char(':')) &&
(atom->string().startsWith("file:")
|| atom->string().startsWith("http:")
|| atom->string().startsWith("https:")
|| atom->string().startsWith("ftp:")
|| atom->string().startsWith("mailto:"))) {
link = atom->string();
}
else {
QStringList path;
if (atom->string().contains('#'))
path = atom->string().split('#');
else
path.append(atom->string());
QString ref;
QString first = path.first().trimmed();
if (first.isEmpty())
*node = relative;
else if (first.endsWith(".html"))
*node = qdb_->findNodeByNameAndType(QStringList(first), Node::Document);
else if (first.endsWith("()")) // The target is a C++ function or QML method.
*node = qdb_->resolveFunctionTarget(first, relative);
else {
*node = qdb_->resolveTarget(first, relative);
if (!(*node))
*node = qdb_->findDocNodeByTitle(first);
if (!*node)
*node = qdb_->findUnambiguousTarget(first, ref);
}
if (*node) {
if (!(*node)->url().isEmpty())
return (*node)->url();
else
path.removeFirst();
}
else
*node = relative;
if (*node && (*node)->status() == Node::Obsolete) {
if (relative && (relative->parent() != *node) &&
(relative->status() != Node::Obsolete)) {
bool porting = false;
if (relative->isDocNode()) {
const DocNode* fake = static_cast<const DocNode*>(relative);
if (fake->title().startsWith("Porting"))
porting = true;
}
QString name = relative->plainFullName();
if (!porting && !name.startsWith("Q3")) {
if (obsoleteLinks) {
relative->doc().location().warning(tr("Link to obsolete item '%1' in %2")
.arg(atom->string())
.arg(name));
}
inObsoleteLink = true;
}
}
}
while (!path.isEmpty()) {
ref = qdb_->findTarget(path.first(), *node);
if (ref.isEmpty())
break;
path.removeFirst();
}
if (path.isEmpty()) {
link = linkForNode(*node, relative);
if (*node && (*node)->subType() == Node::Image)
link = "images/used-in-examples/" + link;
if (!ref.isEmpty()) {
if (link.isEmpty())
link = outFileName();
QString guid = lookupGuid(link, ref);
link += QLatin1Char('#') + guid;
}
else if (!link.isEmpty() && *node && (link.endsWith(".xml") || link.endsWith(".dita"))) {
link += QLatin1Char('#') + (*node)->guid();
}
}
}
if (!link.isEmpty() && link[0] == '#') {
link.prepend(outFileName());
}
return link;
}
void DitaXmlGenerator::generateStatus(const Node* node, CodeMarker* marker)
{
Text text;

View File

@ -425,12 +425,14 @@ private:
void generateLink(const Atom* atom, CodeMarker* marker);
void generateStatus(const Node* node, CodeMarker* marker);
QString getLink(const Atom *atom, const Node *relative, const Node** node);
QString getAutoLink(const Atom *atom, const Node *relative, const Node** node);
QString registerRef(const QString& ref);
virtual QString fileBase(const Node *node) const;
QString fileName(const Node *node);
static int hOffset(const Node *node);
static bool isThreeColumnEnumValueTable(const Atom *atom);
QString getLink(const Atom *atom, const Node *relative, const Node **node);
#ifdef GENERATE_MAC_REFS
void generateMacRef(const Node* node, CodeMarker* marker);
#endif

View File

@ -475,6 +475,7 @@ private:
void startSection(Doc::Sections unit, int cmd);
void endSection(int unit, int endCmd);
void parseAlso();
void append(const QString &string);
void append(Atom::Type type, const QString& string = QString());
void append(Atom::Type type, const QString& p1, const QString& p2);
void append(const QString& p1, const QString& p2);
@ -968,8 +969,9 @@ void DocParser::parse(const QString& source,
break;
case CMD_L:
enterPara();
if (isLeftBracketAhead())
if (isLeftBracketAhead()) {
p2 = getBracketedArgument();
}
if (isLeftBraceAhead()) {
p1 = getArgument();
append(p1, p2);
@ -990,6 +992,7 @@ void DocParser::parse(const QString& source,
append(Atom::String, cleanLink(p1));
append(Atom::FormattingRight, ATOM_FORMATTING_LINK);
}
p2.clear();
break;
case CMD_LEGALESE:
leavePara();
@ -1001,7 +1004,7 @@ void DocParser::parse(const QString& source,
if (openCommand(cmd)) {
enterPara();
p1 = getArgument();
append(Atom::Link, p1);
append(p1);
append(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
skipSpacesOrOneEndl();
}
@ -1981,6 +1984,14 @@ void DocParser::append(Atom::Type type, const QString &string)
priv->text << Atom(type, string);
}
void DocParser::append(const QString &string)
{
Atom::Type lastType = priv->text.lastAtom()->type();
if ((lastType == Atom::Code) && priv->text.lastAtom()->string().endsWith(QLatin1String("\n\n")))
priv->text.lastAtom()->chopString();
priv->text << Atom(string);
}
void DocParser::append(Atom::Type type, const QString& p1, const QString& p2)
{
Atom::Type lastType = priv->text.lastAtom()->type();
@ -1995,7 +2006,7 @@ void DocParser::append(const QString& p1, const QString& p2)
if ((lastType == Atom::Code) && priv->text.lastAtom()->string().endsWith(QLatin1String("\n\n")))
priv->text.lastAtom()->chopString();
if (p2.isEmpty())
priv->text << Atom(Atom::Link, p1, p2);
priv->text << Atom(p1);
else
priv->text << LinkAtom(p1, p2);
}

View File

@ -3648,6 +3648,112 @@ QString HtmlGenerator::refForNode(const Node *node)
#define DEBUG_ABSTRACT 0
/*!
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;
}
QString ref;
*node = qdb_->findNode(atom, relative, ref);
if (!(*node))
return QString();
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;
}
/*!
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;
QString link;
QString target = atom->string().trimmed();
*node = 0;
if (target.endsWith("()")) { // The target is a C++ function or QML method.
*node = qdb_->resolveFunctionTarget(target, relative);
}
else {
*node = qdb_->resolveTarget(target, relative);
if (!(*node)) {
*node = qdb_->findDocNodeByTitle(target);
}
if (!(*node)) {
*node = qdb_->findUnambiguousTarget(target, ref);
if (*node && !(*node)->url().isEmpty() && !ref.isEmpty()) {
QString final = (*node)->url() + "#" + ref;
return final;
}
}
}
if (!(*node))
return link; // empty
if (!(*node)->url().isEmpty())
return (*node)->url();
link = linkForNode(*node, relative);
if (!ref.isEmpty())
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
@ -3847,137 +3953,6 @@ const QPair<QString,QString> HtmlGenerator::anchorForNode(const Node *node)
return anchorPair;
}
/*!
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, qdoc
calls getAutoLink().
Return the link represented by the \a atom, and set \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)
{
if (atom->string().contains(QLatin1Char(':')) && (atom->string().startsWith("file:") ||
atom->string().startsWith("http:") ||
atom->string().startsWith("https:") ||
atom->string().startsWith("ftp:") ||
atom->string().startsWith("mailto:"))) {
return atom->string(); // It's some kind of protocol.
}
QString ref;
QString link;
QString first;
QStringList path;
*node = 0;
if (atom->string().contains('#')) {
path = atom->string().split('#');
first = path.first().trimmed();
path.removeFirst();
}
else
first = atom->string();
if (first.isEmpty())
*node = relative; // search for a target on the current page.
else {
if (first.endsWith(".html")) { // The target is an html file.
*node = qdb_->findNodeByNameAndType(QStringList(first), Node::Document);
//Node* n = qdb_->findHtmlFileNode(atom);
}
else if (first.endsWith("()")) { // The target is a C++ function or QML method.
*node = qdb_->resolveFunctionTarget(first, relative);
}
else {
*node = qdb_->resolveTarget(first, relative);
if (!(*node))
*node = qdb_->findDocNodeByTitle(first);
if (!(*node)) {
*node = qdb_->findUnambiguousTarget(first, ref);
if (*node && !(*node)->url().isEmpty() && !ref.isEmpty()) {
QString final = (*node)->url() + "#" + ref;
return final;
}
}
}
}
if (!(*node))
return link; // empty
if (!(*node)->url().isEmpty())
return (*node)->url();
if (!path.isEmpty()) {
ref = qdb_->findTarget(path.first(), *node);
if (ref.isEmpty())
return link; // empty
}
/*
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 '#'.
*/
link = linkForNode(*node, relative);
if (*node && (*node)->subType() == Node::Image)
link = "images/used-in-examples/" + link;
if (!ref.isEmpty())
link += QLatin1Char('#') + ref;
return link;
}
/*!
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, qdoc calls getLink().
Return the link represented by the \a atom, and set \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;
QString link;
QString path = atom->string().trimmed();
*node = 0;
if (path.endsWith("()")) { // The target is a C++ function or QML method.
*node = qdb_->resolveFunctionTarget(path, relative);
}
else {
*node = qdb_->resolveTarget(path, relative);
if (!(*node)) {
*node = qdb_->findDocNodeByTitle(path);
}
if (!(*node)) {
*node = qdb_->findUnambiguousTarget(path, ref);
if (*node && !(*node)->url().isEmpty() && !ref.isEmpty()) {
QString final = (*node)->url() + "#" + ref;
return final;
}
}
}
if (!(*node))
return link; // empty
if (!(*node)->url().isEmpty())
return (*node)->url();
link = linkForNode(*node, relative);
if (!ref.isEmpty())
link += QLatin1Char('#') + ref;
return link;
}
void HtmlGenerator::generateStatus(const Node *node, CodeMarker *marker)
{
Text text;

View File

@ -211,13 +211,14 @@ private:
void generateLink(const Atom *atom, CodeMarker *marker);
void generateStatus(const Node *node, CodeMarker *marker);
QString getLink(const Atom *atom, const Node *relative, const Node** node);
QString getAutoLink(const Atom *atom, const Node *relative, const Node** node);
QString registerRef(const QString& ref);
virtual QString fileBase(const Node *node) const;
QString fileName(const Node *node);
static int hOffset(const Node *node);
static bool isThreeColumnEnumValueTable(const Atom *atom);
QString getLink(const Atom *atom, const Node *relative, const Node** node);
QString getAutoLink(const Atom *atom, const Node *relative, const Node** node);
#ifdef GENERATE_MAC_REFS
void generateMacRef(const Node *node, CodeMarker *marker);
#endif

View File

@ -292,7 +292,7 @@ static void processQdocconfFile(const QString &fileName)
Location::initialize(config);
config.load(fileName);
QString project = config.getString(CONFIG_PROJECT).toLower();
//qDebug() << "\nSTART PROJECT:" << project;
//qDebug() << "\nStart project:" << project;
/*
Add the defines to the configuration variables.
*/

View File

@ -686,6 +686,14 @@ bool Node::isInternal() const
return false;
}
/*!
Returns a pointer to the Tree this node is in.
*/
Tree* Node::tree() const
{
return (parent() ? parent()->tree() : 0);
}
/*!
Returns a pointer to the root of the Tree this node is in.
*/

View File

@ -248,7 +248,7 @@ public:
virtual bool wasSeen() const { return false; }
virtual void appendGroupName(const QString& ) { }
virtual QString element() const { return QString(); }
virtual Tree* tree() const { return 0; }
virtual Tree* tree() const;
bool isIndexNode() const { return indexNodeFlag_; }
Type type() const { return nodeType_; }
virtual SubType subType() const { return NoSubType; }
@ -440,7 +440,7 @@ public:
NamespaceNode(InnerNode* parent, const QString& name);
virtual ~NamespaceNode() { }
virtual bool isNamespace() const { return true; }
virtual Tree* tree() const { return tree_; }
virtual Tree* tree() const { return (parent() ? parent()->tree() : tree_); }
void setTree(Tree* t) { tree_ = t; }
private:

View File

@ -384,7 +384,7 @@ void QDocForest::newPrimaryTree(const QString& module)
point, but it only makes sense in the primary tree, which is
searched first. After the primary tree is searched, \a relative
is set to 0 for searching the index trees. When relative is 0,
the root node of the index tree is the starting point.
the root nodes of the index trees are the starting points.
*/
const Node* QDocForest::resolveTarget(const QString& target, const Node* relative)
{
@ -395,11 +395,6 @@ const Node* QDocForest::resolveTarget(const QString& target, const Node* relativ
const Node* n = t->findNode(path, relative, flags);
if (n)
return n;
#if 0
n = t->findDocNodeByTitle(target);
if (n)
return n;
#endif
relative = 0;
}
return 0;
@ -1567,67 +1562,86 @@ void QDocDatabase::mergeCollections(CollectionNode* cn)
}
}
/*!
This function is called when the \a{atom} might be a link
atom. It handles the optional, square bracket parameters
for the link command.
*/
Node* QDocDatabase::findNode(const Atom* atom)
{
QStringList path(atom->string());
if (atom->specifiesDomain()) {
return atom->domain()->findNodeByNameAndType(path, atom->goal());
}
qDebug() << "FINDNODE:" << path << atom->goal();
return forest_.findNodeByNameAndType(path, atom->goal());
}
const DocNode* QDocDatabase::findDocNodeByTitle(const Atom* atom)
{
return forest_.findDocNodeByTitle(atom->string());
}
/*!
Searches for the node that matches the path in \a atom. The
\a relative node is used if the first leg of the path is
empty, i.e. if the path begins with a hashtag. The function
also sets \a ref if there remains an unused leg in the path
after the node is found. The node is returned as well as the
\a ref. If the returned node pointer is null, \a ref is not
valid.
*/
const Node* QDocDatabase::findNode(const Atom* atom, const Node* relative, QString& ref)
{
const Node* node = 0;
QStringList path = atom->string().split("#");
QString first = path.first().trimmed();
path.removeFirst();
if (first.isEmpty())
node = relative; // search for a target on the current page.
else if (atom->specifiesDomain()) {
qDebug() << "Processing LinkAtom";
if (first.endsWith(".html")) { // The target is an html file.
node = atom->domain()->findNodeByNameAndType(QStringList(first), Node::Document);
}
else if (first.endsWith("()")) { // The target is a C++ function or QML method.
node = atom->domain()->resolveFunctionTarget(first, 0); //relative);
}
else {
node = atom->domain()->resolveTarget(first, 0); // relative);
if (!node)
node = atom->domain()->findUnambiguousTarget(first, ref); // ref
if (!node && path.isEmpty())
node = atom->domain()->findDocNodeByTitle(first);
}
}
else {
if (first.endsWith(".html")) { // The target is an html file.
node = findNodeByNameAndType(QStringList(first), Node::Document); // ref
}
else if (first.endsWith("()")) { // The target is a C++ function or QML method.
node = resolveFunctionTarget(first, relative);
}
else {
node = resolveTarget(first, relative); // ref
if (!node)
node = findUnambiguousTarget(first, ref); // ref
if (!node && path.isEmpty())
node = findDocNodeByTitle(first);
}
}
if (node && ref.isEmpty()) {
if (!node->url().isEmpty())
return node;
if (!path.isEmpty()) {
ref = findTarget(path.first(), node);
if (ref.isEmpty())
node = 0;
}
}
return node;
}
QT_END_NAMESPACE
#if 0
void getAllGroups(CNMM& t);
void getAllModules(CNMM& t);
void getAllQmlModules(CNMM& t);
/*!
For each tree in the forest, get the group map from the tree.
Insert each pair from the group map into the collection node
multimap \a t.
*/
void QDocForest::getAllGroups(CNMM& t)
{
foreach (Tree* t, searchOrder()) {
const GroupMap& gm = t->groups();
if (!gm.isEmpty()) {
GroupMap::const_iterator i = gm.begin();
while (i != gm.end()) {
t.insert(i.key(), i.value());
++i;
}
}
}
}
/*!
For each tree in the forest, get the module map from the tree.
Insert each pair from the module map into the collection node
multimap \a t.
*/
void QDocForest::getAllModules(CNMM& t)
{
foreach (Tree* t, searchOrder()) {
const ModuleMap& mm = t->modules();
if (!mm.isEmpty()) {
ModuleMap::const_iterator i = mm.begin();
while (i != mm.end()) {
t.insert(i.key(), i.value());
++i;
}
}
}
}
/*!
For each tree in the forest, get the QML module map from the
tree. Insert each pair from the QML module map into the
collection node multimap \a t.
*/
void QDocForest::getAllQmlModules(CNMM& t)
{
foreach (Tree* t, searchOrder()) {
const QmlModuleMap& qmm = t->groups();
if (!qmm.isEmpty()) {
QmlModuleMap::const_iterator i = qmm.begin();
while (i != qmm.end()) {
t.insert(i.key(), i.value());
++i;
}
}
}
}
#endif

View File

@ -249,7 +249,9 @@ class QDocDatabase
void findAllSince(InnerNode *node);
public:
// special collection access functions
/*******************************************************************
special collection access functions
********************************************************************/
NodeMap& getCppClasses();
NodeMap& getMainClasses();
NodeMap& getCompatibilityClasses();
@ -266,9 +268,9 @@ class QDocDatabase
const NodeMap& getQmlTypeMap(const QString& key);
const NodeMultiMap& getSinceMap(const QString& key);
/* convenience functions
Many of these will be either eliminated or replaced.
*/
/*******************************************************************
Many of these will be either eliminated or replaced.
********************************************************************/
void resolveInheritance() { primaryTree()->resolveInheritance(); }
void resolveQmlInheritance(InnerNode* root);
void resolveIssues();
@ -298,6 +300,14 @@ class QDocDatabase
}
/*******************************************************************/
/*******************************************************************
The functions declared below handle the parameters in '[' ']'.
********************************************************************/
Node* findNode(const Atom* atom);
const Node* findNode(const Atom* atom, const Node* relative, QString& ref);
const DocNode* findDocNodeByTitle(const Atom* atom);
/*******************************************************************/
/*******************************************************************
The functions declared below are called for all trees.
********************************************************************/

View File

@ -54,6 +54,8 @@
QT_BEGIN_NAMESPACE
static Node* top = 0;
/*!
\class QDocIndexFiles
@ -110,11 +112,13 @@ void QDocIndexFiles::readIndexes(const QStringList& indexFiles)
foreach (const QString& indexFile, indexFiles) {
QString msg = "Loading index file: " + indexFile;
Location::logToStdErr(msg);
//qDebug() << " LOAD INDEX FILE:" << indexFile;
//qDebug() << msg;
readIndexFile(indexFile);
}
}
static bool readingRoot = true;
/*!
Reads and parses the index file at \a path.
*/
@ -146,6 +150,7 @@ void QDocIndexFiles::readIndexFile(const QString& path)
basesList_.clear();
relatedList_.clear();
readingRoot = true;
NamespaceNode* root = qdb_->newIndexTree(project_);
// Scan all elements in the XML file, constructing a map that contains
@ -154,6 +159,7 @@ void QDocIndexFiles::readIndexFile(const QString& path)
while (!child.isNull()) {
readIndexSection(child, root, indexUrl);
child = child.nextSiblingElement();
readingRoot = true;
}
// Now that all the base classes have been found for this index,
@ -167,13 +173,16 @@ void QDocIndexFiles::readIndexFile(const QString& path)
appropriate node(s).
*/
void QDocIndexFiles::readIndexSection(const QDomElement& element,
InnerNode* parent,
Node* current,
const QString& indexUrl)
{
QString name = element.attribute("name");
QString href = element.attribute("href");
Node* node;
Location location;
InnerNode* parent = 0;
if (current->isInnerNode())
parent = static_cast<InnerNode*>(current);
QString filePath;
int lineNo = 0;
@ -462,15 +471,15 @@ void QDocIndexFiles::readIndexSection(const QDomElement& element,
location = Location(parent->name().toLower() + ".html");
}
else if (element.nodeName() == "keyword") {
qdb_->insertTarget(name, TargetRec::Keyword, parent, 1);
qdb_->insertTarget(name, TargetRec::Keyword, current, 1);
return;
}
else if (element.nodeName() == "target") {
qdb_->insertTarget(name, TargetRec::Target, parent, 2);
qdb_->insertTarget(name, TargetRec::Target, current, 2);
return;
}
else if (element.nodeName() == "contents") {
qdb_->insertTarget(name, TargetRec::Contents, parent, 3);
qdb_->insertTarget(name, TargetRec::Contents, current, 3);
return;
}
else
@ -560,26 +569,15 @@ void QDocIndexFiles::readIndexSection(const QDomElement& element,
node->setReconstitutedBrief(briefAttr);
}
if (node->isInnerNode()) {
InnerNode* inner = static_cast<InnerNode*>(node);
// zzz
bool useParent = (element.nodeName() == "namespace" && name.isEmpty());
if (element.hasChildNodes()) {
QDomElement child = element.firstChildElement();
while (!child.isNull()) {
if (element.nodeName() == "class") {
readIndexSection(child, inner, indexUrl);
}
else if (element.nodeName() == "qmlclass") {
readIndexSection(child, inner, indexUrl);
}
else if (element.nodeName() == "page") {
readIndexSection(child, inner, indexUrl);
}
else if (element.nodeName() == "namespace" && !name.isEmpty()) {
// The root node in the index is a namespace with an empty name.
readIndexSection(child, inner, indexUrl);
}
else {
if (useParent)
readIndexSection(child, parent, indexUrl);
}
else
readIndexSection(child, node, indexUrl);
child = child.nextSiblingElement();
}
}
@ -1143,6 +1141,42 @@ bool QDocIndexFiles::generateIndexSection(QXmlStreamWriter& writer,
break;
}
/*
For our pages, we canonicalize the target, keyword and content
item names so that they can be used by qdoc for other sets of
documentation.
The reason we do this here is that we don't want to ruin
externally composed indexes, containing non-qdoc-style target names
when reading in indexes.
targets and keywords are now allowed in any node, not just inner nodes.
*/
if (node->doc().hasTargets()) {
bool external = false;
if (node->type() == Node::Document) {
const DocNode* docNode = static_cast<const DocNode*>(node);
if (docNode->subType() == Node::ExternalPage)
external = true;
}
foreach (const Atom* target, node->doc().targets()) {
QString targetName = target->string();
if (!external)
targetName = Doc::canonicalTitle(targetName);
writer.writeStartElement("target");
writer.writeAttribute("name", targetName);
writer.writeEndElement(); // target
}
}
if (node->doc().hasKeywords()) {
foreach (const Atom* keyword, node->doc().keywords()) {
writer.writeStartElement("keyword");
writer.writeAttribute("name", Doc::canonicalTitle(keyword->string()));
writer.writeEndElement(); // keyword
}
}
// Inner nodes and function nodes contain child nodes of some sort, either
// actual child nodes or function parameters. For these, we close the
// opening tag, create child elements, then add a closing tag for the
@ -1151,36 +1185,6 @@ bool QDocIndexFiles::generateIndexSection(QXmlStreamWriter& writer,
if (node->isInnerNode()) {
const InnerNode* inner = static_cast<const InnerNode*>(node);
// For internal pages, we canonicalize the target, keyword and content
// item names so that they can be used by qdoc for other sets of
// documentation.
// The reason we do this here is that we don't want to ruin
// externally composed indexes, containing non-qdoc-style target names
// when reading in indexes.
if (inner->doc().hasTargets()) {
bool external = false;
if (inner->type() == Node::Document) {
const DocNode* docNode = static_cast<const DocNode*>(inner);
if (docNode->subType() == Node::ExternalPage)
external = true;
}
foreach (const Atom* target, inner->doc().targets()) {
QString targetName = target->string();
if (!external)
targetName = Doc::canonicalTitle(targetName);
writer.writeStartElement("target");
writer.writeAttribute("name", targetName);
writer.writeEndElement(); // target
}
}
if (inner->doc().hasKeywords()) {
foreach (const Atom* keyword, inner->doc().keywords()) {
writer.writeStartElement("keyword");
writer.writeAttribute("name", Doc::canonicalTitle(keyword->string()));
writer.writeEndElement(); // keyword
}
}
if (inner->doc().hasTableOfContents()) {
for (int i = 0; i < inner->doc().tableOfContents().size(); ++i) {
Atom* item = inner->doc().tableOfContents()[i];
@ -1336,6 +1340,47 @@ void QDocIndexFiles::generateIndexSections(QXmlStreamWriter& writer,
generateIndexSections(writer, child, generateInternalNodes);
}
}
if (node == top) {
/*
We wait until the end of the index file to output the group, module,
and QML module elements. By outputting them at the end, when we read
the index file back in, all the group, module, and QML module member
elements will have already been created. It is then only necessary to
create the group, module, or QML module element and add each member to
its member list.
*/
const CNMap& groups = qdb_->groups();
if (!groups.isEmpty()) {
CNMap::ConstIterator g = groups.constBegin();
while (g != groups.constEnd()) {
if (generateIndexSection(writer, g.value(), generateInternalNodes))
writer.writeEndElement();
++g;
}
}
const CNMap& modules = qdb_->modules();
if (!modules.isEmpty()) {
CNMap::ConstIterator g = modules.constBegin();
while (g != modules.constEnd()) {
if (generateIndexSection(writer, g.value(), generateInternalNodes))
writer.writeEndElement();
++g;
}
}
const CNMap& qmlModules = qdb_->qmlModules();
if (!qmlModules.isEmpty()) {
CNMap::ConstIterator g = qmlModules.constBegin();
while (g != qmlModules.constEnd()) {
if (generateIndexSection(writer, g.value(), generateInternalNodes))
writer.writeEndElement();
++g;
}
}
}
writer.writeEndElement();
}
}
@ -1368,45 +1413,8 @@ void QDocIndexFiles::generateIndex(const QString& fileName,
writer.writeAttribute("version", qdb_->version());
writer.writeAttribute("project", g->config()->getString(CONFIG_PROJECT));
generateIndexSections(writer, qdb_->primaryTreeRoot(), generateInternalNodes);
/*
We wait until the end of the index file to output the group, module,
and QML module elements. By outputting them at the end, when we read
the index file back in, all the group, module, and QML module member
elements will have already been created. It is then only necessary to
create the group, module, or QML module element and add each member to
its member list.
*/
const CNMap& groups = qdb_->groups();
if (!groups.isEmpty()) {
CNMap::ConstIterator g = groups.constBegin();
while (g != groups.constEnd()) {
if (generateIndexSection(writer, g.value(), generateInternalNodes))
writer.writeEndElement();
++g;
}
}
const CNMap& modules = qdb_->modules();
if (!modules.isEmpty()) {
CNMap::ConstIterator g = modules.constBegin();
while (g != modules.constEnd()) {
if (generateIndexSection(writer, g.value(), generateInternalNodes))
writer.writeEndElement();
++g;
}
}
const CNMap& qmlModules = qdb_->qmlModules();
if (!qmlModules.isEmpty()) {
CNMap::ConstIterator g = qmlModules.constBegin();
while (g != qmlModules.constEnd()) {
if (generateIndexSection(writer, g.value(), generateInternalNodes))
writer.writeEndElement();
++g;
}
}
top = qdb_->primaryTreeRoot();
generateIndexSections(writer, top, generateInternalNodes);
writer.writeEndElement(); // INDEX
writer.writeEndElement(); // QDOCINDEX

View File

@ -72,7 +72,7 @@ class QDocIndexFiles
bool generateInternalNodes = false);
void readIndexFile(const QString& path);
void readIndexSection(const QDomElement& element, InnerNode* parent, const QString& indexUrl);
void readIndexSection(const QDomElement& element, Node* current, const QString& indexUrl);
void resolveIndex();
bool generateIndexSection(QXmlStreamWriter& writer, Node* node, bool generateInternalNodes = false);
void generateIndexSections(QXmlStreamWriter& writer, Node* node, bool generateInternalNodes = false);

View File

@ -111,6 +111,22 @@ Text& Text::operator<<(const Atom& atom)
return *this;
}
/*!
Special output operator for LinkAtom. It makes a copy of
the LinkAtom \a atom and connects the cop;y to the list
in this Text.
*/
Text& Text::operator<<(const LinkAtom& atom)
{
if (first == 0) {
first = new LinkAtom(atom);
last = first;
}
else
last = new LinkAtom(last, atom);
return *this;
}
Text& Text::operator<<(const Text& text)
{
const Atom* atom = text.firstAtom();

View File

@ -65,6 +65,7 @@ public:
Text& operator<<(Atom::Type atomType);
Text& operator<<(const QString& string);
Text& operator<<(const Atom& atom);
Text& operator<<(const LinkAtom& atom);
Text& operator<<(const Text& text);
void stripFirstAtom();
void stripLastAtom();

View File

@ -563,20 +563,6 @@ Node* Tree::findNodeByNameAndType(const QStringList& path, Node::Type type) cons
{
return findNodeRecursive(path, 0, root(), type);
}
#if 0
/*!
Find the node with the specified \a path name that is of
the specified \a type and \a subtype. Begin the search at
the \a start node. If the \a start node is 0, begin the
search at the tree root. \a subtype is not used unless
\a type is \c{Document}.
*/
Node* Tree::findHtmlFileNode(const QStringList& path) const
{
return findNodeRecursive(path, 0, root());
}
#endif
/* internal members */
/*!
Recursive search for a node identified by \a path. Each
@ -624,7 +610,7 @@ Node* Tree::findNodeRecursive(const QStringList& path,
}
else if (n->name() == name) {
if (pathIndex+1 >= path.size()) {
if (n->type() == type)
if ((n->type() == type) || (type == Node::NoType))
return n;
continue;
}
@ -815,6 +801,23 @@ void Tree::insertTarget(const QString& name, TargetRec::Type type, Node* node, i
nodesByTarget_.insert(name, target);
}
/*!
Searches this tree for a node named \a target and returns
a pointer to it if found. The \a start node is the starting
point, but it only makes sense if \a start is in this tree.
If \a start is not in this tree, \a start is set to 0 before
beginning the search to ensure that the search starts at the
root.
*/
const Node* Tree::resolveTarget(const QString& target, const Node* start)
{
QStringList path = target.split("::");
int flags = SearchBaseClasses | SearchEnumValues | NonFunction;
if (start && start->tree() != this)
start = 0;
return findNode(path, start, flags);
}
/*!
*/
void Tree::resolveTargets(InnerNode* root)

View File

@ -114,6 +114,7 @@ class Tree
NameCollisionNode* findCollisionNode(const QString& name) const;
QString findTarget(const QString& target, const Node* node) const;
void insertTarget(const QString& name, TargetRec::Type type, Node* node, int priority);
const Node* resolveTarget(const QString& target, const Node* start);
void resolveTargets(InnerNode* root);
const Node* findUnambiguousTarget(const QString& target, QString& ref);
const DocNode* findDocNodeByTitle(const QString& title) const;