tools/qdoc3/tree.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Fri, 19 Feb 2010 23:40:16 +0200
branchRCL_3
changeset 4 3b1da2848fc7
parent 3 41300fa6a67c
permissions -rw-r--r--
Revision: 201003 Kit: 201007

/****************************************************************************
**
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the tools applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

/*
  tree.cpp
*/

#include <QtCore>
#include <QDomDocument>

#include "atom.h"
#include "doc.h"
#include "htmlgenerator.h"
#include "location.h"
#include "node.h"
#include "text.h"
#include "tree.h"

QT_BEGIN_NAMESPACE

struct InheritanceBound
{
    Node::Access access;
    QStringList basePath;
    QString dataTypeWithTemplateArgs;
    InnerNode *parent;

    InheritanceBound()
        : access(Node::Public) { }
    InheritanceBound(Node::Access access0,
                     const QStringList& basePath0,
                     const QString &dataTypeWithTemplateArgs0,
                     InnerNode *parent)
	: access(access0), basePath(basePath0),
	  dataTypeWithTemplateArgs(dataTypeWithTemplateArgs0),
          parent(parent) { }
};

struct Target
{
    Node *node;
    Atom *atom;
    int priority;
};

typedef QMap<PropertyNode::FunctionRole, QString> RoleMap;
typedef QMap<PropertyNode *, RoleMap> PropertyMap;
typedef QMultiMap<QString, Node *> GroupMap;
typedef QMultiHash<QString, FakeNode *> FakeNodeHash;
typedef QMultiHash<QString, Target> TargetHash;

class TreePrivate
{
public:
    QMap<ClassNode *, QList<InheritanceBound> > unresolvedInheritanceMap;
    PropertyMap unresolvedPropertyMap;
    GroupMap groupMap;
    QMultiMap<QString, QString> publicGroupMap;
    FakeNodeHash fakeNodesByTitle;
    TargetHash targetHash;
    QList<QPair<ClassNode*,QString> > basesList;
    QList<QPair<FunctionNode*,QString> > relatedList;
};

/*!
  \class Tree
 */

/*!
  The default constructor is the only constructor.
 */
Tree::Tree()
    : roo(0, "")
{
    priv = new TreePrivate;
}

/*!
  The destructor deletes the internal, private tree.
 */
Tree::~Tree()
{
    delete priv;
}

/*!
 */
Node *Tree::findNode(const QStringList &path, Node *relative, int findFlags)
{
    return const_cast<Node*>(const_cast<const Tree*>(this)->findNode(path,
                                                                     relative,
                                                                     findFlags));
}

/*!
 */
const Node *Tree::findNode(const QStringList &path,
                           const Node *relative,
                           int findFlags) const
{
    if (!relative)
        relative = root();

    do {
        const Node *node = relative;
        int i;

        for (i = 0; i < path.size(); ++i) {
            if (node == 0 || !node->isInnerNode())
                break;

            const Node *next =
                static_cast<const InnerNode*>(node)->findNode(path.at(i));
            if (!next && (findFlags & SearchEnumValues) && i == path.size()-1)
                next = static_cast<const InnerNode*>(node)->findEnumNodeForValue(path.at(i));

            if (!next && node->type() == Node::Class && (findFlags & SearchBaseClasses)) {
                NodeList baseClasses = allBaseClasses(static_cast<const ClassNode *>(node));
                foreach (const Node *baseClass, baseClasses) {
                    next = static_cast<const InnerNode *>(baseClass)->findNode(path.at(i));
                    if (!next && (findFlags & SearchEnumValues) && i == path.size() - 1)
                        next = static_cast<const InnerNode *>(baseClass)
                                        ->findEnumNodeForValue(path.at(i));
                    if (next)
                        break;
                }
            }
            node = next;
        }
        if (node && i == path.size()
                && (!(findFlags & NonFunction) || node->type() != Node::Function
                    || ((FunctionNode *)node)->metaness() == FunctionNode::MacroWithoutParams))
            return node;
        relative = relative->parent();
    } while (relative);

    return 0;
}

/*!
  Find the node with the specified \a path name of the
  specified \a type.
 */
Node *Tree::findNode(const QStringList &path,
                     Node::Type type,
                     Node *relative,
                     int findFlags)
{
    return const_cast<Node*>(const_cast<const Tree*>(this)->findNode(path,
                                                                     type,
                                                                     relative,
                                                                     findFlags));
}

/*!
  Find the node with the specified \a path name of the
  specified \a type.
 */
const Node *Tree::findNode(const QStringList &path,
                           Node::Type type,
                           const Node *relative,
                           int findFlags) const
{
    const Node *node = findNode(path, relative, findFlags);
    if (node != 0 && node->type() == type)
        return node;
    return 0;
}

/*!
 */
FunctionNode *Tree::findFunctionNode(const QStringList& path,
                                     Node *relative,
                                     int findFlags)
{
    return const_cast<FunctionNode *>(
                const_cast<const Tree *>(this)->findFunctionNode(path,
                                                                 relative,
                                                                 findFlags));
}

/*!
 */
const FunctionNode *Tree::findFunctionNode(const QStringList &path,
                                           const Node *relative,
                                           int findFlags) const
{
    if (!relative)
        relative = root();

    do {
        const Node *node = relative;
        int i;

        for (i = 0; i < path.size(); ++i) {
            if (node == 0 || !node->isInnerNode())
                break;

            const Node *next;
            if (i == path.size() - 1)
                next = ((InnerNode *) node)->findFunctionNode(path.at(i));
            else
                next = ((InnerNode *) node)->findNode(path.at(i));

            if (!next && node->type() == Node::Class &&
                (findFlags & SearchBaseClasses)) {
                NodeList baseClasses = allBaseClasses(static_cast<const ClassNode *>(node));
                foreach (const Node *baseClass, baseClasses) {
                    if (i == path.size() - 1)
                        next = static_cast<const InnerNode *>(baseClass)->findFunctionNode(path.at(i));
                    else
                        next = static_cast<const InnerNode *>(baseClass)->findNode(path.at(i));

                    if (next)
                        break;
                }
            }

            node = next;
        }
        if (node && i == path.size() && node->isFunction()) {
            // CppCodeParser::processOtherMetaCommand ensures that reimplemented
            // functions are private.
            const FunctionNode *func = static_cast<const FunctionNode*>(node);
            while (func->access() == Node::Private) {
                const FunctionNode *from = func->reimplementedFrom();
                if (from != 0) {
                    if (from->access() != Node::Private)
                        return from;
                    else
                        func = from;
                }
                else
                    break;
            }
            return func;
        }
        relative = relative->parent();
    } while (relative);

    return 0;
}

/*!
 */
FunctionNode *Tree::findFunctionNode(const QStringList &parentPath,
                                     const FunctionNode *clone,
                                     Node *relative,
                                     int findFlags)
{
    return const_cast<FunctionNode *>(
		const_cast<const Tree *>(this)->findFunctionNode(parentPath,
                                                                 clone,
                                      				 relative,
                                                                 findFlags));
}

/*!
 */
