/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- 
 *
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code is TransforMiiX XSLT processor.
 *
 * The Initial Developer of the Original Code is The MITRE Corporation.
 * Portions created by MITRE are Copyright (C) 1999 The MITRE Corporation.
 *
 * Portions created by Keith Visco as a Non MITRE employee,
 * (C) 1999, 2000 Keith Visco. All Rights Reserved.
 *
 * Contributor(s):
 * Keith Visco, kvisco@ziplink.net
 *    -- original author.
 *
 * Bob Miller, kbob@oblix.com
 *    -- plugged core leak.
 *
 * Pierre Phaneuf, pp@ludusdesign.com
 *    -- fixed some XPCOM usage.
 *
 * Marina Mechtcheriakova, mmarina@mindspring.com
 *    -- Added call to recurisvely attribute-set processing on
 *       xsl:attribute-set itself
 *    -- Added call to handle attribute-set processing for xsl:copy
 *
 * Nathan Pride, npride@wavo.com
 *    -- fixed a document base issue
 *
 * Olivier Gerardin
 *    -- Changed behavior of passing parameters to templates
 *
 */

#include "XSLTProcessor.h"
#include "txAtoms.h"
#include "TxLog.h"
#include "txNodeSetContext.h"
#include "txNodeSorter.h"
#include "txRtfHandler.h"
#include "txStack.h"
#include "txStringUtils.h"
#include "txTextHandler.h"
#include "txTokenizer.h"
#include "txURIUtils.h"
#include "txVariableMap.h"
#include "txXSLTNumber.h"
#include "XMLDOMUtils.h"
#include "XMLUtils.h"

#ifdef TX_EXE
#include "txHTMLOutput.h"
#else
#include "nsContentCID.h"
#include "nsIConsoleService.h"
#include "nsIDOMDocument.h"
#include "nsIServiceManagerUtils.h"

static NS_DEFINE_CID(kXMLDocumentCID, NS_XMLDOCUMENT_CID);
#endif

Expr* txXSLTProcessor::gNodeExpr = 0;

/*
 * Implement static variables for atomservice and dom.
 */
#ifdef TX_EXE
TX_IMPL_DOM_STATICS;
#endif

TX_LG_IMPL;

/* static */
MBool
txXSLTProcessor::txInit()
{
    TX_LG_CREATE;

    // Create default expressions
    // "node()"
    txNodeTest* nt = new txNodeTypeTest(txNodeTypeTest::NODE_TYPE);
    if (!nt) {
        return MB_FALSE;
    }
    gNodeExpr = new LocationStep(nt, LocationStep::CHILD_AXIS);
    if (!gNodeExpr) {
        return MB_FALSE;
    }

    if (!txHTMLAtoms::init())
        return MB_FALSE;
    if (!txXMLAtoms::init())
        return MB_FALSE;
    if (!txXPathAtoms::init())
        return MB_FALSE;
    if (!txXSLTAtoms::init())
        return MB_FALSE;

#ifdef TX_EXE
    if (!txNamespaceManager::init())
        return MB_FALSE;

    if (NS_FAILED(txHTMLOutput::init())) {
        return MB_FALSE;
    }
#endif

    return MB_TRUE;
}

/* static */
MBool
txXSLTProcessor::txShutdown()
{
    TX_LG_DELETE;
#ifdef TX_EXE
    txNamespaceManager::shutdown();
    txHTMLOutput::shutdown();
#endif

    delete gNodeExpr;

    txHTMLAtoms::shutdown();
    txXMLAtoms::shutdown();
    txXPathAtoms::shutdown();
    txXSLTAtoms::shutdown();
    return MB_TRUE;
}

void
txXSLTProcessor::copyNode(Node* aSourceNode, ProcessorState* aPs)
{
    if (!aSourceNode)
        return;

    switch (aSourceNode->getNodeType()) {
        case Node::ATTRIBUTE_NODE:
        {
            nsAutoString nodeName, nodeValue;
            aSourceNode->getNodeName(nodeName);
            aSourceNode->getNodeValue(nodeValue);
            NS_ASSERTION(aPs->mResultHandler, "mResultHandler must not be NULL!");
            aPs->mResultHandler->attribute(nodeName,
                                           aSourceNode->getNamespaceID(),
                                           nodeValue);
            break;
        }
        case Node::COMMENT_NODE:
        {
            nsAutoString nodeValue;
            aSourceNode->getNodeValue(nodeValue);
            NS_ASSERTION(aPs->mResultHandler, "mResultHandler must not be NULL!");
            aPs->mResultHandler->comment(nodeValue);
            break;
        }
        case Node::DOCUMENT_NODE:
        case Node::DOCUMENT_FRAGMENT_NODE:
        {
            // Copy children
            Node* child = aSourceNode->getFirstChild();
            while (child) {
                copyNode(child, aPs);
                child = child->getNextSibling();
            }
            break;
        }
        case Node::ELEMENT_NODE:
        {
            Element* element = (Element*)aSourceNode;
            nsAutoString name;
            element->getNodeName(name);
            PRInt32 nsID = element->getNamespaceID();
            NS_ASSERTION(aPs->mResultHandler, "mResultHandler must not be NULL!");
            aPs->mResultHandler->startElement(name, nsID);

            // Copy attributes
            NamedNodeMap* attList = element->getAttributes();
            if (attList) {
                PRUint32 i = 0;
                for (i = 0; i < attList->getLength(); i++) {
                    Attr* attr = (Attr*)attList->item(i);
                    nsAutoString nodeName, nodeValue;
                    attr->getNodeName(nodeName);
                    attr->getNodeValue(nodeValue);
                    NS_ASSERTION(aPs->mResultHandler, "mResultHandler must not be NULL!");
                    aPs->mResultHandler->attribute(nodeName,
                                                   attr->getNamespaceID(),
                                                   nodeValue);
                }
            }

            // Copy children
            Node* child = element->getFirstChild();
            while (child) {
                copyNode(child, aPs);
                child = child->getNextSibling();
            }

            NS_ASSERTION(aPs->mResultHandler, "mResultHandler must not be NULL!");
            aPs->mResultHandler->endElement(name, nsID);
            break;
        }
        case Node::PROCESSING_INSTRUCTION_NODE:
        {
            ProcessingInstruction* pi = (ProcessingInstruction*)aSourceNode;
            nsAutoString target, data;
            pi->getNodeName(target);
            pi->getNodeValue(data);
            NS_ASSERTION(aPs->mResultHandler, "mResultHandler must not be NULL!");
            aPs->mResultHandler->processingInstruction(target, data);
            break;
        }
        case Node::TEXT_NODE:
        case Node::CDATA_SECTION_NODE:
        {
            nsAutoString nodeValue;
            aSourceNode->getNodeValue(nodeValue);
            NS_ASSERTION(aPs->mResultHandler, "mResultHandler must not be NULL!");
            aPs->mResultHandler->characters(nodeValue);
            break;
        }
    }
}

Document*
txXSLTProcessor::createRTFDocument(txOutputMethod aMethod)
{
#ifdef TX_EXE
    return new Document();
#else
    nsresult rv;
    nsCOMPtr<nsIDOMDocument> domDoc = do_CreateInstance(kXMLDocumentCID, &rv);
    NS_ENSURE_SUCCESS(rv, nsnull);
    return new Document(domDoc);
#endif
}

