tools/qdoc3/webxmlgenerator.cpp
author Eckhart Koeppen <eckhart.koppen@nokia.com>
Thu, 29 Apr 2010 15:15:16 +0300
branchRCL_3
changeset 16 4b6ee5efea19
parent 4 3b1da2848fc7
permissions -rw-r--r--
2010-17 9996a03743ab23f83c83c5bc7ade0f82f71b1506

/****************************************************************************
**
** 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$
**
****************************************************************************/

/*
  webxmlgenerator.cpp
*/

#include <QtXml>

#include "codemarker.h"
#include "pagegenerator.h"
#include "webxmlgenerator.h"
#include "node.h"
#include "separator.h"
#include "tree.h"

QT_BEGIN_NAMESPACE

#define COMMAND_VERSION                 Doc::alias("version")

WebXMLGenerator::WebXMLGenerator()
    : PageGenerator()
{
}

WebXMLGenerator::~WebXMLGenerator()
{
}

void WebXMLGenerator::initializeGenerator(const Config &config)
{
    Generator::initializeGenerator(config);

    project = config.getString(CONFIG_PROJECT);

    projectDescription = config.getString(CONFIG_DESCRIPTION);
    if (projectDescription.isEmpty() && !project.isEmpty())
        projectDescription = project + " Reference Documentation";

    projectUrl = config.getString(CONFIG_URL);

    generateIndex = config.getBool(CONFIG_GENERATEINDEX);
}

void WebXMLGenerator::terminateGenerator()
{
    PageGenerator::terminateGenerator();
}

QString WebXMLGenerator::format()
{
    return "WebXML";
}

QString WebXMLGenerator::fileExtension(const Node * /* node */)
{
    return "xml";
}

void WebXMLGenerator::generateTree(const Tree *tree, CodeMarker *marker)
{
    tre = tree;
    moduleClassMap.clear();
    moduleNamespaceMap.clear();
    serviceClasses.clear();
    findAllClasses(tree->root());
    findAllNamespaces(tree->root());

    PageGenerator::generateTree(tree, marker);

    if (generateIndex)
        tre->generateIndex(outputDir() + "/" + project.toLower() + ".index",
                           projectUrl, projectDescription, false);
}

void WebXMLGenerator::startText(const Node *relative, CodeMarker *marker)
{
    inLink = false;
    inContents = false;
    inSectionHeading = false;
    numTableRows = 0;
    sectionNumber.clear();
    PageGenerator::startText(relative, marker);
}

int WebXMLGenerator::generateAtom(QXmlStreamWriter &writer, const Atom *atom,
                                  const Node *relative, CodeMarker *marker)
{
    Q_UNUSED(writer);

    int skipAhead = 0;

    switch (atom->type()) {
    default:
        PageGenerator::generateAtom(atom, relative, marker);
    }
    return skipAhead;
}

void WebXMLGenerator::generateClassLikeNode(const InnerNode *inner,
                                            CodeMarker *marker)
{
    QByteArray data;
    QXmlStreamWriter writer(&data);
    writer.setAutoFormatting(true);
    writer.writeStartDocument();
    writer.writeStartElement("WebXML");
    writer.writeStartElement("document");

    generateIndexSections(writer, inner, marker);

    writer.writeEndElement(); // document
    writer.writeEndElement(); // WebXML
    writer.writeEndDocument();

    out() << data;
    out().flush();
}

void WebXMLGenerator::generateFakeNode(const FakeNode *fake, CodeMarker *marker)
{
    QByteArray data;
    QXmlStreamWriter writer(&data);
    writer.setAutoFormatting(true);
    writer.writeStartDocument();
    writer.writeStartElement("WebXML");
    writer.writeStartElement("document");

    generateIndexSections(writer, fake, marker);

    writer.writeEndElement(); // document
    writer.writeEndElement(); // WebXML
    writer.writeEndDocument();

    out() << data;
    out().flush();
}