const FunctionNode *Tree::findFunctionNode(const QStringList &parentPath,
                                           const FunctionNode *clone,
                                           const Node *relative,
                                           int findFlags) const
{
    const Node *parent = findNode(parentPath, relative, findFlags);
    if (parent == 0 || !parent->isInnerNode()) {
        return 0;
    }
    else {
        return ((InnerNode *)parent)->findFunctionNode(clone);
    }
}

static const int NumSuffixes = 3;
static const char * const suffixes[NumSuffixes] = { "", "s", "es" };

/*!
 */
const FakeNode *Tree::findFakeNodeByTitle(const QString &title) const
{
    for (int pass = 0; pass < NumSuffixes; ++pass) {
        FakeNodeHash::const_iterator i =
                priv->fakeNodesByTitle.find(Doc::canonicalTitle(title + suffixes[pass]));
        if (i != priv->fakeNodesByTitle.constEnd()) {
            FakeNodeHash::const_iterator j = i;
            ++j;
            if (j != priv->fakeNodesByTitle.constEnd() && j.key() == i.key()) {
                QList<Location> internalLocations;
                while (j != priv->fakeNodesByTitle.constEnd()) {
                    if (j.key() == i.key() && j.value()->url().isEmpty())
                        internalLocations.append(j.value()->doc().location());
                    ++j;
                }
                if (internalLocations.size() > 0) {
                    i.value()->doc().location().warning(
                        tr("Page '%1' defined in more than one location:").arg(title));
                    foreach (const Location &location, internalLocations)
                        location.warning(tr("(defined here)"));
                }
            }
            return i.value();
        }
    }
    return 0;
}

/*!
 */
const Node*
Tree::findUnambiguousTarget(const QString &target, Atom *&atom) const
{
    Target bestTarget = {0, 0, INT_MAX};
    int numBestTargets = 0;

    for (int pass = 0; pass < NumSuffixes; ++pass) {
        TargetHash::const_iterator i =
                priv->targetHash.find(Doc::canonicalTitle(target + suffixes[pass]));
        if (i != priv->targetHash.constEnd()) {
            TargetHash::const_iterator j = i;
            do {
                const Target &candidate = j.value();
                if (candidate.priority < bestTarget.priority) {
                    bestTarget = candidate;
                    numBestTargets = 1;
                } else if (candidate.priority == bestTarget.priority) {
                    ++numBestTargets;
                }
                ++j;
            } while (j != priv->targetHash.constEnd() && j.key() == i.key());

            if (numBestTargets == 1) {
                atom = bestTarget.atom;
                return bestTarget.node;
            }
        }
    }
    return 0;
}

/*!
 */
Atom *Tree::findTarget(const QString &target, const Node *node) const
{
    for (int pass = 0; pass < NumSuffixes; ++pass) {
        QString key = Doc::canonicalTitle(target + suffixes[pass]);
        TargetHash::const_iterator i = priv->targetHash.find(key);

        if (i != priv->targetHash.constEnd()) {
            do {
                if (i.value().node == node)
                    return i.value().atom;
                ++i;
            } while (i != priv->targetHash.constEnd() && i.key() == key);
        }
    }
    return 0;
}

/*!
 */
void Tree::addBaseClass(ClassNode *subclass, Node::Access access,
                        const QStringList &basePath,
                        const QString &dataTypeWithTemplateArgs,
                        InnerNode *parent)
{
    priv->unresolvedInheritanceMap[subclass].append(
	    InheritanceBound(access,
                             basePath,
                             dataTypeWithTemplateArgs,
                             parent)
                                                   );
}


/*!
 */
void Tree::addPropertyFunction(PropertyNode *property,
                               const QString &funcName,
                               PropertyNode::FunctionRole funcRole)
{
    priv->unresolvedPropertyMap[property].insert(funcRole, funcName);
}

/*!
  This function adds the \a node to the \a group. The group
  can be listed anywhere using the \e{annotated list} command.
 */
void Tree::addToGroup(Node *node, const QString &group)
{
    priv->groupMap.insert(group, node);
}

/*!
 */
QMultiMap<QString, Node *> Tree::groups() const
{
    return priv->groupMap;
}

/*!
 */
void Tree::addToPublicGroup(Node *node, const QString &group)
{
    priv->publicGroupMap.insert(node->name(), group);
    addToGroup(node, group);
}

/*!
 */
QMultiMap<QString, QString> Tree::publicGroups() const
{
    return priv->publicGroupMap;
}

/*!
 */
void Tree::resolveInheritance(NamespaceNode *rootNode)
{
    if (!rootNode)
        rootNode = root();

    for (int pass = 0; pass < 2; pass++) {
        NodeList::ConstIterator c = rootNode->childNodes().begin();
        while (c != rootNode->childNodes().end()) {
            if ((*c)->type() == Node::Class)
                resolveInheritance(pass, (ClassNode *) *c);
            else if ((*c)->type() == Node::Namespace) {
                NamespaceNode *ns = static_cast<NamespaceNode*>(*c);
                resolveInheritance(ns);
            }
            ++c;
        }
        if (rootNode == root())
            priv->unresolvedInheritanceMap.clear();
    }
}

/*!
 */
void Tree::resolveProperties()
{
    PropertyMap::ConstIterator propEntry;

    propEntry = priv->unresolvedPropertyMap.begin();
    while (propEntry != priv->unresolvedPropertyMap.end()) {
        PropertyNode *property = propEntry.key();
        InnerNode *parent = property->parent();
        QString getterName = (*propEntry)[PropertyNode::Getter];
        QString setterName = (*propEntry)[PropertyNode::Setter];
        QString resetterName = (*propEntry)[PropertyNode::Resetter];
        QString notifierName = (*propEntry)[PropertyNode::Notifier];

        NodeList::ConstIterator c = parent->childNodes().begin();
        while (c != parent->childNodes().end()) {
            if ((*c)->type() == Node::Function) {
                FunctionNode *function = static_cast<FunctionNode *>(*c);
                if (function->access() == property->access() &&
                    (function->status() == property->status() ||
                     function->doc().isEmpty())) {
                    if (function->name() == getterName) {
                        property->addFunction(function, PropertyNode::Getter);
                    } else if (function->name() == setterName) {
                        property->addFunction(function, PropertyNode::Setter);
                    } else if (function->name() == resetterName) {
                        property->addFunction(function, PropertyNode::Resetter);
                    } else if (function->name() == notifierName) {
                        property->addSignal(function, PropertyNode::Notifier);
                    }
                }
            }
            ++c;
        }
        ++propEntry;
    }

    propEntry = priv->unresolvedPropertyMap.begin();
    while (propEntry != priv->unresolvedPropertyMap.end()) {
        PropertyNode *property = propEntry.key();
        // redo it to set the property functions
        if (property->overriddenFrom())
            property->setOverriddenFrom(property->overriddenFrom());
        ++propEntry;
    }

    priv->unresolvedPropertyMap.clear();
}

/*!
 */