void
txXSLTProcessor::logMessage(const nsAString& aMessage)
{
#ifdef TX_EXE
    cout << "xsl:message - "<< NS_LossyConvertUCS2toASCII(aMessage).get() << endl;
#else
    nsresult rv;
    nsCOMPtr<nsIConsoleService> consoleSvc = 
      do_GetService("@mozilla.org/consoleservice;1", &rv);
    NS_ASSERTION(NS_SUCCEEDED(rv),
                 "xsl:message couldn't get console service");
    if (consoleSvc) {
        nsAutoString logString(NS_LITERAL_STRING("xsl:message - "));
        logString.Append(aMessage);
        rv = consoleSvc->LogStringMessage(logString.get());
        NS_ASSERTION(NS_SUCCEEDED(rv), "xsl:message couldn't log");
    }
#endif
}

void
txXSLTProcessor::processAction(Node* aAction,
                               ProcessorState* aPs)
{
    nsresult rv = NS_OK;
    NS_ASSERTION(aAction, "We need an action to process.");
    if (!aAction)
        return;

    unsigned short nodeType = aAction->getNodeType();

    // Handle text nodes
    if (nodeType == Node::TEXT_NODE ||
        nodeType == Node::CDATA_SECTION_NODE) {
        nsAutoString nodeValue;
        aAction->getNodeValue(nodeValue);
        if (!XMLUtils::isWhitespace(nodeValue) ||
            XMLUtils::getXMLSpacePreserve(aAction)) {
            NS_ASSERTION(aPs->mResultHandler, "mResultHandler must not be NULL!");
            aPs->mResultHandler->characters(nodeValue);
        }
        return;
    }

    if (nodeType != Node::ELEMENT_NODE) {
        return;
    }

    // Handle element nodes
    Element* actionElement = (Element*)aAction;
    PRInt32 nsID = aAction->getNamespaceID();
    if (nsID != kNameSpaceID_XSLT) {
        // Literal result element
        // XXX TODO Check for excluded namespaces and aliased namespaces (element and attributes) 
        nsAutoString nodeName;
        aAction->getNodeName(nodeName);
        NS_ASSERTION(aPs->mResultHandler, "mResultHandler must not be NULL!");
        aPs->mResultHandler->startElement(nodeName, nsID);

        processAttributeSets(actionElement, aPs);

        // Handle attributes
        NamedNodeMap* atts = actionElement->getAttributes();

        if (atts) {
            // Process all non XSLT attributes
            PRUint32 i;
            for (i = 0; i < atts->getLength(); ++i) {
                Attr* attr = (Attr*)atts->item(i);
                if (attr->getNamespaceID() == kNameSpaceID_XSLT)
                    continue;
                // Process Attribute Value Templates
                nsAutoString nodeName, nodeValue, value;
                attr->getNodeName(nodeName);
                attr->getNodeValue(nodeValue);
                aPs->processAttrValueTemplate(nodeValue, actionElement, value);
                NS_ASSERTION(aPs->mResultHandler, "mResultHandler must not be NULL!");
                aPs->mResultHandler->attribute(nodeName,
                                               attr->getNamespaceID(), value);
            }
        }

        // Process children
        processChildren(actionElement, aPs);
        NS_ASSERTION(aPs->mResultHandler, "mResultHandler must not be NULL!");
        aPs->mResultHandler->endElement(nodeName, nsID);
        return;
    }

    Expr* expr = 0;
    nsCOMPtr<nsIAtom> localName;
    aAction->getLocalName(getter_AddRefs(localName));
    // xsl:apply-imports
    if (localName == txXSLTAtoms::applyImports) {
        ProcessorState::TemplateRule* curr;
        Node* xslTemplate;
        ProcessorState::ImportFrame *frame;

        curr = aPs->getCurrentTemplateRule();
        if (!curr) {
            aPs->receiveError(NS_LITERAL_STRING("apply-imports not allowed here"),
                              NS_ERROR_FAILURE);
            return;
        }

        xslTemplate = aPs->findTemplate(aPs->getEvalContext()->getContextNode(),
                                        *curr->mMode, curr->mFrame, &frame);
        processMatchedTemplate(xslTemplate, curr->mParams, *curr->mMode,
                               frame, aPs);

    }
    // xsl:apply-templates
    else if (localName == txXSLTAtoms::applyTemplates) {
        if (actionElement->hasAttr(txXSLTAtoms::select,
                                   kNameSpaceID_None))
            expr = aPs->getExpr(actionElement,
                                ProcessorState::SelectAttr);
        else
            expr = gNodeExpr;

        if (!expr) {
            return;
        }

        ExprResult* exprResult = expr->evaluate(aPs->getEvalContext());
        if (!exprResult) {
            return;
        }

        if (exprResult->getResultType() == ExprResult::NODESET) {
            NodeSet* nodeSet = (NodeSet*)exprResult;
            if (nodeSet->isEmpty()) {
                delete nodeSet;
                return;
            }

            // Look for xsl:sort elements
            txNodeSorter sorter(aPs);
            Node* child = actionElement->getFirstChild();
            while (child) {
                if (child->getNodeType() == Node::ELEMENT_NODE &&
                    child->getNamespaceID() == kNameSpaceID_XSLT) {
                    nsCOMPtr<nsIAtom> childLocalName;
                    child->getLocalName(getter_AddRefs(childLocalName));
                    if (childLocalName == txXSLTAtoms::sort) {
                        sorter.addSortElement((Element*)child);
                    }
                }
                child = child->getNextSibling();
            }
            sorter.sortNodeSet(nodeSet);

            // Process xsl:with-param elements
            txVariableMap params(0);
            processParameters(actionElement, &params, aPs);

            // Get mode
            nsAutoString modeStr;
            txExpandedName mode;
            if (actionElement->getAttr(txXSLTAtoms::mode,
                                       kNameSpaceID_None, modeStr)) {
                rv = mode.init(modeStr, actionElement, MB_FALSE);
                if (NS_FAILED(rv)) {
                    aPs->receiveError(NS_LITERAL_STRING("malformed mode-name in xsl:apply-templates"));
                    return;
                }
            }

            txNodeSetContext evalContext(nodeSet, aPs);
            txIEvalContext* priorEC =
                aPs->setEvalContext(&evalContext);
            while (evalContext.hasNext()) {
                evalContext.next();
                ProcessorState::ImportFrame *frame;
                Node* currNode = evalContext.getContextNode();
                Node* xslTemplate;
                xslTemplate = aPs->findTemplate(currNode, mode, &frame);
                processMatchedTemplate(xslTemplate, &params, mode, frame, aPs);
            }

            aPs->setEvalContext(priorEC);
        }
        else {
            aPs->receiveError(NS_LITERAL_STRING("error processing apply-templates"),
                              NS_ERROR_FAILURE);
        }
        //-- clean up
        delete exprResult;
    }
    // xsl:attribute
    else if (localName == txXSLTAtoms::attribute) {
        nsAutoString nameAttr;
        if (!actionElement->getAttr(txXSLTAtoms::name,
                                    kNameSpaceID_None, nameAttr)) {
            aPs->receiveError(NS_LITERAL_STRING("missing required name attribute for xsl:attribute"), NS_ERROR_FAILURE);
            return;
        }

        // Process name as an AttributeValueTemplate
        nsAutoString name;
        aPs->processAttrValueTemplate(nameAttr, actionElement, name);

        // Check name validity (must be valid QName and not xmlns)
        if (!XMLUtils::isValidQName(name)) {
            aPs->receiveError(NS_LITERAL_STRING("error processing xsl:attribute, ") +
                              name + NS_LITERAL_STRING(" is not a valid QName."),
                              NS_ERROR_FAILURE);
            return;
        }

        if (TX_StringEqualsAtom(name, txXMLAtoms::xmlns)) {
            aPs->receiveError(NS_LITERAL_STRING("error processing xsl:attribute, name is xmlns."), NS_ERROR_FAILURE);
            return;
        }

        // Determine namespace URI from the namespace attribute or
        // from the prefix of the name (using the xslt action element).
        nsAutoString resultNs;
        PRInt32 resultNsID = kNameSpaceID_None;
        if (actionElement->getAttr(txXSLTAtoms::_namespace, kNameSpaceID_None,
                                   resultNs)) {
            nsAutoString nsURI;
            aPs->processAttrValueTemplate(resultNs, actionElement,
                                          nsURI);
            resultNsID = aPs->getStylesheetDocument()->namespaceURIToID(nsURI);
        }
        else {
            nsCOMPtr<nsIAtom> prefix;
            XMLUtils::getPrefix(name, getter_AddRefs(prefix));
            if (prefix) {
                if (prefix != txXMLAtoms::xmlns)
                    resultNsID = actionElement->lookupNamespaceID(prefix);
                else
                    // Cut xmlns: (6 characters)
                    name.Cut(0, 6);
            }
        }

        // XXX Should verify that this is correct behaviour. Signal error too?
        if (resultNsID == kNameSpaceID_Unknown) {
            return;
        }

        // Compute value
        nsAutoString value;
        processChildrenAsValue(actionElement, aPs, MB_TRUE, value);

        NS_ASSERTION(aPs->mResultHandler, "mResultHandler must not be NULL!");
        aPs->mResultHandler->attribute(name, resultNsID, value);
    }
    // xsl:call-template
    else if (localName == txXSLTAtoms::callTemplate) {
        nsAutoString nameStr;
        txExpandedName templateName;
        actionElement->getAttr(txXSLTAtoms::name,
                               kNameSpaceID_None, nameStr);

        rv = templateName.init(nameStr, actionElement, MB_FALSE);
        if (NS_SUCCEEDED(rv)) {
            Element* xslTemplate = aPs->getNamedTemplate(templateName);
            if (xslTemplate) {
#ifdef PR_LOGGING
                nsAutoString baseURI;
                xslTemplate->getBaseURI(baseURI);
                PR_LOG(txLog::xslt, PR_LOG_DEBUG,
                       ("CallTemplate, Name %s, Stylesheet %s\n",
                        NS_LossyConvertUCS2toASCII(nameStr).get(),
                        NS_LossyConvertUCS2toASCII(baseURI).get()));
#endif
                txVariableMap params(0);
                processParameters(actionElement, &params, aPs);
                processTemplate(xslTemplate, &params, aPs);
            }
        }
        else {
            aPs->receiveError(NS_LITERAL_STRING("missing or malformed name in xsl:call-template"), NS_ERROR_FAILURE);
        }
    }
    // xsl:choose
    else if (localName == txXSLTAtoms::choose) {
        Node* condition = actionElement->getFirstChild();
        MBool caseFound = MB_FALSE;
        while (!caseFound && condition) {
            if (condition->getNodeType() != Node::ELEMENT_NODE ||
                condition->getNamespaceID() != kNameSpaceID_XSLT) {
                condition = condition->getNextSibling();
                continue;
            }

            Element* xslTemplate = (Element*)condition;
            nsCOMPtr<nsIAtom> conditionLocalName;
            condition->getLocalName(getter_AddRefs(conditionLocalName));
            if (conditionLocalName == txXSLTAtoms::when) {
                expr = aPs->getExpr(xslTemplate,
                                    ProcessorState::TestAttr);
                if (!expr) {
                    condition = condition->getNextSibling();
                    continue;
                }

                ExprResult* result =
                    expr->evaluate(aPs->getEvalContext());
                if (result && result->booleanValue()) {
                    processChildren(xslTemplate, aPs);
                    caseFound = MB_TRUE;
                }
                delete result;
            }
            else if (conditionLocalName == txXSLTAtoms::otherwise) {
                processChildren(xslTemplate, aPs);
                caseFound = MB_TRUE;
            }
            condition = condition->getNextSibling();
        } // end for-each child of xsl:choose
    }
    // xsl:comment
    else if (localName == txXSLTAtoms::comment) {
        nsAutoString value;
        processChildrenAsValue(actionElement, aPs, MB_TRUE, value);
        PRInt32 pos = 0;
        PRUint32 length = value.Length();
        while ((pos = value.FindChar('-', (PRUint32)pos)) != kNotFound) {
            ++pos;
            if (((PRUint32)pos == length) || (value.CharAt(pos) == '-')) {
                value.Insert(PRUnichar(' '), pos++);
                ++length;
            }
        }
        NS_ASSERTION(aPs->mResultHandler, "mResultHandler must not be NULL!");
        aPs->mResultHandler->comment(value);
    }
    // xsl:copy
    else if (localName == txXSLTAtoms::copy) {
        xslCopy(actionElement, aPs);
    }
    // xsl:copy-of
    else if (localName == txXSLTAtoms::copyOf) {
        expr = aPs->getExpr(actionElement, ProcessorState::SelectAttr);
        if (!expr) {
            return;
        }

        ExprResult* exprResult = expr->evaluate(aPs->getEvalContext());
        xslCopyOf(exprResult, aPs);
        delete exprResult;
    }
    // xsl:element
    else if (localName == txXSLTAtoms::element) {
        nsAutoString nameAttr;
        if (!actionElement->getAttr(txXSLTAtoms::name,
                                    kNameSpaceID_None, nameAttr)) {
            aPs->receiveError(NS_LITERAL_STRING("missing required name attribute for xsl:element"), NS_ERROR_FAILURE);
            return;
        }

        // Process name as an AttributeValueTemplate
        nsAutoString name;
        aPs->processAttrValueTemplate(nameAttr, actionElement, name);

        // Check name validity (must be valid QName and not xmlns)
        if (!XMLUtils::isValidQName(name)) {
            aPs->receiveError(NS_LITERAL_STRING("error processing xsl:element, '") +
                              name + NS_LITERAL_STRING("' is not a valid QName."),
                              NS_ERROR_FAILURE);
            // XXX We should processChildren without creating attributes or
            //     namespace nodes.
            return;
        }

        // Determine namespace URI from the namespace attribute or
        // from the prefix of the name (using the xslt action element).
        nsAutoString resultNs;
        PRInt32 resultNsID;
        if (actionElement->getAttr(txXSLTAtoms::_namespace, kNameSpaceID_None, resultNs)) {
            nsAutoString nsURI;
            aPs->processAttrValueTemplate(resultNs, actionElement, nsURI);
            if (nsURI.IsEmpty())
                resultNsID = kNameSpaceID_None;
            else
                resultNsID = aPs->getStylesheetDocument()->namespaceURIToID(nsURI);
        }
        else {
            nsCOMPtr<nsIAtom> prefix;
            XMLUtils::getPrefix(name, getter_AddRefs(prefix));
            resultNsID = actionElement->lookupNamespaceID(prefix);
         }

        if (resultNsID == kNameSpaceID_Unknown) {
            aPs->receiveError(NS_LITERAL_STRING("error processing xsl:element, can't resolve prefix on'") +
                              name + NS_LITERAL_STRING("'."),
                              NS_ERROR_FAILURE);
            // XXX We should processChildren without creating attributes or
            //     namespace nodes.
            return;
        }

        NS_ASSERTION(aPs->mResultHandler, "mResultHandler must not be NULL!");
        aPs->mResultHandler->startElement(name, resultNsID);
        processAttributeSets(actionElement, aPs);
        processChildren(actionElement, aPs);
        NS_ASSERTION(aPs->mResultHandler, "mResultHandler must not be NULL!");
        aPs->mResultHandler->endElement(name, resultNsID);
    }
    // xsl:for-each
    else if (localName == txXSLTAtoms::forEach) {
        expr = aPs->getExpr(actionElement, ProcessorState::SelectAttr);
        if (!expr) {
            return;
        }

        ExprResult* exprResult = expr->evaluate(aPs->getEvalContext());
        if (!exprResult) {
            return;
        }

        if (exprResult->getResultType() == ExprResult::NODESET) {
            NodeSet* nodeSet = (NodeSet*)exprResult;
            if (nodeSet->isEmpty()) {
                delete nodeSet;
                return;
            }
            txNodeSetContext evalContext(nodeSet, aPs);
            txIEvalContext* priorEC =
                aPs->setEvalContext(&evalContext);

            // Look for xsl:sort elements
            txNodeSorter sorter(aPs);
            Node* child = actionElement->getFirstChild();
            while (child) {
                unsigned short nodeType = child->getNodeType();
                if (nodeType == Node::ELEMENT_NODE) {
                    nsCOMPtr<nsIAtom> childLocalName;
                    child->getLocalName(getter_AddRefs(childLocalName));
                    if (child->getNamespaceID() != kNameSpaceID_XSLT ||
                        childLocalName != txXSLTAtoms::sort) {
                        // xsl:sort must occur first
                        break;
                    }
                    sorter.addSortElement((Element*)child);
                }
                else if ((nodeType == Node::TEXT_NODE ||
                          nodeType == Node::CDATA_SECTION_NODE) &&
                         !XMLUtils::isWhitespace(child)) {
                    break;
                }

                child = child->getNextSibling();
            }
            sorter.sortNodeSet(nodeSet);

            // Set current template to null
            ProcessorState::TemplateRule *oldTemplate;
            oldTemplate = aPs->getCurrentTemplateRule();
            aPs->setCurrentTemplateRule(0);

            while (evalContext.hasNext()) {
                evalContext.next();
                processChildren(actionElement, aPs);
            }

            aPs->setCurrentTemplateRule(oldTemplate);
            aPs->setEvalContext(priorEC);
        }
        else {
            aPs->receiveError(NS_LITERAL_STRING("error processing for-each"),
                              NS_ERROR_FAILURE);
        }
        //-- clean up exprResult
        delete exprResult;
    }
    // xsl:if
    else if (localName == txXSLTAtoms::_if) {
        expr = aPs->getExpr(actionElement, ProcessorState::TestAttr);
        if (!expr) {
            return;
        }

        ExprResult* exprResult = expr->evaluate(aPs->getEvalContext());
        if (!exprResult) {
            return;
        }

        if (exprResult->booleanValue()) {
            processChildren(actionElement, aPs);
        }
        delete exprResult;
    }
    // xsl:message
    else if (localName == txXSLTAtoms::message) {
        nsAutoString message;
        processChildrenAsValue(actionElement, aPs, MB_FALSE, message);
        // We should add a MessageObserver class
        logMessage(message);
    }
    // xsl:number
    else if (localName == txXSLTAtoms::number) {
        nsAutoString result;
        // XXX ErrorReport, propagate result from ::createNumber
        txXSLTNumber::createNumber(actionElement, aPs, result);
        NS_ASSERTION(aPs->mResultHandler, "mResultHandler must not be NULL!");
        aPs->mResultHandler->characters(result);
    }
    // xsl:param
    else if (localName == txXSLTAtoms::param) {
        aPs->receiveError(NS_LITERAL_STRING("misplaced xsl:param"),
                          NS_ERROR_FAILURE);
    }
    // xsl:processing-instruction
    else if (localName == txXSLTAtoms::processingInstruction) {
        nsAutoString nameAttr;
        if (!actionElement->getAttr(txXSLTAtoms::name,
                                    kNameSpaceID_None, nameAttr)) {
            aPs->receiveError(NS_LITERAL_STRING("missing required name attribute for xsl:processing-instruction"), NS_ERROR_FAILURE);
            return;
        }

        // Process name as an AttributeValueTemplate
        nsAutoString name;
        aPs->processAttrValueTemplate(nameAttr, actionElement, name);

        // Check name validity (must be valid NCName and a PITarget)
        // XXX Need to check for NCName and PITarget
        if (!XMLUtils::isValidQName(name)) {
            aPs->receiveError(NS_LITERAL_STRING("error processing xsl:processing-instruction, '") +
                              name + NS_LITERAL_STRING("' is not a valid QName."),
                              NS_ERROR_FAILURE);
        }

        // Compute value
        nsAutoString value;
        processChildrenAsValue(actionElement, aPs, MB_TRUE, value);
        XMLUtils::normalizePIValue(value);
        NS_ASSERTION(aPs->mResultHandler, "mResultHandler must not be NULL!");
        aPs->mResultHandler->processingInstruction(name, value);
    }
    // xsl:sort
    else if (localName == txXSLTAtoms::sort) {
        // Ignore in this loop
    }
    // xsl:text
    else if (localName == txXSLTAtoms::text) {
        nsAutoString data;
        XMLDOMUtils::getNodeValue(actionElement, data);

        NS_ASSERTION(aPs->mResultHandler, "mResultHandler must not be NULL!");
        MBool doe = MB_FALSE;
        if ((aPs->mResultHandler == aPs->mOutputHandler) &&
            aPs->mOutputHandler->hasDisableOutputEscaping()) {
            nsAutoString attValue;
            doe = actionElement->getAttr(txXSLTAtoms::disableOutputEscaping,
                                         kNameSpaceID_None, attValue) &&
                  TX_StringEqualsAtom(attValue, txXSLTAtoms::yes);
        }
        if (doe) {
            aPs->mOutputHandler->charactersNoOutputEscaping(data);
        }
        else {
            aPs->mResultHandler->characters(data);
        }
    }
    // xsl:value-of
    else if (localName == txXSLTAtoms::valueOf) {
        expr = aPs->getExpr(actionElement, ProcessorState::SelectAttr);
        if (!expr) {
            return;
        }

        ExprResult* exprResult = expr->evaluate(aPs->getEvalContext());
        nsAutoString value;
        if (!exprResult) {
            aPs->receiveError(NS_LITERAL_STRING("null ExprResult"),
                              NS_ERROR_FAILURE);
            return;
        }
        exprResult->stringValue(value);
        if (value.IsEmpty()) {
            delete exprResult;
            return;
        }

        NS_ASSERTION(aPs->mResultHandler, "mResultHandler must not be NULL!");
        MBool doe = MB_FALSE;
        if ((aPs->mResultHandler == aPs->mOutputHandler) &&
            aPs->mOutputHandler->hasDisableOutputEscaping()) {
            nsAutoString attValue;
            doe = actionElement->getAttr(txXSLTAtoms::disableOutputEscaping,
                                         kNameSpaceID_None, attValue) &&
                  TX_StringEqualsAtom(attValue, txXSLTAtoms::yes);
        }
        if (doe) {
            aPs->mOutputHandler->charactersNoOutputEscaping(value);
        }
        else {
            aPs->mResultHandler->characters(value);
        }
        delete exprResult;
    }
    // xsl:variable
    else if (localName == txXSLTAtoms::variable) {
        txExpandedName varName;
        nsAutoString qName;
        actionElement->getAttr(txXSLTAtoms::name, kNameSpaceID_None,
                               qName);
        rv = varName.init(qName, actionElement, MB_FALSE);
        if (NS_FAILED(rv)) {
            aPs->receiveError(NS_LITERAL_STRING("bad name for xsl:variable"),
                              NS_ERROR_FAILURE);
            return;
        }
        ExprResult* exprResult = processVariable(actionElement, aPs);
        if (!exprResult) {
            return;
        }

        txVariableMap* vars = aPs->getLocalVariables();
        NS_ASSERTION(vars, "missing localvariable map");
        rv = vars->bindVariable(varName, exprResult, MB_TRUE);
        if (NS_FAILED(rv)) {
            aPs->receiveError(NS_LITERAL_STRING("bad name for xsl:variable"),
                              NS_ERROR_FAILURE);
        }
    }
}