void WebXMLGenerator::generateIndexSections(QXmlStreamWriter &writer,
                                 const Node *node, CodeMarker *marker)
{
    if (tre->generateIndexSection(writer, node, true)) {

        // Add documentation to this node if it exists.
        writer.writeStartElement("description");
        writer.writeAttribute("path", node->doc().location().filePath());
        writer.writeAttribute("line", QString::number(node->doc().location().lineNo()));
        writer.writeAttribute("column", QString::number(node->doc().location().columnNo()));

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

            const FakeNode *fake = static_cast<const FakeNode *>(node);

            generateRelations(writer, node, marker);

            if (fake->subType() == Node::Module) {
                writer.writeStartElement("generatedlist");
                writer.writeAttribute("contents", "classesbymodule");

                if (moduleNamespaceMap.contains(fake->name())) {
                    writer.writeStartElement("section");
                    writer.writeStartElement("heading");
                    writer.writeAttribute("level", "1");
                    writer.writeCharacters("Namespaces");
                    writer.writeEndElement(); // heading
                    generateAnnotatedList(writer, fake, marker, moduleNamespaceMap[fake->name()]);
                    writer.writeEndElement(); // section
                }
                if (moduleClassMap.contains(fake->name())) {
                    writer.writeStartElement("section");
                    writer.writeStartElement("heading");
                    writer.writeAttribute("level", "1");
                    writer.writeCharacters("Classes");
                    writer.writeEndElement(); // heading
                    generateAnnotatedList(writer, fake, marker, moduleClassMap[fake->name()]);
                    writer.writeEndElement(); // section
                }

                writer.writeEndElement(); // generatedlist
            }
        }

        startText(node, marker);

        const Atom *atom = node->doc().body().firstAtom();
        while (atom)
            atom = addAtomElements(writer, atom, node, marker);

        QList<Text> alsoList = node->doc().alsoList();
        supplementAlsoList(node, alsoList);

        if (!alsoList.isEmpty()) {
            writer.writeStartElement("see-also");
            for (int i = 0; i < alsoList.size(); ++i) {
                const Atom *atom = alsoList.at(i).firstAtom();
                while (atom)
                    atom = addAtomElements(writer, atom, node, marker);
            }
            writer.writeEndElement(); // see-also
        }

        writer.writeEndElement(); // description

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

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

            writer.writeStartElement("related");
            if (inner->relatedNodes().size() > 0) {
                foreach (const Node *child, inner->relatedNodes())
                    generateIndexSections(writer, child, marker);
            }
            writer.writeEndElement(); // related
        }
        writer.writeEndElement();
    }
}

void WebXMLGenerator::generateInnerNode(const InnerNode *node, CodeMarker *marker)
{
    if (!node->url().isNull())
        return;

    if (node->type() == Node::Fake) {
        const FakeNode *fakeNode = static_cast<const FakeNode *>(node);
        if (fakeNode->subType() == Node::ExternalPage)
            return;
    }

    if ( node->parent() != 0 ) {
	beginSubPage( node->location(), fileName(node) );
	if ( node->type() == Node::Namespace || node->type() == Node::Class) {
	    generateClassLikeNode(node, marker);
	} else if ( node->type() == Node::Fake ) {
	    generateFakeNode(static_cast<const FakeNode *>(node), marker);
	}
	endSubPage();
    }

    NodeList::ConstIterator c = node->childNodes().begin();
    while ( c != node->childNodes().end() ) {
	if ((*c)->isInnerNode() && (
            (*c)->access() != Node::Private || (*c)->status() == Node::Internal))
	    generateInnerNode( (const InnerNode *) *c, marker );
	++c;
    }
}