void Tree::resolveInheritance(int pass, ClassNode *classe)
{
    if (pass == 0) {
	QList<InheritanceBound> bounds = priv->unresolvedInheritanceMap[classe];
	QList<InheritanceBound>::ConstIterator b = bounds.begin();
	while (b != bounds.end()) {
	    ClassNode *baseClass = (ClassNode*)findNode((*b).basePath,
                                                        Node::Class);
            if (!baseClass && (*b).parent)
                baseClass = (ClassNode*)findNode((*b).basePath,
                                                 Node::Class,
                                                 (*b).parent);
	    if (baseClass)
		classe->addBaseClass((*b).access,
                                     baseClass,
                                     (*b).dataTypeWithTemplateArgs);
	    ++b;
	}
    }
    else {
	NodeList::ConstIterator c = classe->childNodes().begin();
	while (c != classe->childNodes().end()) {
	    if ((*c)->type() == Node::Function) {
		FunctionNode *func = (FunctionNode *) *c;
		FunctionNode *from = findVirtualFunctionInBaseClasses(classe, func);
		if (from != 0) {
		    if (func->virtualness() == FunctionNode::NonVirtual)
			func->setVirtualness(FunctionNode::ImpureVirtual);
		    func->setReimplementedFrom(from);
		}
	    }
            else if ((*c)->type() == Node::Property) {
                fixPropertyUsingBaseClasses(classe, static_cast<PropertyNode *>(*c));
            }
            ++c;
        }
    }
}

/*!
 */
void Tree::resolveGroups()
{
    GroupMap::const_iterator i;
    QString prevGroup;
    for (i = priv->groupMap.constBegin(); i != priv->groupMap.constEnd(); ++i) {
        if (i.value()->access() == Node::Private)
            continue;

        FakeNode *fake =
            static_cast<FakeNode*>(findNode(QStringList(i.key()),Node::Fake));
        if (fake && fake->subType() == Node::Group) {
            fake->addGroupMember(i.value());
        }
#if 0        
        else {
            if (prevGroup != i.key())
                i.value()->doc().location().warning(tr("No such group '%1'").arg(i.key()));
        }
#endif        

        prevGroup = i.key();
    }

    //priv->groupMap.clear();
}

/*!
 */
void Tree::resolveTargets()
{
    // need recursion

    foreach (Node *child, roo.childNodes()) {
        if (child->type() == Node::Fake) {
            FakeNode *node = static_cast<FakeNode *>(child);
            priv->fakeNodesByTitle.insert(Doc::canonicalTitle(node->title()), node);
        }

        if (child->doc().hasTableOfContents()) {
            const QList<Atom *> &toc = child->doc().tableOfContents();
            Target target;
            target.node = child;
            target.priority = 3;

            for (int i = 0; i < toc.size(); ++i) {
                target.atom = toc.at(i);
                QString title = Text::sectionHeading(target.atom).toString();
                if (!title.isEmpty())
                    priv->targetHash.insert(Doc::canonicalTitle(title), target);
            }
        }
        if (child->doc().hasKeywords()) {
            const QList<Atom *> &keywords = child->doc().keywords();
            Target target;
            target.node = child;
            target.priority = 1;

            for (int i = 0; i < keywords.size(); ++i) {
                target.atom = keywords.at(i);
                priv->targetHash.insert(Doc::canonicalTitle(target.atom->string()), target);
            }
        }
        if (child->doc().hasTargets()) {
            const QList<Atom *> &toc = child->doc().targets();
            Target target;
            target.node = child;
            target.priority = 2;

            for (int i = 0; i < toc.size(); ++i) {
                target.atom = toc.at(i);
                priv->targetHash.insert(Doc::canonicalTitle(target.atom->string()), target);
            }
        }
    }
}

/*!
 */
void Tree::fixInheritance(NamespaceNode *rootNode)
{
    if (!rootNode)
        rootNode = root();

    NodeList::ConstIterator c = rootNode->childNodes().begin();
    while (c != rootNode->childNodes().end()) {
        if ((*c)->type() == Node::Class)
            static_cast<ClassNode *>(*c)->fixBaseClasses();
        else if ((*c)->type() == Node::Namespace) {
            NamespaceNode *ns = static_cast<NamespaceNode*>(*c);
            fixInheritance(ns);
        }
        ++c;
    }
}

/*!
 */
FunctionNode *Tree::findVirtualFunctionInBaseClasses(ClassNode *classe,
                                                     FunctionNode *clone)
{
    QList<RelatedClass>::ConstIterator r = classe->baseClasses().begin();
    while (r != classe->baseClasses().end()) {
        FunctionNode *func;
        if (((func = findVirtualFunctionInBaseClasses((*r).node, clone)) != 0 ||
              (func = (*r).node->findFunctionNode(clone)) != 0)) {
            if (func->virtualness() != FunctionNode::NonVirtual)
                return func;
        }
         ++r;
    }
    return 0;
}

/*!
 */
void Tree::fixPropertyUsingBaseClasses(ClassNode *classe,
                                       PropertyNode *property)
{
    QList<RelatedClass>::const_iterator r = classe->baseClasses().begin();
    while (r != classe->baseClasses().end()) {
	PropertyNode *baseProperty =
            static_cast<PropertyNode *>(r->node->findNode(property->name(),
                                                          Node::Property));
        if (baseProperty) {
            fixPropertyUsingBaseClasses(r->node, baseProperty);
            property->setOverriddenFrom(baseProperty);
        }
        else {
            fixPropertyUsingBaseClasses(r->node, property);
        }
         ++r;
    }
}

/*!
 */
NodeList Tree::allBaseClasses(const ClassNode *classe) const
{
    NodeList result;
    foreach (const RelatedClass &r, classe->baseClasses()) {
        result += r.node;
        result += allBaseClasses(r.node);
    }
    return result;
}

/*!
 */
void Tree::readIndexes(const QStringList &indexFiles)
{
    foreach (const QString &indexFile, indexFiles)
        readIndexFile(indexFile);
}

/*!
  Read the QDomDocument at \a path and get the index from it.
 */
void Tree::readIndexFile(const QString &path)
{
    QFile file(path);
    if (file.open(QFile::ReadOnly)) {
        QDomDocument document;
        document.setContent(&file);
        file.close();

        QDomElement indexElement = document.documentElement();
        QString indexUrl = indexElement.attribute("url", "");
        priv->basesList.clear();
        priv->relatedList.clear();

        // Scan all elements in the XML file, constructing a map that contains
        // base classes for each class found.

        QDomElement child = indexElement.firstChildElement();
        while (!child.isNull()) {
            readIndexSection(child, root(), indexUrl);
            child = child.nextSiblingElement();
        }

        // Now that all the base classes have been found for this index,
        // arrange them into an inheritance hierarchy.

        resolveIndex();
    }
}

/*!
 */