void
txXSLTProcessor::processAttributeSets(Element* aElement,
                                      ProcessorState* aPs,
                                      txStack* aRecursionStack)
{
    nsresult rv = NS_OK;
    nsAutoString names;
    PRInt32 namespaceID;
    if (aElement->getNamespaceID() == kNameSpaceID_XSLT)
        namespaceID = kNameSpaceID_None;
    else
        namespaceID = kNameSpaceID_XSLT;
    if (!aElement->getAttr(txXSLTAtoms::useAttributeSets, namespaceID, names) || names.IsEmpty())
        return;

    // Split names
    txTokenizer tokenizer(names);
    while (tokenizer.hasMoreTokens()) {
        txExpandedName name;
        rv = name.init(tokenizer.nextToken(), aElement, MB_FALSE);
        if (NS_FAILED(rv)) {
            aPs->receiveError(NS_LITERAL_STRING("missing or malformed name in use-attribute-sets"));
            return;
        }

        if (aRecursionStack) {
            txStackIterator attributeSets(aRecursionStack);
            while (attributeSets.hasNext()) {
                if (name == *(txExpandedName*)attributeSets.next()) {
                    aPs->receiveError(NS_LITERAL_STRING("circular inclusion detected in use-attribute-sets"));
                    return;
                }
            }
        }

        NodeSet* attSet = aPs->getAttributeSet(name);
        if (attSet) {
            int i;
            //-- issue: we still need to handle the following fix cleaner, since
            //-- attribute sets are merged, a different parent could exist
            //-- for different xsl:attribute nodes. I will probably create
            //-- an AttributeSet object, which will handle this case better. - Keith V.
            if (attSet->size() > 0) {
                Element* parent = (Element*) attSet->get(0)->getXPathParent();
                if (aRecursionStack) {
                    aRecursionStack->push(&name);
                    processAttributeSets(parent, aPs, aRecursionStack);
                    aRecursionStack->pop();
                }
                else {
                    txStack recursionStack;
                    recursionStack.push(&name);
                    processAttributeSets(parent, aPs, &recursionStack);
                    recursionStack.pop();
                }
            }
            for (i = 0; i < attSet->size(); i++)
                processAction(attSet->get(i), aPs);
            delete attSet;
        }
    }
}