const Atom *WebXMLGenerator::addAtomElements(QXmlStreamWriter &writer,
     const Atom *atom, const Node *relative, CodeMarker *marker)
{
    switch (atom->type()) {
    case Atom::AbstractLeft:
    case Atom::AbstractRight:
        break;
    case Atom::AutoLink:
        if (!inLink && !inSectionHeading) {
            const Node *node = findNode(atom, relative, marker);
            if (node) {
                startLink(writer, atom, node, relative);
                if (inLink) {
                    writer.writeCharacters(atom->string());
                    writer.writeEndElement(); // link
                    inLink = false;
                }
            } else
                writer.writeCharacters(atom->string());
        } else
            writer.writeCharacters(atom->string());
        break;
    case Atom::BaseName:
        break;
    case Atom::BriefLeft:

        writer.writeStartElement("brief");
        switch (relative->type()) {
        case Node::Property:
            writer.writeCharacters("This property");
            break;
        case Node::Variable:
            writer.writeCharacters("This variable");
            break;
        default:
            break;
        }
        if (relative->type() == Node::Property || relative->type() == Node::Variable) {
            QString str;
            const Atom *a = atom->next();
            while (a != 0 && a->type() != Atom::BriefRight) {
                if (a->type() == Atom::String || a->type() == Atom::AutoLink)
                    str += a->string();
                a = a->next();
            }
            str[0] = str[0].toLower();
            if (str.right(1) == ".")
                str.chop(1);

            QStringList words = str.split(" ");
            if (!(words.first() == "contains" || words.first() == "specifies"
                || words.first() == "describes" || words.first() == "defines"
                || words.first() == "holds" || words.first() == "determines"))
                writer.writeCharacters(" holds ");
            else
                writer.writeCharacters(" ");
        }
        break;

    case Atom::BriefRight:
        if (relative->type() == Node::Property || relative->type() == Node::Variable)
            writer.writeCharacters(".");

        writer.writeEndElement(); // brief
        break;

    case Atom::C:
        writer.writeStartElement("teletype");
        if (inLink)
            writer.writeAttribute("type", "normal");
        else
            writer.writeAttribute("type", "highlighted");

        writer.writeCharacters(plainCode(atom->string()));
        writer.writeEndElement(); // teletype
        break;

    case Atom::Code:
        writer.writeTextElement("code", trimmedTrailing(plainCode(atom->string())));
        break;

#ifdef QDOC_QML        
    case Atom::Qml:
        writer.writeTextElement("qml", trimmedTrailing(plainCode(atom->string())));
#endif
        
    case Atom::CodeBad:
        writer.writeTextElement("badcode", trimmedTrailing(plainCode(atom->string())));
        break;

    case Atom::CodeNew:
        writer.writeTextElement("para", "you can rewrite it as");
        writer.writeTextElement("newcode", trimmedTrailing(plainCode(atom->string())));
        break;

    case Atom::CodeOld:
        writer.writeTextElement("para", "For example, if you have code like");
        writer.writeTextElement("oldcode", trimmedTrailing(plainCode(atom->string())));
        break;

    case Atom::CodeQuoteArgument:
        if (quoteCommand == "dots") {
            writer.writeAttribute("indent", atom->string());
            writer.writeCharacters("...");
        } else
            writer.writeCharacters(atom->string());
        writer.writeEndElement(); // code
        break;

    case Atom::CodeQuoteCommand:
        quoteCommand = atom->string();
        writer.writeStartElement(quoteCommand);
        break;

    case Atom::FootnoteLeft:
        writer.writeStartElement("footnote");
        break;

    case Atom::FootnoteRight:
        writer.writeEndElement(); // footnote
        break;
/*
    case Atom::FormatElse:
        writer.writeStartElement("else");
        writer.writeEndElement(); // else
        break;
*/
    case Atom::FormatEndif:
        writer.writeEndElement(); // raw
        break;
    case Atom::FormatIf:
        writer.writeStartElement("raw");
        writer.writeAttribute("format", atom->string());
        break;
    case Atom::FormattingLeft:
	{
            if (atom->string() == ATOM_FORMATTING_BOLD)
                writer.writeStartElement("bold");
	    else if (atom->string() == ATOM_FORMATTING_ITALIC)
                writer.writeStartElement("italic");
	    else if (atom->string() == ATOM_FORMATTING_UNDERLINE)
                writer.writeStartElement("underline");
	    else if (atom->string() == ATOM_FORMATTING_SUBSCRIPT)
                writer.writeStartElement("subscript");
	    else if (atom->string() == ATOM_FORMATTING_SUPERSCRIPT)
                writer.writeStartElement("superscript");
	    else if (atom->string() == ATOM_FORMATTING_TELETYPE)
                writer.writeStartElement("teletype");
	    else if (atom->string() == ATOM_FORMATTING_PARAMETER)
                writer.writeStartElement("argument");
	    else if (atom->string() == ATOM_FORMATTING_INDEX)
                writer.writeStartElement("index");
        }
        break;
/*        out() << formattingLeftMap()[atom->string()];
        if ( atom->string() == ATOM_FORMATTING_PARAMETER ) {
            if ( atom->next() != 0 && atom->next()->type() == Atom::String ) {
                QRegExp subscriptRegExp( "([a-z]+)_([0-9n])" );
                if ( subscriptRegExp.exactMatch(atom->next()->string()) ) {
                    out() << subscriptRegExp.cap( 1 ) << "<sub>"
                          << subscriptRegExp.cap( 2 ) << "</sub>";
                    skipAhead = 1;
                }
            }
        }*/
    case Atom::FormattingRight:
	{
            if (atom->string() == ATOM_FORMATTING_BOLD)
                writer.writeEndElement();
	    else if (atom->string() == ATOM_FORMATTING_ITALIC)
                writer.writeEndElement();
	    else if (atom->string() == ATOM_FORMATTING_UNDERLINE)
                writer.writeEndElement();
	    else if (atom->string() == ATOM_FORMATTING_SUBSCRIPT)
                writer.writeEndElement();
	    else if (atom->string() == ATOM_FORMATTING_SUPERSCRIPT)
                writer.writeEndElement();
	    else if (atom->string() == ATOM_FORMATTING_TELETYPE)
                writer.writeEndElement();
	    else if (atom->string() == ATOM_FORMATTING_PARAMETER)
                writer.writeEndElement();
	    else if (atom->string() == ATOM_FORMATTING_INDEX)
                writer.writeEndElement();
        }
        if (inLink) {
            writer.writeEndElement(); // link
            inLink = false;
        }
	break;
/*        if ( atom->string() == ATOM_FORMATTING_LINK ) {
            if (inLink) {
                if ( link.isEmpty() ) {
                    if (showBrokenLinks)
                        out() << "</i>";
                } else {
                    out() << "</a>";
                }
            }
            inLink = false;
        } else {
            out() << formattingRightMap()[atom->string()];
        }*/
    case Atom::GeneratedList:
        writer.writeStartElement("generatedlist");
        writer.writeAttribute("contents", atom->string());
        writer.writeEndElement(); // generatedlist
/*
        if (atom->string() == "annotatedclasses") {
            generateAnnotatedList(relative, marker, nonCompatClasses);
        } else if (atom->string() == "classes") {
            generateCompactList(relative, marker, nonCompatClasses);
        } else if (atom->string().contains("classesbymodule")) {
            QString arg = atom->string().trimmed();
            QString moduleName = atom->string().mid(atom->string().indexOf(
                "classesbymodule") + 15).trimmed();
            if (moduleClassMap.contains(moduleName))
                generateAnnotatedList(relative, marker, moduleClassMap[moduleName]);
        } else if (atom->string().contains("classesbyedition")) {
            QString arg = atom->string().trimmed();
            QString editionName = atom->string().mid(atom->string().indexOf(
                "classesbyedition") + 16).trimmed();
            if (editionModuleMap.contains(editionName)) {
                QMap<QString, const Node *> editionClasses;
                foreach (const QString &moduleName, editionModuleMap[editionName]) {
                    if (moduleClassMap.contains(moduleName))
                        editionClasses.unite(moduleClassMap[moduleName]);
                }
                generateAnnotatedList(relative, marker, editionClasses);
            }
        } else if (atom->string() == "classhierarchy") {
            generateClassHierarchy(relative, marker, nonCompatClasses);
        } else if (atom->string() == "compatclasses") {
            generateCompactList(relative, marker, compatClasses);
        } else if (atom->string() == "functionindex") {
            generateFunctionIndex(relative, marker);
        } else if (atom->string() == "legalese") {
            generateLegaleseList(relative, marker);
        } else if (atom->string() == "mainclasses") {
            generateCompactList(relative, marker, mainClasses);
        } else if (atom->string() == "services") {
            generateCompactList(relative, marker, serviceClasses);
        } else if (atom->string() == "overviews") {
            generateOverviewList(relative, marker);
        } else if (atom->string() == "namespaces") {
            generateAnnotatedList(relative, marker, namespaceIndex);
        } else if (atom->string() == "related") {
            const FakeNode *fake = static_cast<const FakeNode *>(relative);
            if (fake && !fake->groupMembers().isEmpty()) {
                QMap<QString, const Node *> groupMembersMap;
                foreach (Node *node, fake->groupMembers()) {
                    if (node->type() == Node::Fake)
                        groupMembersMap[fullName(node, relative, marker)] = node;
                }
                generateAnnotatedList(fake, marker, groupMembersMap);
            }
        } else if (atom->string() == "relatedinline") {
            const FakeNode *fake = static_cast<const FakeNode *>(relative);
            if (fake && !fake->groupMembers().isEmpty()) {
                // Reverse the list into the original scan order.
                // Should be sorted.  But on what?  It may not be a
                // regular class or page definition.
                QList<const Node *> list;
                foreach (const Node *node, fake->groupMembers())
                    list.prepend(node);
                foreach (const Node *node, list)
                    generateBody(node, marker );
            }
        }
        break;
*/
        break;
    case Atom::Image:
        writer.writeStartElement("image");
        writer.writeAttribute("href", imageFileName(relative, atom->string()));
        writer.writeEndElement(); // image
        break;

    case Atom::InlineImage:
        writer.writeStartElement("inlineimage");
        writer.writeAttribute("href", imageFileName(relative, atom->string()));
        writer.writeEndElement(); // inlineimage
        break;

    case Atom::ImageText:
        break;

    case Atom::LegaleseLeft:
        writer.writeStartElement("legalese");
        break;

    case Atom::LegaleseRight:
        writer.writeEndElement(); // legalese
        break;

    case Atom::Link:
    case Atom::LinkNode:
        if (!inLink) {
            const Node *node = findNode(atom, relative, marker);
            if (node)
                startLink(writer, atom, node, relative);
        }
        break;

    case Atom::ListLeft:
        writer.writeStartElement("list");

        if (atom->string() == ATOM_LIST_BULLET)
            writer.writeAttribute("type", "bullet");
        else if (atom->string() == ATOM_LIST_TAG)
            writer.writeAttribute("type", "definition");
        else if (atom->string() == ATOM_LIST_VALUE)
            writer.writeAttribute("type", "enum");
        else {
            writer.writeAttribute("type", "ordered");
            if (atom->string() == ATOM_LIST_UPPERALPHA)
                writer.writeAttribute("start", "A");
            else if (atom->string() == ATOM_LIST_LOWERALPHA)
                writer.writeAttribute("start", "a");
            else if (atom->string() == ATOM_LIST_UPPERROMAN)
                writer.writeAttribute("start", "I");
            else if (atom->string() == ATOM_LIST_LOWERROMAN)
                writer.writeAttribute("start", "i");
            else // (atom->string() == ATOM_LIST_NUMERIC)
                writer.writeAttribute("start", "1");
        }
        break;

    case Atom::ListItemNumber:
        break;

    case Atom::ListTagLeft:
        {
            writer.writeStartElement("definition");

            writer.writeTextElement("term", plainCode(
                marker->markedUpEnumValue(atom->next()->string(), relative)));
        }
        break;

    case Atom::ListTagRight:
        writer.writeEndElement(); // definition
        break;

    case Atom::ListItemLeft:
        writer.writeStartElement("item");
        break;

    case Atom::ListItemRight:
        writer.writeEndElement(); // item
        break;

    case Atom::ListRight:
        writer.writeEndElement(); // list
        break;

    case Atom::Nop:
        break;

    case Atom::ParaLeft:
        writer.writeStartElement("para");
        break;

    case Atom::ParaRight:
        writer.writeEndElement(); // para
        break;

    case Atom::QuotationLeft:
        writer.writeStartElement("quote");
        break;

    case Atom::QuotationRight:
        writer.writeEndElement(); // quote
        break;

    case Atom::RawString:
        writer.writeCharacters(atom->string());
        break;

    case Atom::SectionLeft:
        writer.writeStartElement("section");
        writer.writeAttribute("id", Doc::canonicalTitle(Text::sectionHeading(atom).toString()));
        break;

    case Atom::SectionRight:
        writer.writeEndElement(); // section
        break;

    case Atom::SectionHeadingLeft:
        writer.writeStartElement("heading");
        writer.writeAttribute("level", atom->string()); // + hOffset(relative)
        inSectionHeading = true;
        break;

    case Atom::SectionHeadingRight:
        writer.writeEndElement(); // heading
        inSectionHeading = false;
        break;

    case Atom::SidebarLeft:
    case Atom::SidebarRight:
        break;

    case Atom::SnippetCommand:
        writer.writeStartElement(atom->string());
        break;

    case Atom::SnippetIdentifier:
        writer.writeAttribute("identifier", atom->string());
        writer.writeEndElement(); // snippet
        break;

    case Atom::SnippetLocation:
        writer.writeAttribute("location", atom->string());
        break;

    case Atom::String:
        writer.writeCharacters(atom->string());
        break;

    case Atom::TableLeft:
        writer.writeStartElement("table");
        if (atom->string().contains("%"))
            writer.writeAttribute("width", atom->string());
        break;

    case Atom::TableRight:
        writer.writeEndElement(); // table
        break;

    case Atom::TableHeaderLeft:
        writer.writeStartElement("header");
        break;

    case Atom::TableHeaderRight:
        writer.writeEndElement(); // header
        break;

    case Atom::TableRowLeft:
        writer.writeStartElement("row");
        break;

    case Atom::TableRowRight:
        writer.writeEndElement(); // row
        break;

    case Atom::TableItemLeft:
        {
            writer.writeStartElement("item");
            QStringList spans = atom->string().split(",");
            if (spans.size() == 2) {
                if (spans.at(0) != "1")
                    writer.writeAttribute("colspan", spans.at(0).trimmed());
                if (spans.at(1) != "1")
                    writer.writeAttribute("rowspan", spans.at(1).trimmed());
            }
        }
        break;

    case Atom::TableItemRight:
        writer.writeEndElement(); // item
        break;

    case Atom::TableOfContents:
        writer.writeStartElement("tableofcontents");
        writer.writeAttribute("details", atom->string());
        {
            int numColumns = 1;
            const Node *node = relative;

            Doc::SectioningUnit sectioningUnit = Doc::Section4;
            QStringList params = atom->string().split(",");
            QString columnText = params.at(0);
            QStringList pieces = columnText.split(" ", QString::SkipEmptyParts);
            if (pieces.size() >= 2) {
                columnText = pieces.at(0);
                pieces.pop_front();
                QString path = pieces.join(" ").trimmed();
                node = findNode(path, relative, marker);
                if (node)
                    writer.writeAttribute("href", fileName(node));
            }

            if (params.size() == 2) {
                numColumns = qMax(columnText.toInt(), numColumns);
                sectioningUnit = (Doc::SectioningUnit)params.at(1).toInt();
                writer.writeAttribute("columns", QString::number(numColumns));
                writer.writeAttribute("unit", QString::number(sectioningUnit));
            }

            if (node)
                generateTableOfContents(writer, node, sectioningUnit, numColumns,
                                        relative);
        }
        writer.writeEndElement(); // tableofcontents
        break;

    case Atom::Target:
        writer.writeStartElement("target");
        writer.writeAttribute("name", Doc::canonicalTitle(atom->string()));
        writer.writeEndElement(); // target
        break;

    case Atom::UnhandledFormat:
    case Atom::UnknownCommand:
        writer.writeCharacters(atom->typeString());
        break;
    default:
        break;
    }

    if (atom)
        return atom->next();

    return 0;
}
/*
        QDomElement atomElement = document.createElement(atom->typeString().toLower());
        QDomText atomValue = document.createTextNode(atom->string());
        atomElement.appendChild(atomValue);
        descriptionElement.appendChild(atomElement);
*/