void Tree::readIndexSection(const QDomElement &element,
                            InnerNode *parent,
                            const QString &indexUrl)
{
    QString name = element.attribute("name");
    QString href = element.attribute("href");

    Node *section;
    Location location;

    if (element.nodeName() == "namespace") {
        section = new NamespaceNode(parent, name);

        if (!indexUrl.isEmpty())
            location = Location(indexUrl + "/" + name.toLower() + ".html");
        else if (!indexUrl.isNull())
            location = Location(name.toLower() + ".html");

    }
    else if (element.nodeName() == "class") {
        section = new ClassNode(parent, name);
        priv->basesList.append(QPair<ClassNode*,QString>(
            static_cast<ClassNode*>(section), element.attribute("bases")));

        if (!indexUrl.isEmpty())
            location = Location(indexUrl + "/" + name.toLower() + ".html");
        else if (!indexUrl.isNull())
            location = Location(name.toLower() + ".html");

    }
    else if (element.nodeName() == "page") {
        Node::SubType subtype;
        if (element.attribute("subtype") == "example")
            subtype = Node::Example;
        else if (element.attribute("subtype") == "header")
            subtype = Node::HeaderFile;
        else if (element.attribute("subtype") == "file")
            subtype = Node::File;
        else if (element.attribute("subtype") == "group")
            subtype = Node::Group;
        else if (element.attribute("subtype") == "module")
            subtype = Node::Module;
        else if (element.attribute("subtype") == "page")
            subtype = Node::Page;
        else if (element.attribute("subtype") == "externalpage")
            subtype = Node::ExternalPage;
        else
            return;

        FakeNode *fakeNode = new FakeNode(parent, name, subtype);
        fakeNode->setTitle(element.attribute("title"));

        if (element.hasAttribute("location"))
            name = element.attribute("location", "");

        if (!indexUrl.isEmpty())
            location = Location(indexUrl + "/" + name);
        else if (!indexUrl.isNull())
            location = Location(name);

        section = fakeNode;

    }
    else if (element.nodeName() == "enum") {
        EnumNode *enumNode = new EnumNode(parent, name);

        if (!indexUrl.isEmpty())
            location =
                Location(indexUrl + "/" + parent->name().toLower() + ".html");
        else if (!indexUrl.isNull())
            location = Location(parent->name().toLower() + ".html");

        QDomElement child = element.firstChildElement("value");
        while (!child.isNull()) {
            EnumItem item(child.attribute("name"), child.attribute("value"));
            enumNode->addItem(item);
            child = child.nextSiblingElement("value");
        }

        section = enumNode;

    } else if (element.nodeName() == "typedef") {
        section = new TypedefNode(parent, name);

        if (!indexUrl.isEmpty())
            location =
                Location(indexUrl + "/" + parent->name().toLower() + ".html");
        else if (!indexUrl.isNull())
            location = Location(parent->name().toLower() + ".html");

    }
    else if (element.nodeName() == "property") {
        section = new PropertyNode(parent, name);

        if (!indexUrl.isEmpty())
            location =
                Location(indexUrl + "/" + parent->name().toLower() + ".html");
        else if (!indexUrl.isNull())
            location = Location(parent->name().toLower() + ".html");

    } else if (element.nodeName() == "function") {
        FunctionNode::Virtualness virt;
        if (element.attribute("virtual") == "non")
            virt = FunctionNode::NonVirtual;
        else if (element.attribute("virtual") == "impure")
            virt = FunctionNode::ImpureVirtual;
        else if (element.attribute("virtual") == "pure")
            virt = FunctionNode::PureVirtual;
        else
            return;

        FunctionNode::Metaness meta;
        if (element.attribute("meta") == "plain")
            meta = FunctionNode::Plain;
        else if (element.attribute("meta") == "signal")
            meta = FunctionNode::Signal;
        else if (element.attribute("meta") == "slot")
            meta = FunctionNode::Slot;
        else if (element.attribute("meta") == "constructor")
            meta = FunctionNode::Ctor;
        else if (element.attribute("meta") == "destructor")
            meta = FunctionNode::Dtor;
        else if (element.attribute("meta") == "macro")
            meta = FunctionNode::MacroWithParams;
        else if (element.attribute("meta") == "macrowithparams")
            meta = FunctionNode::MacroWithParams;
        else if (element.attribute("meta") == "macrowithoutparams")
            meta = FunctionNode::MacroWithoutParams;
        else
            return;

        FunctionNode *functionNode = new FunctionNode(parent, name);
        functionNode->setReturnType(element.attribute("return"));
        functionNode->setVirtualness(virt);
        functionNode->setMetaness(meta);
        functionNode->setConst(element.attribute("const") == "true");
        functionNode->setStatic(element.attribute("static") == "true");
        functionNode->setOverload(element.attribute("overload") == "true");

        if (element.hasAttribute("relates")
            && element.attribute("relates") != parent->name()) {
            priv->relatedList.append(
                QPair<FunctionNode*,QString>(functionNode,
                                             element.attribute("relates")));
        }

        QDomElement child = element.firstChildElement("parameter");
        while (!child.isNull()) {
            // Do not use the default value for the parameter; it is not
            // required, and has been known to cause problems.
            Parameter parameter(child.attribute("left"),
                                child.attribute("right"),
                                child.attribute("name"),
                                ""); // child.attribute("default")
            functionNode->addParameter(parameter);
            child = child.nextSiblingElement("parameter");
        }

        section = functionNode;

        if (!indexUrl.isEmpty())
            location =
                Location(indexUrl + "/" + parent->name().toLower() + ".html");
        else if (!indexUrl.isNull())
            location = Location(parent->name().toLower() + ".html");

    }
    else if (element.nodeName() == "variable") {
        section = new VariableNode(parent, name);

        if (!indexUrl.isEmpty())
            location = Location(indexUrl + "/" + parent->name().toLower() + ".html");
        else if (!indexUrl.isNull())
            location = Location(parent->name().toLower() + ".html");

    }
    else if (element.nodeName() == "keyword") {
        Target target;
        target.node = parent;
        target.priority = 1;
        target.atom = new Atom(Atom::Target, name);
        priv->targetHash.insert(name, target);
        return;

    }
    else if (element.nodeName() == "target") {
        Target target;
        target.node = parent;
        target.priority = 2;
        target.atom = new Atom(Atom::Target, name);
        priv->targetHash.insert(name, target);
        return;

    }
    else if (element.nodeName() == "contents") {
        Target target;
        target.node = parent;
        target.priority = 3;
        target.atom = new Atom(Atom::Target, name);
        priv->targetHash.insert(name, target);
        return;

    }
    else
        return;

    QString access = element.attribute("access");
    if (access == "public")
        section->setAccess(Node::Public);
    else if (access == "protected")
        section->setAccess(Node::Protected);
    else if (access == "private")
        section->setAccess(Node::Private);
    else
        section->setAccess(Node::Public);

    if (element.nodeName() != "page") {
        QString threadSafety = element.attribute("threadsafety");
        if (threadSafety == "non-reentrant")
            section->setThreadSafeness(Node::NonReentrant);
        else if (threadSafety == "reentrant")
            section->setThreadSafeness(Node::Reentrant);
        else if (threadSafety == "thread safe")
            section->setThreadSafeness(Node::ThreadSafe);
        else
            section->setThreadSafeness(Node::UnspecifiedSafeness);
    }
    else
        section->setThreadSafeness(Node::UnspecifiedSafeness);

    QString status = element.attribute("status");
    if (status == "compat")
        section->setStatus(Node::Compat);
    else if (status == "obsolete")
        section->setStatus(Node::Obsolete);
    else if (status == "deprecated")
        section->setStatus(Node::Deprecated);
    else if (status == "preliminary")
        section->setStatus(Node::Preliminary);
    else if (status == "commendable")
        section->setStatus(Node::Commendable);
    else if (status == "internal")
        section->setStatus(Node::Internal);
    else if (status == "main")
        section->setStatus(Node::Main);
    else
        section->setStatus(Node::Commendable);

    section->setModuleName(element.attribute("module"));
    if (!indexUrl.isEmpty()) {
        if (indexUrl.startsWith("."))
            section->setUrl(href);
        else
            section->setUrl(indexUrl + "/" + href);
    }

    // Create some content for the node.
    QSet<QString> emptySet;

    Doc doc(location, location, " ", emptySet); // placeholder
    section->setDoc(doc);

    if (section->isInnerNode()) {
        InnerNode *inner = static_cast<InnerNode*>(section);
        if (inner) {
            QDomElement child = element.firstChildElement();

            while (!child.isNull()) {
                if (element.nodeName() == "class")
                    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
                    readIndexSection(child, parent, indexUrl);

                child = child.nextSiblingElement();
            }
        }
    }
}