void
txXSLTProcessor::processChildren(Element* aElement,
                                 ProcessorState* aPs)
{
    NS_ASSERTION(aElement, "missing aElement");

    txVariableMap* oldVars = aPs->getLocalVariables();
    txVariableMap localVars(oldVars);
    aPs->setLocalVariables(&localVars);
    Node* child = aElement->getFirstChild();
    while (child) {
        processAction(child, aPs);
        child = child->getNextSibling();
    }
    aPs->setLocalVariables(oldVars);
}

void
txXSLTProcessor::processChildrenAsValue(Element* aElement,
                                        ProcessorState* aPs,
                                        MBool aOnlyText,
                                        nsAString& aValue)
{
    txXMLEventHandler* previousHandler = aPs->mResultHandler;
    txTextHandler valueHandler(aValue, aOnlyText);
    aPs->mResultHandler = &valueHandler;
    processChildren(aElement, aPs);
    aPs->mResultHandler = previousHandler;
}

void
txXSLTProcessor::processDefaultTemplate(ProcessorState* aPs,
                                        const txExpandedName& aMode)
{
    Node* node = aPs->getEvalContext()->getContextNode();
    switch (node->getNodeType())
    {
        case Node::ELEMENT_NODE :
        case Node::DOCUMENT_NODE :
        {
            if (!gNodeExpr)
                break;

            ExprResult* exprResult = gNodeExpr->evaluate(aPs->getEvalContext());
            if (!exprResult ||
                exprResult->getResultType() != ExprResult::NODESET) {
                aPs->receiveError(NS_LITERAL_STRING("None-nodeset returned while processing default template"), NS_ERROR_FAILURE);
                delete exprResult;
                return;
            }

            NodeSet* nodeSet = (NodeSet*)exprResult;
            if (nodeSet->isEmpty()) {
                delete nodeSet;
                return;
            }
            txNodeSetContext evalContext(nodeSet, aPs);
            txIEvalContext* priorEC = aPs->setEvalContext(&evalContext);

            while (evalContext.hasNext()) {
                evalContext.next();
                Node* currNode = evalContext.getContextNode();

                ProcessorState::ImportFrame *frame;
                Node* xslTemplate = aPs->findTemplate(currNode, aMode, &frame);
                processMatchedTemplate(xslTemplate, 0, aMode, frame, aPs);
            }
            aPs->setEvalContext(priorEC);
            delete exprResult;
            break;
        }
        case Node::ATTRIBUTE_NODE :
        case Node::TEXT_NODE :
        case Node::CDATA_SECTION_NODE :
        {
            nsAutoString nodeValue;
            node->getNodeValue(nodeValue);
            NS_ASSERTION(aPs->mResultHandler, "mResultHandler must not be NULL!");
            aPs->mResultHandler->characters(nodeValue);
            break;
        }
        default:
            // on all other nodetypes (including namespace nodes)
            // we do nothing
            break;
    }
}