/*
    ### Warning: findNode() is a modified version of HtmlGenerator::getLink().
*/
const Node *WebXMLGenerator::findNode(const Atom *atom, const Node *relative, CodeMarker *marker)
{
    return findNode(atom->string(), relative, marker);
}

const Node *WebXMLGenerator::findNode(const QString &title, const Node *relative, CodeMarker *marker)
{
    QString link;
    if (title.contains(":") &&
            (title.startsWith("file:")
             || title.startsWith("http:")
             || title.startsWith("https:")
             || title.startsWith("ftp:")
             || title.startsWith("mailto:"))) {

        return 0;
    } else if (title.count('@') == 1) {
        return 0;
    } else {
        QStringList path;
        if (title.contains('#')) {
            path = title.split('#');
        } else {
            path.append(title);
        }

        const Node *node = 0;
        Atom *targetAtom = 0;

        QString first = path.first().trimmed();
        if (first.isEmpty()) {
            node = relative;
        } else if (first.endsWith(".html")) {
            node = tre->root()->findNode(first, Node::Fake);
        } else {
            node = marker->resolveTarget(first, tre, relative);
            if (!node)
                node = tre->findFakeNodeByTitle(first);
            if (!node)
                node = tre->findUnambiguousTarget(first, targetAtom);
        }

        if (node) {
            if (!node->url().isEmpty())
                return node;
            else
                path.removeFirst();
        } else {
            return 0;
        }

        while (!path.isEmpty()) {
            targetAtom = tre->findTarget(path.first(), node);
            if (targetAtom == 0)
                break;
            path.removeFirst();
        }
/* We would ideally treat targets as nodes to be consistent.
        if (targetAtom && node && node->isInnerNode()) {
            Node *parentNode = const_cast<Node *>(node);
            node = new TargetNode(static_cast<InnerNode*>(parentNode), first);
        }
*/
        return node;
    }
    return 0;
}