/*!
 */
QString Tree::readIndexText(const QDomElement &element)
{
    QString text;
    QDomNode child = element.firstChild();
    while (!child.isNull()) {
        if (child.isText())
            text += child.toText().nodeValue();
        child = child.nextSibling();
    }
    return text;
}

/*!
 */
void Tree::resolveIndex()
{
    QPair<ClassNode*,QString> pair;

    foreach (pair, priv->basesList) {
        foreach (const QString &base, pair.second.split(",")) {
            Node *baseClass = root()->findNode(base, Node::Class);
            if (baseClass) {
                pair.first->addBaseClass(Node::Public,
                                         static_cast<ClassNode*>(baseClass));
            }
        }
    }

    QPair<FunctionNode*,QString> relatedPair;

    foreach (relatedPair, priv->relatedList) {
        Node *classNode = root()->findNode(relatedPair.second, Node::Class);
        if (classNode)
            relatedPair.first->setRelates(static_cast<ClassNode*>(classNode));
    }
}

/*!
  Generate the index section with the given \a writer for the \a node
  specified, returning true if an element was written; otherwise returns
  false.
 */
bool Tree::generateIndexSection(QXmlStreamWriter &writer,
                                const Node *node,
                                bool generateInternalNodes) const
{
    if (!node->url().isEmpty())
        return false;

    QString nodeName;
    switch (node->type()) {
        case Node::Namespace:
            nodeName = "namespace";
            break;
        case Node::Class:
            nodeName = "class";
            break;
        case Node::Fake:
            nodeName = "page";
            break;
        case Node::Enum:
            nodeName = "enum";
            break;
        case Node::Typedef:
            nodeName = "typedef";
            break;
        case Node::Property:
            nodeName = "property";
            break;
        case Node::Function:
            nodeName = "function";
            break;
        case Node::Variable:
            nodeName = "variable";
            break;
        case Node::Target:
            nodeName = "target";
            break;
        default:
            return false;
    }

    QString access;
    switch (node->access()) {
        case Node::Public:
            access = "public";
            break;
        case Node::Protected:
            access = "protected";
            break;
        case Node::Private:
            // Do not include private non-internal nodes in the index.
            // (Internal public and protected nodes are marked as private
            // by qdoc. We can check their internal status to determine
            // whether they were really private to begin with.)
            if (node->status() == Node::Internal && generateInternalNodes)
                access = "internal";
            else
                return false;
            break;
        default:
            return false;
    }

    QString objName = node->name();

    // Special case: only the root node should have an empty name.
    if (objName.isEmpty() && node != root())
        return false;

    writer.writeStartElement(nodeName);

    QXmlStreamAttributes attributes;
    writer.writeAttribute("access", access);

    if (node->type() != Node::Fake) {
        QString threadSafety;
        switch (node->threadSafeness()) {
            case Node::NonReentrant:
                threadSafety = "non-reentrant";
                break;
            case Node::Reentrant:
                threadSafety = "reentrant";
                break;
            case Node::ThreadSafe:
                threadSafety = "thread safe";
                break;
            case Node::UnspecifiedSafeness:
            default:
                threadSafety = "unspecified";
                break;
        }
        writer.writeAttribute("threadsafety", threadSafety);
    }

    QString status;
    switch (node->status()) {
        case Node::Compat:
            status = "compat";
            break;
        case Node::Obsolete:
            status = "obsolete";
            break;
        case Node::Deprecated:
            status = "deprecated";
            break;
        case Node::Preliminary:
            status = "preliminary";
            break;
        case Node::Commendable:
            status = "commendable";
            break;
        case Node::Internal:
            status = "internal";
            break;
        case Node::Main:
        default:
            status = "main";
            break;
    }
    writer.writeAttribute("status", status);

    writer.writeAttribute("name", objName);
    QString fullName = fullDocumentName(node);
    if (fullName != objName)
        writer.writeAttribute("fullname", fullName);
    writer.writeAttribute("href", fullDocumentLocation(node));
    if (node->type() != Node::Fake)
        writer.writeAttribute("location", node->location().fileName());

    switch (node->type()) {

    case Node::Class:
        {
            // Classes contain information about their base classes.

            const ClassNode *classNode = static_cast<const ClassNode*>(node);
            QList<RelatedClass> bases = classNode->baseClasses();
            QSet<QString> baseStrings;
            foreach (const RelatedClass &related, bases) {
                ClassNode *baseClassNode = related.node;
                baseStrings.insert(baseClassNode->name());
            }
            writer.writeAttribute("bases", QStringList(baseStrings.toList()).join(","));
            writer.writeAttribute("module", node->moduleName());
        }
        break;

    case Node::Namespace:
        writer.writeAttribute("module", node->moduleName());
        break;

    case Node::Fake:
        {
            /*
              Fake nodes (such as manual pages) contain subtypes,
              titles and other attributes.
            */

            const FakeNode *fakeNode = static_cast<const FakeNode*>(node);
            switch (fakeNode->subType()) {
                case Node::Example:
                    writer.writeAttribute("subtype", "example");
                    break;
                case Node::HeaderFile:
                    writer.writeAttribute("subtype", "header");
                    break;
                case Node::File:
                    writer.writeAttribute("subtype", "file");
                    break;
                case Node::Group:
                    writer.writeAttribute("subtype", "group");
                    break;
                case Node::Module:
                    writer.writeAttribute("subtype", "module");
                    break;
                case Node::Page:
                    writer.writeAttribute("subtype", "page");
                    break;
                case Node::ExternalPage:
                    writer.writeAttribute("subtype", "externalpage");
                    break;
                default:
                    break;
            }
            writer.writeAttribute("title", fakeNode->title());
            writer.writeAttribute("fulltitle", fakeNode->fullTitle());
            writer.writeAttribute("subtitle", fakeNode->subTitle());
            writer.writeAttribute("location", fakeNode->doc().location().fileName());
        }
        break;

    case Node::Function:
        {
            /*
              Function nodes contain information about the type of
              function being described.
            */

            const FunctionNode *functionNode =
                static_cast<const FunctionNode*>(node);

            switch (functionNode->virtualness()) {
                case FunctionNode::NonVirtual:
                    writer.writeAttribute("virtual", "non");
                    break;
                case FunctionNode::ImpureVirtual:
                    writer.writeAttribute("virtual", "impure");
                    break;
                case FunctionNode::PureVirtual:
                    writer.writeAttribute("virtual", "pure");
                    break;
                default:
                    break;
            }
            switch (functionNode->metaness()) {
                case FunctionNode::Plain:
                    writer.writeAttribute("meta", "plain");
                    break;
                case FunctionNode::Signal:
                    writer.writeAttribute("meta", "signal");
                    break;
                case FunctionNode::Slot:
                    writer.writeAttribute("meta", "slot");
                    break;
                case FunctionNode::Ctor:
                    writer.writeAttribute("meta", "constructor");
                    break;
                case FunctionNode::Dtor:
                    writer.writeAttribute("meta", "destructor");
                    break;
                case FunctionNode::MacroWithParams:
                    writer.writeAttribute("meta", "macrowithparams");
                    break;
                case FunctionNode::MacroWithoutParams:
                    writer.writeAttribute("meta", "macrowithoutparams");
                    break;
                default:
                    break;
            }
            writer.writeAttribute("const", functionNode->isConst()?"true":"false");
            writer.writeAttribute("static", functionNode->isStatic()?"true":"false");
            writer.writeAttribute("overload", functionNode->isOverload()?"true":"false");
            if (functionNode->isOverload())
                writer.writeAttribute("overload-number", QString::number(functionNode->overloadNumber()));
            if (functionNode->relates())
                writer.writeAttribute("relates", functionNode->relates()->name());
            const PropertyNode *propertyNode = functionNode->associatedProperty();
            if (propertyNode)
                writer.writeAttribute("associated-property", propertyNode->name());
            writer.writeAttribute("type", functionNode->returnType());
        }
        break;

    case Node::Property:
        {
            const PropertyNode *propertyNode = static_cast<const PropertyNode*>(node);
            writer.writeAttribute("type", propertyNode->dataType());
            foreach (const Node *fnNode, propertyNode->getters()) {
                if (fnNode) {
                    const FunctionNode *functionNode = static_cast<const FunctionNode*>(fnNode);
                    writer.writeStartElement("getter");
                    writer.writeAttribute("name", functionNode->name());
                    writer.writeEndElement(); // getter
                }
            }
            foreach (const Node *fnNode, propertyNode->setters()) {
                if (fnNode) {
                    const FunctionNode *functionNode = static_cast<const FunctionNode*>(fnNode);
                    writer.writeStartElement("setter");
                    writer.writeAttribute("name", functionNode->name());
                    writer.writeEndElement(); // getter
                }
            }
            foreach (const Node *fnNode, propertyNode->resetters()) {
                if (fnNode) {
                    const FunctionNode *functionNode = static_cast<const FunctionNode*>(fnNode);
                    writer.writeStartElement("resetter");
                    writer.writeAttribute("name", functionNode->name());
                    writer.writeEndElement(); // getter
                }
            }
        }
        break;

    case Node::Variable:
        {
            const VariableNode *variableNode =
                static_cast<const VariableNode*>(node);
            writer.writeAttribute("type", variableNode->dataType());
            writer.writeAttribute("static",
                                  variableNode->isStatic() ? "true" : "false");
        }
        break;
    default:
        break;
    }

    // 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
    // element. Elements for all other nodes are closed in the opening tag.

    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::Fake) {
                const FakeNode *fakeNode = static_cast<const FakeNode *>(inner);
                if (fakeNode->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];
                int level = inner->doc().tableOfContentsLevels()[i];

                QString title = Text::sectionHeading(item).toString();
                writer.writeStartElement("contents");
                writer.writeAttribute("name", Doc::canonicalTitle(title));
                writer.writeAttribute("title", title);
                writer.writeAttribute("level", QString::number(level));
                writer.writeEndElement(); // contents
            }
        }

    }
    else if (node->type() == Node::Function) {

        const FunctionNode *functionNode = static_cast<const FunctionNode*>(node);
        // Write a signature attribute for convenience.
        QStringList signatureList;
        QStringList resolvedParameters;

        foreach (const Parameter &parameter, functionNode->parameters()) {
            QString leftType = parameter.leftType();
            const Node *leftNode =
                const_cast<Tree*>(this)->findNode(parameter.leftType().split("::"),
                Node::Typedef, 0, SearchBaseClasses|NonFunction);
            if (!leftNode) {
                leftNode = const_cast<Tree *>(this)->findNode(
                    parameter.leftType().split("::"), Node::Typedef,
                    node->parent(), SearchBaseClasses|NonFunction);
            }
            if (leftNode) {
                if (leftNode->type() == Node::Typedef) {
                    const TypedefNode *typedefNode =
                        static_cast<const TypedefNode *>(leftNode);
                    if (typedefNode->associatedEnum()) {
                        leftType = "QFlags<"+fullDocumentName(typedefNode->associatedEnum())+">";
                    }
                }
                else
                    leftType = fullDocumentName(leftNode);
            }
            resolvedParameters.append(leftType);
            signatureList.append(leftType + " " + parameter.name());
        }

        QString signature = functionNode->name()+"("+signatureList.join(", ")+")";
        if (functionNode->isConst())
            signature += " const";
        writer.writeAttribute("signature", signature);

        for (int i = 0; i < functionNode->parameters().size(); ++i) {
            Parameter parameter = functionNode->parameters()[i];
            writer.writeStartElement("parameter");
            writer.writeAttribute("left", resolvedParameters[i]);
            writer.writeAttribute("right", parameter.rightType());
            writer.writeAttribute("name", parameter.name());
            writer.writeAttribute("default", parameter.defaultValue());
            writer.writeEndElement(); // parameter
        }

    }
    else if (node->type() == Node::Enum) {

        const EnumNode *enumNode = static_cast<const EnumNode*>(node);
        if (enumNode->flagsType()) {
            writer.writeAttribute("typedef",
                fullDocumentName(enumNode->flagsType()));
        }
        foreach (const EnumItem &item, enumNode->items()) {
            writer.writeStartElement("value");
            writer.writeAttribute("name", item.name());
            writer.writeAttribute("value", item.value());
            writer.writeEndElement(); // value
        }

    }
    else if (node->type() == Node::Typedef) {

        const TypedefNode *typedefNode = static_cast<const TypedefNode*>(node);
        if (typedefNode->associatedEnum()) {
            writer.writeAttribute("enum",
                fullDocumentName(typedefNode->associatedEnum()));
        }
    }

    return true;
}