void
txXSLTProcessor::processInclude(const nsAString& aHref,
                                txListIterator* aImportFrame,
                                ProcessorState* aPs)
{
    // make sure the include isn't included yet
    nsStringArray& stylesheetStack = aPs->getEnteredStylesheets();
    if (stylesheetStack.IndexOf(aHref) != -1) {
        aPs->receiveError(NS_LITERAL_STRING("Stylesheet includes itself. URI: ") +
                          aHref, NS_ERROR_FAILURE);
        return;
    }
    stylesheetStack.AppendString(aHref);

    // Load XSL document
    Node* stylesheet = aPs->retrieveDocument(aHref, NS_LITERAL_STRING(""));
    if (!stylesheet) {
        aPs->receiveError(NS_LITERAL_STRING("Unable to load included stylesheet ") +
                          aHref, NS_ERROR_FAILURE);
        stylesheetStack.RemoveStringAt(stylesheetStack.Count() - 1);
        return;
    }

    switch(stylesheet->getNodeType()) {
        case Node::DOCUMENT_NODE :
            processStylesheet((Document*)stylesheet, 0,
                              aImportFrame, aPs);
            break;
        case Node::ELEMENT_NODE :
            processTopLevel((Element*)stylesheet, 0,
                            aImportFrame, aPs);
            break;
        default:
            // This should never happen
            aPs->receiveError(NS_LITERAL_STRING("Unsupported fragment identifier"), NS_ERROR_FAILURE);
            break;
    }

    stylesheetStack.RemoveStringAt(stylesheetStack.Count() - 1);
}