void WebXMLGenerator::startLink(QXmlStreamWriter &writer, const Atom *atom,
                                const Node *node, const Node *relative)
{
    QString location = tre->fullDocumentLocation(node);
    if (!location.isEmpty()) {
        writer.writeStartElement("link");
        writer.writeAttribute("raw", atom->string());
        if (atom->string().contains("#") || node == relative) {
            QString target = atom->string().split("#").last();
            Atom *targetAtom = tre->findTarget(target, node);
            if (targetAtom)
                location += "#" + Doc::canonicalTitle(target);
        }
        writer.writeAttribute("href", location);
        QString type = targetType(node);
        writer.writeAttribute("type", type);
        switch (node->type()) {
        case Node::Enum:
            writer.writeAttribute("enum", tre->fullDocumentName(node));
            break;
        case Node::Fake:
            writer.writeAttribute("page", tre->fullDocumentName(node));
            break;
        case Node::Property:
        {
            const PropertyNode *propertyNode = static_cast<const PropertyNode *>(node);
            if (propertyNode->getters().size() > 0)
                writer.writeAttribute("getter", tre->fullDocumentName(propertyNode->getters()[0]));
        }
        default:
            ;
        }
        inLink = true;
    }
}

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

void WebXMLGenerator::generateRelations(QXmlStreamWriter &writer, const Node *node, CodeMarker *marker)
{
    if (node && !node->links().empty()) {
        QPair<QString,QString> linkPair;
        QPair<QString,QString> anchorPair;
        const Node *linkNode;

        foreach (Node::LinkType relation, node->links().keys()) {

            linkPair = node->links()[relation];
            linkNode = findNode(linkPair.first, node, marker);
            
            if (!linkNode)
                linkNode = node;

            if (linkNode == node)
                anchorPair = linkPair;
            else
                anchorPair = anchorForNode(linkNode);

            writer.writeStartElement("relation");
            writer.writeAttribute("href", anchorPair.first);
            writer.writeAttribute("type", targetType(linkNode));
            
            switch (relation) {
            case Node::StartLink:
                writer.writeAttribute("meta", "start");
                break;
            case Node::NextLink:
                writer.writeAttribute("meta", "next");
                break;
            case Node::PreviousLink:
                writer.writeAttribute("meta", "previous");
                break;
            case Node::ContentsLink: 
                writer.writeAttribute("meta", "contents");
                break;
            case Node::IndexLink:
                writer.writeAttribute("meta", "index");
                break;
            default:
                writer.writeAttribute("meta", "");
            }
            writer.writeAttribute("description", anchorPair.second);
            writer.writeEndElement(); // link
        }
    }
}