/*!
 */
void Tree::generateIndexSections(QXmlStreamWriter &writer,
                                 const Node *node,
                                 bool generateInternalNodes) const
{
    if (generateIndexSection(writer, node, generateInternalNodes)) {

        if (node->isInnerNode()) {
            const InnerNode *inner = static_cast<const InnerNode *>(node);

            // Recurse to write an element for this child node and all its children.
            foreach (const Node *child, inner->childNodes())
                generateIndexSections(writer, child, generateInternalNodes);

/*
            foreach (const Node *child, inner->relatedNodes()) {
                QDomElement childElement = generateIndexSections(document, child);
                element.appendChild(childElement);
            }
*/
        }
        writer.writeEndElement();
    }
}

/*!
  Outputs an index file.
 */
void Tree::generateIndex(const QString &fileName,
                         const QString &url,
                         const QString &title,
                         bool generateInternalNodes) const
{
    QFile file(fileName);
    if (!file.open(QFile::WriteOnly | QFile::Text))
        return ;

    QXmlStreamWriter writer(&file);
    writer.setAutoFormatting(true);
    writer.writeStartDocument();
    writer.writeDTD("<!DOCTYPE QDOCINDEX>");

    writer.writeStartElement("INDEX");
    writer.writeAttribute("url", url);
    writer.writeAttribute("title", title);
    writer.writeAttribute("version", version());

    generateIndexSections(writer, root(), generateInternalNodes);

    writer.writeEndElement(); // INDEX
    writer.writeEndElement(); // QDOCINDEX
    writer.writeEndDocument();
    file.close();
}