void
txXSLTProcessor::processMatchedTemplate(Node* aTemplate,
                                        txVariableMap* aParams,
                                        const txExpandedName& aMode,
                                        ProcessorState::ImportFrame* aFrame,
                                        ProcessorState* aPs)
{
    if (aTemplate) {
        ProcessorState::TemplateRule *oldTemplate, newTemplate;
        oldTemplate = aPs->getCurrentTemplateRule();
        newTemplate.mFrame = aFrame;
        newTemplate.mMode = &aMode;
        newTemplate.mParams = aParams;
        aPs->setCurrentTemplateRule(&newTemplate);

        processTemplate(aTemplate, aParams, aPs);

        aPs->setCurrentTemplateRule(oldTemplate);
    }
    else {
        processDefaultTemplate(aPs, aMode);
    }
}

nsresult
txXSLTProcessor::processParameters(Element* aAction,
                                   txVariableMap* aMap,
                                   ProcessorState* aPs)
{
    NS_ASSERTION(aAction && aMap, "missing argument");
    nsresult rv = NS_OK;

    Node* tmpNode = aAction->getFirstChild();
    while (tmpNode) {
        if (tmpNode->getNodeType() == Node::ELEMENT_NODE &&
            tmpNode->getNamespaceID() == kNameSpaceID_XSLT) {
            nsCOMPtr<nsIAtom> localName;
            tmpNode->getLocalName(getter_AddRefs(localName));
            if (localName != txXSLTAtoms::withParam) {
                tmpNode = tmpNode->getNextSibling();
                continue;
            }

            Element* action = (Element*)tmpNode;
            txExpandedName paramName;
            nsAutoString qName;
            action->getAttr(txXSLTAtoms::name, kNameSpaceID_None, qName);
            rv = paramName.init(qName, action, MB_FALSE);
            if (NS_FAILED(rv)) {
                aPs->receiveError(NS_LITERAL_STRING("bad name for xsl:param"),
                                  NS_ERROR_FAILURE);
                break;
            }

            ExprResult* exprResult = processVariable(action, aPs);
            if (!exprResult) {
                return NS_ERROR_FAILURE;
            }

            rv = aMap->bindVariable(paramName, exprResult, MB_TRUE);
            if (NS_FAILED(rv)) {
                aPs->receiveError(NS_LITERAL_STRING("Unable to bind parameter '") +
                                  qName + NS_LITERAL_STRING("'"),
                                  NS_ERROR_FAILURE);
                return rv;
            }
        }
        tmpNode = tmpNode->getNextSibling();
    }
    return NS_OK;
}

nsresult
txXSLTProcessor::processStylesheet(Document* aStylesheet,
                                   txExpandedNameMap* aGlobalParams,
                                   ProcessorState* aPs)
{
    txListIterator importFrame(aPs->getImportFrames());
    importFrame.addAfter(new ProcessorState::ImportFrame(0));
    if (!importFrame.next()) {
        return NS_ERROR_OUT_OF_MEMORY;
    }
    processStylesheet(aStylesheet, aGlobalParams, &importFrame, aPs);
    return NS_OK;
}

void
txXSLTProcessor::processStylesheet(Document* aStylesheet,
                                   txExpandedNameMap* aGlobalParams,
                                   txListIterator* aImportFrame,
                                   ProcessorState* aPs)
{
    NS_ASSERTION(aStylesheet, "processTopLevel called without stylesheet");
    if (!aStylesheet || !aStylesheet->getDocumentElement())
        return;

    Element* elem = aStylesheet->getDocumentElement();

    nsCOMPtr<nsIAtom> localName;
    PRInt32 namespaceID = elem->getNamespaceID();
    elem->getLocalName(getter_AddRefs(localName));

    if (((localName == txXSLTAtoms::stylesheet) ||
         (localName == txXSLTAtoms::transform)) &&
        (namespaceID == kNameSpaceID_XSLT)) {
        processTopLevel(elem, aGlobalParams, aImportFrame, aPs);
    }
    else {
        NS_ASSERTION(aImportFrame->current(), "no current importframe");
        if (!aImportFrame->current()) {
            return;
        }
        aPs->addLREStylesheet(aStylesheet,
            (ProcessorState::ImportFrame*)aImportFrame->current());
    }
}

void
txXSLTProcessor::processTemplate(Node* aTemplate,
                                 txVariableMap* aParams,
                                 ProcessorState* aPs)
{
    NS_ASSERTION(aTemplate, "aTemplate is NULL");

    nsresult rv;

    txVariableMap* oldVars = aPs->getLocalVariables();
    txVariableMap localVars(0);
    aPs->setLocalVariables(&localVars);

    // handle params
    Node* tmpNode = aTemplate->getFirstChild();
    while (tmpNode) {
        int nodeType = tmpNode->getNodeType();
        if (nodeType == Node::ELEMENT_NODE) {
            nsCOMPtr<nsIAtom> localName;
            tmpNode->getLocalName(getter_AddRefs(localName));
            if (tmpNode->getNamespaceID() != kNameSpaceID_XSLT ||
                localName != txXSLTAtoms::param) {
                break;
            }

            Element* action = (Element*)tmpNode;
            txExpandedName paramName;
            nsAutoString qName;
            action->getAttr(txXSLTAtoms::name, kNameSpaceID_None, qName);
            rv = paramName.init(qName, action, MB_FALSE);
            if (NS_FAILED(rv)) {
                aPs->receiveError(NS_LITERAL_STRING("bad name for xsl:param"),
                                  NS_ERROR_FAILURE);
                break;
            }

            ExprResult* exprResult;
            if (aParams && (exprResult = aParams->getVariable(paramName))) {
                rv = localVars.bindVariable(paramName, exprResult, MB_FALSE);
            }
            else {
                exprResult = processVariable(action, aPs);
                if (!exprResult)
                    break;
                rv = localVars.bindVariable(paramName, exprResult, MB_TRUE);
            }

            if (NS_FAILED(rv)) {
                aPs->receiveError(NS_LITERAL_STRING("unable to bind xsl:param"),
                                  NS_ERROR_FAILURE);
            }

        }
        else if (!(nodeType == Node::COMMENT_NODE ||
                  ((nodeType == Node::TEXT_NODE ||
                    nodeType == Node::CDATA_SECTION_NODE) &&
                   XMLUtils::isWhitespace(tmpNode)))) {
            break;
        }
        tmpNode = tmpNode->getNextSibling();
    }

    // execute contents
    while (tmpNode) {
        processAction(tmpNode, aPs);
        tmpNode = tmpNode->getNextSibling();
    }
    aPs->setLocalVariables(oldVars);
}

nsresult
txXSLTProcessor::processTopLevel(Element* aStylesheet,
                                 txExpandedNameMap* aGlobalParams,
                                 ProcessorState* aPs)
{
    txListIterator importFrame(aPs->getImportFrames());
    importFrame.addAfter(new ProcessorState::ImportFrame(0));
    if (!importFrame.next()) {
        return NS_ERROR_OUT_OF_MEMORY;
    }
    processTopLevel(aStylesheet, aGlobalParams, &importFrame, aPs);
    return NS_OK;
}