// Classes adapted from HtmlGenerator.

void WebXMLGenerator::generateTableOfContents(QXmlStreamWriter &writer, const Node *node,
                                              Doc::SectioningUnit sectioningUnit,
                                              int numColumns, const Node *relative)

{
    if (!node->doc().hasTableOfContents())
        return;
    QList<Atom *> toc = node->doc().tableOfContents();
    if (toc.isEmpty())
        return;

    QString nodeName = "";
    if (node != relative)
        nodeName = node->name();

    QStringList sectionNumber;
    int columnSize = 0;

    if (numColumns > 1) {
        writer.writeStartElement("table");
        writer.writeAttribute("width", "100%");
        writer.writeStartElement("row");
        writer.writeStartElement("item");
        writer.writeAttribute("width", QString::number((100 + numColumns - 1) / numColumns) + "%");
    }

    // disable nested links in table of contents
    inContents = true;
    inLink = true;

    for (int i = 0; i < toc.size(); ++i) {
        Atom *atom = toc.at(i);

        int nextLevel = atom->string().toInt();
        if (nextLevel > (int)sectioningUnit)
            continue;

        if (sectionNumber.size() < nextLevel) {
            do {
                writer.writeStartElement("list");
                sectionNumber.append("1");
            } while (sectionNumber.size() < nextLevel);
        } else {
            while (sectionNumber.size() > nextLevel) {
                writer.writeEndElement();
                sectionNumber.removeLast();
            }
            sectionNumber.last() = QString::number(sectionNumber.last().toInt() + 1);
        }
        Text headingText = Text::sectionHeading(atom);

        if (sectionNumber.size() == 1 && columnSize > toc.size() / numColumns) {
            writer.writeEndElement(); // list
            writer.writeEndElement(); // item
            writer.writeStartElement("item");
            writer.writeAttribute("width", QString::number((100 + numColumns - 1) / numColumns) + "%");
            writer.writeStartElement("list");
            columnSize = 0;
        }

        writer.writeStartElement("item");
        writer.writeStartElement("para");
        writer.writeStartElement("link");
        writer.writeAttribute("href", nodeName + "#" + Doc::canonicalTitle(headingText.toString()));
        writer.writeAttribute("type", "page");
        writer.writeCharacters(headingText.toString());
        writer.writeEndElement(); // link
        writer.writeEndElement(); // para
        writer.writeEndElement(); // item

        ++columnSize;
    }
    while (!sectionNumber.isEmpty()) {
        writer.writeEndElement(); // list
        sectionNumber.removeLast();
    }

    if (numColumns > 1) {
        writer.writeEndElement(); // item
        writer.writeEndElement(); // row
        writer.writeEndElement(); // table
    }

    inContents = false;
    inLink = false;
}