/*!
  Generate the tag file section with the given \a writer for the \a node
  specified, returning true if an element was written; otherwise returns
  false.
 */
void Tree::generateTagFileCompounds(QXmlStreamWriter &writer,
                                    const InnerNode *inner) const
{
    foreach (const Node *node, inner->childNodes()) {

        if (!node->url().isEmpty())
            continue;

        QString kind;
        switch (node->type()) {
            case Node::Namespace:
                kind = "namespace";
                break;
            case Node::Class:
                kind = "class";
                break;
            case Node::Enum:
            case Node::Typedef:
            case Node::Property:
            case Node::Function:
            case Node::Variable:
            case Node::Target:
            default:
                continue;
        }

        QString access;
        switch (node->access()) {
            case Node::Public:
                access = "public";
                break;
            case Node::Protected:
                access = "protected";
                break;
            case Node::Private:
            default:
                continue;
        }

        QString objName = node->name();

        // Special case: only the root node should have an empty name.
        if (objName.isEmpty() && node != root())
            continue;

        // *** Write the starting tag for the element here. ***
        writer.writeStartElement("compound");
        writer.writeAttribute("kind", kind);

        if (node->type() == Node::Class) {
            writer.writeTextElement("name", fullDocumentName(node));
            writer.writeTextElement("filename", fullDocumentLocation(node));

            // Classes contain information about their base classes.
            const ClassNode *classNode = static_cast<const ClassNode*>(node);
            QList<RelatedClass> bases = classNode->baseClasses();
            foreach (const RelatedClass &related, bases) {
                ClassNode *baseClassNode = related.node;
                writer.writeTextElement("base", baseClassNode->name());
            }

            // Recurse to write all members.
            generateTagFileMembers(writer, static_cast<const InnerNode *>(node));
            writer.writeEndElement();

            // Recurse to write all compounds.
            generateTagFileCompounds(writer, static_cast<const InnerNode *>(node));
        } else {
            writer.writeTextElement("name", fullDocumentName(node));
            writer.writeTextElement("filename", fullDocumentLocation(node));

            // Recurse to write all members.
            generateTagFileMembers(writer, static_cast<const InnerNode *>(node));
            writer.writeEndElement();

            // Recurse to write all compounds.
            generateTagFileCompounds(writer, static_cast<const InnerNode *>(node));
        }
    }
}

/*!
 */
void Tree::generateTagFileMembers(QXmlStreamWriter &writer,
                                  const InnerNode *inner) const
{
    foreach (const Node *node, inner->childNodes()) {

        if (!node->url().isEmpty())
            continue;

        QString nodeName;
        QString kind;
        switch (node->type()) {
            case Node::Enum:
                nodeName = "member";
                kind = "enum";
                break;
            case Node::Typedef:
                nodeName = "member";
                kind = "typedef";
                break;
            case Node::Property:
                nodeName = "member";
                kind = "property";
                break;
            case Node::Function:
                nodeName = "member";
                kind = "function";
                break;
            case Node::Namespace:
                nodeName = "namespace";
                break;
            case Node::Class:
                nodeName = "class";
                break;
            case Node::Variable:
            case Node::Target:
            default:
                continue;
        }

        QString access;
        switch (node->access()) {
            case Node::Public:
                access = "public";
                break;
            case Node::Protected:
                access = "protected";
                break;
            case Node::Private:
            default:
                continue;
        }

        QString objName = node->name();

        // Special case: only the root node should have an empty name.
        if (objName.isEmpty() && node != root())
            continue;

        // *** Write the starting tag for the element here. ***
        writer.writeStartElement(nodeName);
        if (!kind.isEmpty())
            writer.writeAttribute("kind", kind);

        switch (node->type()) {

        case Node::Class:
            writer.writeCharacters(fullDocumentName(node));
            writer.writeEndElement();
            break;
        case Node::Namespace:
            writer.writeCharacters(fullDocumentName(node));
            writer.writeEndElement();
            break;
        case Node::Function:
            {
                /*
                  Function nodes contain information about
                  the type of function being described.
                */

                const FunctionNode *functionNode =
                    static_cast<const FunctionNode*>(node);
                writer.writeAttribute("protection", access);

                switch (functionNode->virtualness()) {
                    case FunctionNode::NonVirtual:
                        writer.writeAttribute("virtualness", "non");
                        break;
                    case FunctionNode::ImpureVirtual:
                        writer.writeAttribute("virtualness", "virtual");
                        break;
                    case FunctionNode::PureVirtual:
                        writer.writeAttribute("virtual", "pure");
                        break;
                    default:
                        break;
                }
                writer.writeAttribute("static",
                                      functionNode->isStatic() ? "yes" : "no");

                if (functionNode->virtualness() == FunctionNode::NonVirtual)
                    writer.writeTextElement("type", functionNode->returnType());
                else
                    writer.writeTextElement("type",
                                            "virtual " + functionNode->returnType());

                writer.writeTextElement("name", objName);
                QStringList pieces = fullDocumentLocation(node).split("#");
                writer.writeTextElement("anchorfile", pieces[0]);
                writer.writeTextElement("anchor", pieces[1]);

                // Write a signature attribute for convenience.
                QStringList signatureList;

                foreach (const Parameter &parameter, functionNode->parameters()) {
                    QString leftType = parameter.leftType();
                    const Node *leftNode = const_cast<Tree *>(this)->findNode(parameter.leftType().split("::"),
                        Node::Typedef, 0, SearchBaseClasses|NonFunction);
                    if (!leftNode) {
                        leftNode = const_cast<Tree *>(this)->findNode(
                            parameter.leftType().split("::"), Node::Typedef,
                            node->parent(), SearchBaseClasses|NonFunction);
                    }
                    if (leftNode) {
                        const TypedefNode *typedefNode = static_cast<const TypedefNode *>(leftNode);
                        if (typedefNode->associatedEnum()) {
                            leftType = "QFlags<"+fullDocumentName(typedefNode->associatedEnum())+">";
                        }
                    }
                    signatureList.append(leftType + " " + parameter.name());
                }

                QString signature = "("+signatureList.join(", ")+")";
                if (functionNode->isConst())
                    signature += " const";
                if (functionNode->virtualness() == FunctionNode::PureVirtual)
                    signature += " = 0";
                writer.writeTextElement("arglist", signature);
            }
            writer.writeEndElement(); // member
            break;

        case Node::Property:
            {
                const PropertyNode *propertyNode = static_cast<const PropertyNode*>(node);
                writer.writeAttribute("type", propertyNode->dataType());
                writer.writeTextElement("name", objName);
                QStringList pieces = fullDocumentLocation(node).split("#");
                writer.writeTextElement("anchorfile", pieces[0]);
                writer.writeTextElement("anchor", pieces[1]);
                writer.writeTextElement("arglist", "");
            }
            writer.writeEndElement(); // member
            break;

        case Node::Enum:
            {
                const EnumNode *enumNode = static_cast<const EnumNode*>(node);
                writer.writeTextElement("name", objName);
                QStringList pieces = fullDocumentLocation(node).split("#");
                writer.writeTextElement("anchor", pieces[1]);
                writer.writeTextElement("arglist", "");
                writer.writeEndElement(); // member

                for (int i = 0; i < enumNode->items().size(); ++i) {
                    EnumItem item = enumNode->items().value(i);
                    writer.writeStartElement("member");
                    writer.writeAttribute("name", item.name());
                    writer.writeTextElement("anchor", pieces[1]);
                    writer.writeTextElement("arglist", "");
                    writer.writeEndElement(); // member
                }
            }
            break;

        case Node::Typedef:
            {
                const TypedefNode *typedefNode = static_cast<const TypedefNode*>(node);
                if (typedefNode->associatedEnum())
                    writer.writeAttribute("type", fullDocumentName(typedefNode->associatedEnum()));
                else
                    writer.writeAttribute("type", "");
                writer.writeTextElement("name", objName);
                QStringList pieces = fullDocumentLocation(node).split("#");
                writer.writeTextElement("anchorfile", pieces[0]);
                writer.writeTextElement("anchor", pieces[1]);
                writer.writeTextElement("arglist", "");
            }
            writer.writeEndElement(); // member
            break;

        case Node::Variable:
        case Node::Target:
        default:
            break;
        }
    }
}