void
txXSLTProcessor::processTopLevel(Element* aStylesheet,
                                 txExpandedNameMap* aGlobalParams,
                                 txListIterator* importFrame,
                                 ProcessorState* aPs)
{
    // Index templates and process top level xslt elements
    NS_ASSERTION(aStylesheet, "processTopLevel called without stylesheet element");
    if (!aStylesheet)
        return;

    ProcessorState::ImportFrame* currentFrame =
        (ProcessorState::ImportFrame*)importFrame->current();

    NS_ASSERTION(currentFrame,
                 "processTopLevel called with no current importframe");
    if (!currentFrame)
        return;

    MBool importsDone = MB_FALSE;
    Node* node = aStylesheet->getFirstChild();
    while (node && !importsDone) {
        if (node->getNodeType() == Node::ELEMENT_NODE) {
            nsCOMPtr<nsIAtom> localName;
            node->getLocalName(getter_AddRefs(localName));
            if (node->getNamespaceID() == kNameSpaceID_XSLT &&
                localName == txXSLTAtoms::import) {
                Element* element = (Element*)node;
                nsAutoString hrefAttr, href, baseURI;
                element->getAttr(txXSLTAtoms::href, kNameSpaceID_None,
                                 hrefAttr);
                element->getBaseURI(baseURI);
                URIUtils::resolveHref(hrefAttr, baseURI,
                                      href);

                // Create a new ImportFrame with correct firstNotImported
                ProcessorState::ImportFrame *nextFrame, *newFrame;
                nextFrame =
                    (ProcessorState::ImportFrame*)importFrame->next();
                newFrame = new ProcessorState::ImportFrame(nextFrame);
                if (!newFrame) {
                    // XXX ErrorReport: out of memory
                    break;
                }

                // Insert frame and process stylesheet
                importFrame->addBefore(newFrame);
                importFrame->previous();
                processInclude(href, importFrame, aPs);

                // Restore iterator to initial position
                importFrame->previous();
            }
            else {
                importsDone = MB_TRUE;
            }
        }
        if (!importsDone)
            node = node->getNextSibling();
    }

    while (node) {
        if (node->getNodeType() != Node::ELEMENT_NODE ||
            node->getNamespaceID() != kNameSpaceID_XSLT) {
            node = node->getNextSibling();
            continue;
        }

        nsCOMPtr<nsIAtom> localName;
        node->getLocalName(getter_AddRefs(localName));
        Element* element = (Element*)node;
        // xsl:attribute-set
        if (localName == txXSLTAtoms::attributeSet) {
            aPs->addAttributeSet(element, currentFrame);
        }
        // xsl:decimal-format
        else if (localName == txXSLTAtoms::decimalFormat) {
            if (!aPs->addDecimalFormat(element)) {
                // Add error to ErrorObserver
                nsAutoString fName;
                element->getAttr(txXSLTAtoms::name, kNameSpaceID_None,
                                 fName);
                nsAutoString err(NS_LITERAL_STRING("unable to add "));
                if (fName.IsEmpty()) {
                    err.Append(NS_LITERAL_STRING("default"));
                }
                else {
                    err.Append(PRUnichar('\"'));
                    err.Append(fName);
                    err.Append(PRUnichar('\"'));
                }
                err.Append(NS_LITERAL_STRING(" decimal format for xsl:decimal-format"));
                aPs->receiveError(err, NS_ERROR_FAILURE);
            }
        }
        // xsl:param
        else if (localName == txXSLTAtoms::param) {
            nsAutoString qName;
            element->getAttr(txXSLTAtoms::name, kNameSpaceID_None, qName);
            txExpandedName varName;
            nsresult rv = varName.init(qName, element, MB_FALSE);
            if (NS_SUCCEEDED(rv)) {
                ExprResult* defaultValue = 0;
                if (aGlobalParams) {
                    txIGlobalParameter* globalParam =
                        (txIGlobalParameter*)aGlobalParams->get(varName);
                    if (globalParam) {
                        globalParam->getValue(&defaultValue);
                    }
                }

                rv = aPs->addGlobalVariable(varName, element, currentFrame,
                                            defaultValue);
                if (NS_FAILED(rv)) {
                    aPs->receiveError(NS_LITERAL_STRING("unable to add global xsl:param"), NS_ERROR_FAILURE);
                }
            }
            else {
                aPs->receiveError(NS_LITERAL_STRING("unable to add global xsl:param"), NS_ERROR_FAILURE);
            }
        }
        // xsl:import
        else if (localName == txXSLTAtoms::import) {
            aPs->receiveError(NS_LITERAL_STRING("xsl:import only allowed at top of stylesheet"), NS_ERROR_FAILURE);
        }
        // xsl:include
        else if (localName == txXSLTAtoms::include) {
            nsAutoString hrefAttr, href, baseURI;
            element->getAttr(txXSLTAtoms::href, kNameSpaceID_None,
                             hrefAttr);
            element->getBaseURI(baseURI);
            URIUtils::resolveHref(hrefAttr, baseURI,
                                  href);

            processInclude(href, importFrame, aPs);
        }
        // xsl:key
        else if (localName == txXSLTAtoms::key) {
            if (!aPs->addKey(element)) {
                nsAutoString name;
                element->getAttr(txXSLTAtoms::name, kNameSpaceID_None,
                                 name);
                aPs->receiveError(NS_LITERAL_STRING("error adding key '") +
                                  name + NS_LITERAL_STRING("'"),
                                  NS_ERROR_FAILURE);
            }
        }
        // xsl:output
        else if (localName == txXSLTAtoms::output) {
            txOutputFormat& format = currentFrame->mOutputFormat;
            nsAutoString attValue;

            if (element->getAttr(txXSLTAtoms::method, kNameSpaceID_None,
                                 attValue)) {
                if (attValue.Equals(NS_LITERAL_STRING("html"))) {
                    format.mMethod = eHTMLOutput;
                }
                else if (attValue.Equals(NS_LITERAL_STRING("text"))) {
                    format.mMethod = eTextOutput;
                }
                else {
                    format.mMethod = eXMLOutput;
                }
            }

            if (element->getAttr(txXSLTAtoms::version, kNameSpaceID_None,
                                 attValue)) {
                format.mVersion = attValue;
            }

            if (element->getAttr(txXSLTAtoms::encoding, kNameSpaceID_None,
                                 attValue)) {
                format.mEncoding = attValue;
            }

            if (element->getAttr(txXSLTAtoms::omitXmlDeclaration,
                                 kNameSpaceID_None, attValue)) {
                format.mOmitXMLDeclaration =
                    TX_StringEqualsAtom(attValue, txXSLTAtoms::yes) ? eTrue : eFalse;
            }

            if (element->getAttr(txXSLTAtoms::standalone, kNameSpaceID_None,
                                 attValue)) {
                format.mStandalone =
                    TX_StringEqualsAtom(attValue, txXSLTAtoms::yes) ? eTrue : eFalse;
            }

            if (element->getAttr(txXSLTAtoms::doctypePublic,
                                 kNameSpaceID_None, attValue)) {
                format.mPublicId = attValue;
            }

            if (element->getAttr(txXSLTAtoms::doctypeSystem,
                                 kNameSpaceID_None, attValue)) {
                format.mSystemId = attValue;
            }

            if (element->getAttr(txXSLTAtoms::cdataSectionElements,
                                 kNameSpaceID_None, attValue)) {
                txTokenizer tokens(attValue);
                while (tokens.hasMoreTokens()) {
                    txExpandedName* qname = new txExpandedName();
                    if (!qname || NS_FAILED(qname->init(tokens.nextToken(),
                                                        element, PR_FALSE))) {
                        // XXX ErrorReport
                        break;
                    }
                    format.mCDATASectionElements.add(qname);
                }
            }

            if (element->getAttr(txXSLTAtoms::indent, kNameSpaceID_None,
                                 attValue)) {
                format.mIndent =
                    TX_StringEqualsAtom(attValue, txXSLTAtoms::yes) ? eTrue : eFalse;
            }

            if (element->getAttr(txXSLTAtoms::mediaType, kNameSpaceID_None,
                                 attValue)) {
                format.mMediaType = attValue;
            }
        }
        // xsl:template
        else if (localName == txXSLTAtoms::_template) {
            aPs->addTemplate(element, currentFrame);
        }
        // xsl:variable
        else if (localName == txXSLTAtoms::variable) {
            nsAutoString qName;
            element->getAttr(txXSLTAtoms::name, kNameSpaceID_None, qName);
            txExpandedName varName;
            nsresult rv = varName.init(qName, element, MB_FALSE);
            if (NS_SUCCEEDED(rv)) {
                rv = aPs->addGlobalVariable(varName, element, currentFrame,
                                            0);
                if (NS_FAILED(rv)) {
                    aPs->receiveError(NS_LITERAL_STRING("unable to add global xsl:variable"), NS_ERROR_FAILURE);
                }
            }
            else {
                aPs->receiveError(NS_LITERAL_STRING("unable to add global xsl:variable"), NS_ERROR_FAILURE);
            }
        }
        // xsl:preserve-space
        else if (localName == txXSLTAtoms::preserveSpace) {
            nsAutoString elements;
            if (!element->getAttr(txXSLTAtoms::elements,
                                  kNameSpaceID_None, elements)) {
                //-- add error to ErrorObserver
                aPs->receiveError(NS_LITERAL_STRING("missing required 'elements' attribute for xsl:preserve-space"), NS_ERROR_FAILURE);
            }
            else {
                aPs->shouldStripSpace(elements, element,
                                      MB_FALSE,
                                      currentFrame);
            }
        }
        // xsl:strip-space
        else if (localName == txXSLTAtoms::stripSpace) {
            nsAutoString elements;
            if (!element->getAttr(txXSLTAtoms::elements,
                                  kNameSpaceID_None, elements)) {
                //-- add error to ErrorObserver
                aPs->receiveError(NS_LITERAL_STRING("missing required 'elements' attribute for xsl:strip-space"), NS_ERROR_FAILURE);
            }
            else {
                aPs->shouldStripSpace(elements, element,
                                      MB_TRUE,
                                      currentFrame);
            }
        }
        node = node->getNextSibling();
    }
}