void WebXMLGenerator::generateAnnotatedList(QXmlStreamWriter &writer,
    const Node *relative, CodeMarker *marker, const QMap<QString, const Node *> &nodeMap)
{
    writer.writeStartElement("table");
    writer.writeAttribute("width", "100%");

    foreach (QString name, nodeMap.keys()) {
        const Node *node = nodeMap[name];

        writer.writeStartElement("row");
        writer.writeStartElement("heading");
        generateFullName(writer, node, relative, marker);
        writer.writeEndElement(); // heading

        writer.writeStartElement("item");
        writer.writeCharacters(node->doc().briefText().toString());
        writer.writeEndElement(); // item
        writer.writeEndElement(); // row
    }
    writer.writeEndElement(); // table
}

void WebXMLGenerator::generateFullName(QXmlStreamWriter &writer,
    const Node *apparentNode, const Node *relative, CodeMarker *marker,
    const Node *actualNode)
{
    if ( actualNode == 0 )
        actualNode = apparentNode;
    writer.writeStartElement("link");
    writer.writeAttribute("href", tre->fullDocumentLocation(actualNode));
    writer.writeAttribute("type", targetType(actualNode));
    writer.writeCharacters(fullName(apparentNode, relative, marker));
    writer.writeEndElement(); // link
}