/*!
 */
void Tree::generateTagFile(const QString &fileName) const
{
    QFile file(fileName);
    if (!file.open(QFile::WriteOnly | QFile::Text))
        return ;

    QXmlStreamWriter writer(&file);
    writer.setAutoFormatting(true);
    writer.writeStartDocument();

    writer.writeStartElement("tagfile");

    generateTagFileCompounds(writer, root());

    writer.writeEndElement(); // tagfile
    writer.writeEndDocument();
    file.close();
}

/*!
 */
void Tree::addExternalLink(const QString &url, const Node *relative)
{
    FakeNode *fakeNode = new FakeNode(root(), url, Node::ExternalPage);
    fakeNode->setAccess(Node::Public);

    // Create some content for the node.
    QSet<QString> emptySet;
    Location location(relative->doc().location());
    Doc doc(location, location, " ", emptySet); // placeholder
    fakeNode->setDoc(doc);
}

/*!
  Returns the full document location for HTML-based documentation.
  This should be moved into the HTML generator.
 */
QString Tree::fullDocumentLocation(const Node *node) const
{
    if (!node)
        return "";
    if (!node->url().isEmpty())
        return node->url();

    QString parentName;
    QString anchorRef;

    if (node->type() == Node::Namespace) {

        // The root namespace has no name - check for this before creating
        // an attribute containing the location of any documentation.

        if (!node->fileBase().isEmpty())
            parentName = node->fileBase() + ".html";
        else
            return "";
    }
    else if (node->type() == Node::Fake) {
#ifdef QDOC_QML
        if (node->subType() == Node::QmlClass)
            return "qml-" + node->fileBase() + ".html";
        else
#endif
        parentName = node->fileBase() + ".html";
    }
    else if (node->fileBase().isEmpty())
        return "";

    Node *parentNode = 0;

    if ((parentNode = node->relates()))
        parentName = fullDocumentLocation(node->relates());
    else if ((parentNode = node->parent()))
        parentName = fullDocumentLocation(node->parent());

    switch (node->type()) {
        case Node::Class:
        case Node::Namespace:
            if (parentNode && !parentNode->name().isEmpty())
                parentName = parentName.replace(".html", "") + "-"
                           + node->fileBase().toLower() + ".html";
            else
                parentName = node->fileBase() + ".html";
            break;
        case Node::Function:
            {
                /*
                  Functions can be destructors, overloaded, or
                  have associated properties.
                */
                const FunctionNode *functionNode =
                    static_cast<const FunctionNode *>(node);

                if (functionNode->metaness() == FunctionNode::Dtor)
                    anchorRef = "#dtor." + functionNode->name().mid(1);

                else if (functionNode->associatedProperty())
                    return fullDocumentLocation(functionNode->associatedProperty());

                else if (functionNode->overloadNumber() > 1)
                    anchorRef = "#" + functionNode->name()
                              + "-" + QString::number(functionNode->overloadNumber());
                else
                    anchorRef = "#" + functionNode->name();
            }

            /*
              Use node->name() instead of node->fileBase() as
              the latter returns the name in lower-case. For
              HTML anchors, we need to preserve the case.
            */
            break;
        case Node::Enum:
            anchorRef = "#" + node->name() + "-enum";
            break;
        case Node::Typedef:
            anchorRef = "#" + node->name() + "-typedef";
            break;
        case Node::Property:
            anchorRef = "#" + node->name() + "-prop";
            break;
        case Node::Variable:
            anchorRef = "#" + node->name() + "-var";
            break;
        case Node::Target:
            anchorRef = "#" + Doc::canonicalTitle(node->name());
            break;
        case Node::Fake:
            {
            /*
              Use node->fileBase() for fake nodes because they are represented
              by pages whose file names are lower-case.
            */
            parentName = node->fileBase();
            parentName.replace("/", "-").replace(".", "-");
            parentName += ".html";
            }
            break;
        default:
            break;
    }

    // Various objects can be compat (deprecated) or obsolete.
    if (node->type() != Node::Class && node->type() != Node::Namespace) {
        switch (node->status()) {
        case Node::Compat:
            parentName.replace(".html", "-qt3.html");
            break;
        case Node::Obsolete:
            parentName.replace(".html", "-obsolete.html");
            break;
        default:
            ;
        }
    }

    return parentName.toLower() + anchorRef;
}

/*!
 */
QString Tree::fullDocumentName(const Node *node) const
{
    if (!node)
        return "";

    QStringList pieces;
    const Node *n = node;

    do {
        if (!n->name().isEmpty())
            pieces.insert(0, n->name());

        if (n->type() == Node::Fake)
            break;

        // Examine the parent node if one exists.
        if (n->parent())
            n = n->parent();
        else
            break;
    } while (true);

    // Create a name based on the type of the ancestor node.
    if (n->type() == Node::Fake)
        return pieces.join("#");
    else
        return pieces.join("::");
}

QT_END_NAMESPACE