ExprResult*
txXSLTProcessor::processVariable(Element* aVariable,
                                 ProcessorState* aPs)
{
    NS_ASSERTION(aVariable, "missing xslVariable");

    //-- check for select attribute
    if (aVariable->hasAttr(txXSLTAtoms::select, kNameSpaceID_None)) {
        Expr* expr = aPs->getExpr(aVariable, ProcessorState::SelectAttr);
        if (!expr)
            return new StringResult(NS_LITERAL_STRING("unable to process variable"));
        return expr->evaluate(aPs->getEvalContext());
    }
    if (aVariable->hasChildNodes()) {
        txResultTreeFragment* rtf = new txResultTreeFragment();
        if (!rtf)
            // XXX ErrorReport: Out of memory
            return 0;
        txXMLEventHandler* previousHandler = aPs->mResultHandler;
        Document* rtfDocument = aPs->getRTFDocument();
        if (!rtfDocument) {
            rtfDocument = createRTFDocument(eXMLOutput);
            aPs->setRTFDocument(rtfDocument);
        }
        NS_ASSERTION(rtfDocument, "No document to create result tree fragments");
        if (!rtfDocument) {
            return 0;
        }
        txRtfHandler rtfHandler(rtfDocument, rtf);
        aPs->mResultHandler = &rtfHandler;
        processChildren(aVariable, aPs);
        NS_ASSERTION(previousHandler, "Setting mResultHandler to NULL!");
        aPs->mResultHandler = previousHandler;
        return rtf;
    }
    return new StringResult();
}

void
txXSLTProcessor::transform(ProcessorState* aPs)
{
    nsresult rv = NS_OK;
    txListIterator frameIter(aPs->getImportFrames());
    ProcessorState::ImportFrame* frame;
    txOutputFormat* outputFormat = aPs->getOutputFormat();
    while ((frame = (ProcessorState::ImportFrame*)frameIter.next())) {
        outputFormat->merge(frame->mOutputFormat);
    }

    txIOutputXMLEventHandler* handler = 0;
    rv = aPs->mOutputHandlerFactory->createHandlerWith(aPs->getOutputFormat(),
                                                       &handler);
    if (NS_FAILED(rv)) {
        return;
    }
    aPs->mOutputHandler = handler;
    aPs->mResultHandler = handler;
    aPs->mOutputHandler->startDocument();

    frame = 0;
    txExpandedName nullMode;
    Node* xslTemplate = aPs->findTemplate(aPs->getEvalContext()->getContextNode(),
                                          nullMode, &frame);
    processMatchedTemplate(xslTemplate, 0, nullMode, frame, aPs);

    aPs->mOutputHandler->endDocument();
}

void
txXSLTProcessor::xslCopy(Element* aAction, ProcessorState* aPs)
{
    Node* node = aPs->getEvalContext()->getContextNode();

    switch (node->getNodeType()) {
        case Node::DOCUMENT_NODE:
        {
            // Just process children
            processChildren(aAction, aPs);
            break;
        }
        case Node::ELEMENT_NODE:
        {
            Element* element = (Element*)node;
            nsAutoString nodeName;
            element->getNodeName(nodeName);
            PRInt32 nsID = element->getNamespaceID();

            NS_ASSERTION(aPs->mResultHandler, "mResultHandler must not be NULL!");
            aPs->mResultHandler->startElement(nodeName, nsID);
            // XXX copy namespace attributes once we have them
            processAttributeSets(aAction, aPs);
            processChildren(aAction, aPs);
            NS_ASSERTION(aPs->mResultHandler, "mResultHandler must not be NULL!");
            aPs->mResultHandler->endElement(nodeName, nsID);
            break;
        }
        default:
        {
            copyNode(node, aPs);
        }
    }
}

void
txXSLTProcessor::xslCopyOf(ExprResult* aExprResult, ProcessorState* aPs)
{
    if (!aExprResult)
        return;

    switch (aExprResult->getResultType()) {
        case ExprResult::NODESET:
        {
            NodeSet* nodes = (NodeSet*)aExprResult;
            int i;
            for (i = 0; i < nodes->size(); i++) {
                Node* node = nodes->get(i);
                copyNode(node, aPs);
            }
            break;
        }
        default:
        {
            nsAutoString value;
            aExprResult->stringValue(value);
            NS_ASSERTION(aPs->mResultHandler, "mResultHandler must not be NULL!");
            aPs->mResultHandler->characters(value);
        }
    }
}