// Classes copied (and slightly adapted) from the HtmlGenerator. These need
// refactoring into a common ancestor class.

void WebXMLGenerator::findAllClasses(const InnerNode *node)
{
    NodeList::const_iterator c = node->childNodes().constBegin();
    while (c != node->childNodes().constEnd()) {
        if ((*c)->access() != Node::Private && (*c)->url().isEmpty()) {
            if ((*c)->type() == Node::Class && !(*c)->doc().isEmpty()) {
                QString className = (*c)->name();
                if ((*c)->parent() && (*c)->parent()->type() == Node::Namespace &&
                    !(*c)->parent()->name().isEmpty())
                    className = (*c)->parent()->name()+"::"+className;

                QString moduleName = (*c)->moduleName();
                if (!moduleName.isEmpty())
                    moduleClassMap[moduleName].insert((*c)->name(), *c);

                QString serviceName =
                    (static_cast<const ClassNode *>(*c))->serviceName();
                if (!serviceName.isEmpty())
                    serviceClasses.insert(serviceName, *c);
            } else if ((*c)->isInnerNode()) {
                findAllClasses(static_cast<InnerNode *>(*c));
            }
        }
        ++c;
    }
}

void WebXMLGenerator::findAllNamespaces(const InnerNode *node)
{
    NodeList::ConstIterator c = node->childNodes().begin();
    while (c != node->childNodes().end()) {
        if ((*c)->access() != Node::Private) {
            if ((*c)->isInnerNode() && (*c)->url().isEmpty()) {
                findAllNamespaces(static_cast<const InnerNode *>(*c));
                if ((*c)->type() == Node::Namespace) {
                    const NamespaceNode *nspace = static_cast<const NamespaceNode *>(*c);
                    // Ensure that the namespace's name is not empty (the root
                    // namespace has no name).
                    if (!nspace->name().isEmpty()) {
                        namespaceIndex.insert(nspace->name(), *c);
                        QString moduleName = (*c)->moduleName();
                        if (!moduleName.isEmpty())
                            moduleNamespaceMap[moduleName].insert((*c)->name(), *c);
                    }
                }
            }
        }
        ++c;
    }
}

const QPair<QString,QString> WebXMLGenerator::anchorForNode(const Node *node)
{
    QPair<QString,QString> anchorPair;

    anchorPair.first = PageGenerator::fileName(node);
    if (node->type() == Node::Fake) {
        const FakeNode *fakeNode = static_cast<const FakeNode*>(node);
        anchorPair.second = fakeNode->title();
    }

    return anchorPair;
}

QT_END_NAMESPACE