/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the QtXmlPatterns module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL21$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** 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 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** As a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include <QStringList>

#include "qbuiltintypes_p.h"
#include "qcommonnamespaces_p.h"
#include "qparsercontext_p.h"
#include "qquerytransformparser_p.h"
#include "qxquerytokenizer_p.h"
#include "qpatternistlocale_p.h"

#include "qxslttokenizer_p.h"

QT_BEGIN_NAMESPACE

using namespace QPatternist;

Tokenizer::Token SingleTokenContainer::nextToken(YYLTYPE *const location)
{
    if(m_hasDelivered)
        return Tokenizer::Token(T_END_OF_FILE);
    else
    {
        *location = m_location;
        m_hasDelivered = true;
        return m_token;
    }
}

XSLTTokenizer::XSLTTokenizer(QIODevice *const queryDevice,
                             const QUrl &location,
                             const ReportContext::Ptr &context,
                             const NamePool::Ptr &np) : Tokenizer(location)
                                                      , MaintainingReader<XSLTTokenLookup>(createElementDescriptions(), createStandardAttributes(), context, queryDevice)
                                                      , m_location(location)
                                                      , m_namePool(np)
                                                      /* We initialize after all name constants. */
                                                      , m_validationAlternatives(createValidationAlternatives())
                                                      , m_parseInfo(0)
{
    Q_ASSERT(m_namePool);

    pushState(OutsideDocumentElement);
}

bool XSLTTokenizer::isAnyAttributeAllowed() const
{
    return m_processingMode.top() == ForwardCompatible;
}

void XSLTTokenizer::setParserContext(const ParserContext::Ptr &parseInfo)
{
    m_parseInfo = parseInfo;
}

void XSLTTokenizer::validateElement() const
{
    MaintainingReader<XSLTTokenLookup>::validateElement(currentElementName());
}

QSet<XSLTTokenizer::NodeName> XSLTTokenizer::createStandardAttributes()
{
    QSet<NodeName> retval;
    enum
    {
        ReservedForAttributes = 6
    };

    retval.reserve(6);

    retval.insert(DefaultCollation);
    retval.insert(ExcludeResultPrefixes);
    retval.insert(ExtensionElementPrefixes);
    retval.insert(UseWhen);
    retval.insert(Version);
    retval.insert(XpathDefaultNamespace);

    Q_ASSERT(retval.count() == ReservedForAttributes);

    return retval;
}

ElementDescription<XSLTTokenLookup>::Hash XSLTTokenizer::createElementDescriptions()
{
    ElementDescription<XSLTTokenLookup>::Hash result;
    enum
    {
        ReservedForElements = 40
    };
    result.reserve(ReservedForElements);

    /* xsl:apply-templates */
    {
        ElementDescription<XSLTTokenLookup> &e = result[ApplyTemplates];
        e.optionalAttributes.insert(Select);
        e.optionalAttributes.insert(Mode);
    }

    /* xsl:template */
    {
        ElementDescription<XSLTTokenLookup> &e = result[Template];
        e.optionalAttributes.insert(Match);
        e.optionalAttributes.insert(Name);
        e.optionalAttributes.insert(Mode);
        e.optionalAttributes.insert(Priority);
        e.optionalAttributes.insert(As);
    }

    /* xsl:text, xsl:choose and xsl:otherwise */
    {
        ElementDescription<XSLTTokenLookup> &e = result[Text];
        result.insert(Choose, e);
        result.insert(Otherwise, e);
    }

    /* xsl:stylesheet */
    {
        ElementDescription<XSLTTokenLookup> &e = result[Stylesheet];

        e.requiredAttributes.insert(Version);

        e.optionalAttributes.insert(Id);
        e.optionalAttributes.insert(ExtensionElementPrefixes);
        e.optionalAttributes.insert(ExcludeResultPrefixes);
        e.optionalAttributes.insert(XpathDefaultNamespace);
        e.optionalAttributes.insert(DefaultValidation);
        e.optionalAttributes.insert(DefaultCollation);
        e.optionalAttributes.insert(InputTypeAnnotations);
    }

    /* xsl:transform */
    {
        result[Transform] = result[Stylesheet];
    }

    /* xsl:value-of */
    {
        ElementDescription<XSLTTokenLookup> &e = result[ValueOf];
        e.optionalAttributes.insert(Separator);
        e.optionalAttributes.insert(Select);
    }

    /* xsl:variable */
    {
        ElementDescription<XSLTTokenLookup> &e = result[Variable];

        e.requiredAttributes.insert(Name);

        e.optionalAttributes.insert(Select);
        e.optionalAttributes.insert(As);
    }

    /* xsl:when & xsl:if */
    {
        ElementDescription<XSLTTokenLookup> &e = result[When];

        e.requiredAttributes.insert(Test);

        result.insert(If, e);
    }

    /* xsl:sequence, xsl:for-each */
    {
        ElementDescription<XSLTTokenLookup> &e = result[Sequence];

        e.requiredAttributes.insert(Select);

        result.insert(ForEach, e);
    }

    /* xsl:comment */
    {
        ElementDescription<XSLTTokenLookup> &e = result[XSLTTokenLookup::Comment];

        e.optionalAttributes.insert(Select);
    }

    /* xsl:processing-instruction */
    {
        ElementDescription<XSLTTokenLookup> &e = result[XSLTTokenLookup::ProcessingInstruction];

        e.requiredAttributes.insert(Name);
        e.optionalAttributes.insert(Select);
    }

    /* xsl:document */
    {
        ElementDescription<XSLTTokenLookup> &e = result[Document];

        e.optionalAttributes.insert(Validation);
        e.optionalAttributes.insert(Type);
    }

    /* xsl:element */
    {
        ElementDescription<XSLTTokenLookup> &e = result[Element];

        e.requiredAttributes.insert(Name);

        e.optionalAttributes.insert(Namespace);
        e.optionalAttributes.insert(InheritNamespaces);
        e.optionalAttributes.insert(UseAttributeSets);
        e.optionalAttributes.insert(Validation);
        e.optionalAttributes.insert(Type);
    }

    /* xsl:attribute */
    {
        ElementDescription<XSLTTokenLookup> &e = result[Attribute];

        e.requiredAttributes.insert(Name);

        e.optionalAttributes.insert(Namespace);
        e.optionalAttributes.insert(Select);
        e.optionalAttributes.insert(Separator);
        e.optionalAttributes.insert(Validation);
        e.optionalAttributes.insert(Type);
    }

    /* xsl:function */
    {
        ElementDescription<XSLTTokenLookup> &e = result[Function];

        e.requiredAttributes.insert(Name);

        e.optionalAttributes.insert(As);
        e.optionalAttributes.insert(Override);
    }

    /* xsl:param */
    {
        ElementDescription<XSLTTokenLookup> &e = result[Param];

        e.requiredAttributes.insert(Name);

        e.optionalAttributes.insert(Select);
        e.optionalAttributes.insert(As);
        e.optionalAttributes.insert(Required);
        e.optionalAttributes.insert(Tunnel);
    }

    /* xsl:namespace */
    {
        ElementDescription<XSLTTokenLookup> &e = result[Namespace];

        e.requiredAttributes.insert(Name);
        e.optionalAttributes.insert(Select);
    }

    /* xsl:call-template */
    {
        ElementDescription<XSLTTokenLookup> &e = result[CallTemplate];
        e.requiredAttributes.insert(Name);
    }

    /* xsl:perform-sort */
    {
        ElementDescription<XSLTTokenLookup> &e = result[PerformSort];
        e.requiredAttributes.insert(Select);
    }

    /* xsl:sort */
    {
        ElementDescription<XSLTTokenLookup> &e = result[Sort];

        e.optionalAttributes.reserve(7);
        e.optionalAttributes.insert(Select);
        e.optionalAttributes.insert(Lang);
        e.optionalAttributes.insert(Order);
        e.optionalAttributes.insert(Collation);
        e.optionalAttributes.insert(Stable);
        e.optionalAttributes.insert(CaseOrder);
        e.optionalAttributes.insert(DataType);
    }

    /* xsl:import-schema */
    {
        ElementDescription<XSLTTokenLookup> &e = result[ImportSchema];

        e.optionalAttributes.reserve(2);
        e.optionalAttributes.insert(Namespace);
        e.optionalAttributes.insert(SchemaLocation);
    }

    /* xsl:message */
    {
        ElementDescription<XSLTTokenLookup> &e = result[Message];

        e.optionalAttributes.reserve(2);
        e.optionalAttributes.insert(Select);
        e.optionalAttributes.insert(Terminate);
    }

    /* xsl:copy-of */
    {
        ElementDescription<XSLTTokenLookup> &e = result[CopyOf];

        e.requiredAttributes.insert(Select);

        e.optionalAttributes.reserve(2);
        e.optionalAttributes.insert(CopyNamespaces);
        e.optionalAttributes.insert(Type);
        e.optionalAttributes.insert(Validation);
    }

    /* xsl:copy */
    {
        ElementDescription<XSLTTokenLookup> &e = result[Copy];

        e.optionalAttributes.reserve(5);
        e.optionalAttributes.insert(CopyNamespaces);
        e.optionalAttributes.insert(InheritNamespaces);
        e.optionalAttributes.insert(UseAttributeSets);
        e.optionalAttributes.insert(Type);
        e.optionalAttributes.insert(Validation);
    }

    /* xsl:output */
    {
        ElementDescription<XSLTTokenLookup> &e = result[Output];

        e.optionalAttributes.reserve(17);
        e.optionalAttributes.insert(Name);
        e.optionalAttributes.insert(Method);
        e.optionalAttributes.insert(ByteOrderMark);
        e.optionalAttributes.insert(CdataSectionElements);
        e.optionalAttributes.insert(DoctypePublic);
        e.optionalAttributes.insert(DoctypeSystem);
        e.optionalAttributes.insert(Encoding);
        e.optionalAttributes.insert(EscapeUriAttributes);
        e.optionalAttributes.insert(IncludeContentType);
        e.optionalAttributes.insert(Indent);
        e.optionalAttributes.insert(MediaType);
        e.optionalAttributes.insert(NormalizationForm);
        e.optionalAttributes.insert(OmitXmlDeclaration);
        e.optionalAttributes.insert(Standalone);
        e.optionalAttributes.insert(UndeclarePrefixes);
        e.optionalAttributes.insert(UseCharacterMaps);
        e.optionalAttributes.insert(Version);
    }

    /* xsl:attribute-set */
    {
        ElementDescription<XSLTTokenLookup> &e = result[AttributeSet];

        e.requiredAttributes.insert(Name);
        e.optionalAttributes.insert(UseAttributeSets);
    }

    /* xsl:include and xsl:import. */
    {
        ElementDescription<XSLTTokenLookup> &e = result[Include];
        e.requiredAttributes.insert(Href);
        result[Import] = e;
    }

    /* xsl:with-param */
    {
        ElementDescription<XSLTTokenLookup> &e = result[WithParam];
        e.requiredAttributes.insert(Name);

        e.optionalAttributes.insert(Select);
        e.optionalAttributes.insert(As);
        e.optionalAttributes.insert(Tunnel);
    }

    /* xsl:strip-space */
    {
        ElementDescription<XSLTTokenLookup> &e = result[StripSpace];
        e.requiredAttributes.insert(Elements);

        result.insert(PreserveSpace, e);
    }

    /* xsl:result-document */
    {
        ElementDescription<XSLTTokenLookup> &e = result[ResultDocument];

        e.optionalAttributes.insert(ByteOrderMark);
        e.optionalAttributes.insert(CdataSectionElements);
        e.optionalAttributes.insert(DoctypePublic);
        e.optionalAttributes.insert(DoctypeSystem);
        e.optionalAttributes.insert(Encoding);
        e.optionalAttributes.insert(EscapeUriAttributes);
        e.optionalAttributes.insert(Format);
        e.optionalAttributes.insert(Href);
        e.optionalAttributes.insert(IncludeContentType);
        e.optionalAttributes.insert(Indent);
        e.optionalAttributes.insert(MediaType);
        e.optionalAttributes.insert(Method);
        e.optionalAttributes.insert(NormalizationForm);
        e.optionalAttributes.insert(OmitXmlDeclaration);
        e.optionalAttributes.insert(OutputVersion);
        e.optionalAttributes.insert(Standalone);
        e.optionalAttributes.insert(Type);
        e.optionalAttributes.insert(UndeclarePrefixes);
        e.optionalAttributes.insert(UseCharacterMaps);
        e.optionalAttributes.insert(Validation);
    }

    /* xsl:key */
    {
        ElementDescription<XSLTTokenLookup> &e = result[Key];

        e.requiredAttributes.insert(Name);
        e.requiredAttributes.insert(Match);

        e.optionalAttributes.insert(Use);
        e.optionalAttributes.insert(Collation);
    }

    /* xsl:analyze-string */
    {
        ElementDescription<XSLTTokenLookup> &e = result[AnalyzeString];

        e.requiredAttributes.insert(Select);
        e.requiredAttributes.insert(Regex);

        e.optionalAttributes.insert(Flags);
    }

    /* xsl:matching-substring */
    {
        /* We insert a default constructed value. */
        result[MatchingSubstring];
    }

    /* xsl:non-matching-substring */
    {
        /* We insert a default constructed value. */
        result[NonMatchingSubstring];
    }

    Q_ASSERT(result.count() == ReservedForElements);

    return result;
}

QHash<QString, int> XSLTTokenizer::createValidationAlternatives()
{
    QHash<QString, int> retval;

    retval.insert(QLatin1String("preserve"), 0);
    retval.insert(QLatin1String("strip"), 1);
    retval.insert(QLatin1String("strict"), 2);
    retval.insert(QLatin1String("lax"), 3);

    return retval;
}

bool XSLTTokenizer::whitespaceToSkip() const
{
    return m_stripWhitespace.top() && isWhitespace();
}

void XSLTTokenizer::unexpectedContent(const ReportContext::ErrorCode code) const
{
    QString message;

    ReportContext::ErrorCode effectiveCode = code;

    switch(tokenType())
    {
        case QXmlStreamReader::StartElement:
        {
            if(isXSLT())
            {
                switch(currentElementName())
                {
                    case Include:
                        effectiveCode = ReportContext::XTSE0170;
                        break;
                    case Import:
                        effectiveCode = ReportContext::XTSE0190;
                        break;
                    default:
                        ;
                }
            }

            message = QtXmlPatterns::tr("Element %1 is not allowed at this location.")
                                       .arg(formatKeyword(name()));
            break;
        }
        case QXmlStreamReader::Characters:
        {
            if(whitespaceToSkip())
                return;

            message = QtXmlPatterns::tr("Text nodes are not allowed at this location.");
            break;
        }
        case QXmlStreamReader::Invalid:
        {
            /* It's an issue with well-formedness. */
            message = escape(errorString());
            break;
        }
        default:
            Q_ASSERT(false);
    }

    error(message, effectiveCode);
}

void XSLTTokenizer::checkForParseError() const
{
    if(hasError())
    {
        error(QtXmlPatterns::tr("Parse error: %1").arg(escape(errorString())), ReportContext::XTSE0010);
    }
}

QString XSLTTokenizer::readElementText()
{
    QString result;

    while(!atEnd())
    {
        switch(readNext())
        {
            case QXmlStreamReader::Characters:
            {
                result += text().toString();
                continue;
            }
            case QXmlStreamReader::Comment:
            /* Fallthrough. */
            case QXmlStreamReader::ProcessingInstruction:
                continue;
            case QXmlStreamReader::EndElement:
                return result;
            default:
                unexpectedContent();
        }
    }

    checkForParseError();
    return result;
}

int XSLTTokenizer::commenceScanOnly()
{
    /* Do nothing, return a dummy value. */
    return 0;
}

void XSLTTokenizer::resumeTokenizationFrom(const int position)
{
    /* Do nothing. */
    Q_UNUSED(position);
}

void XSLTTokenizer::handleXSLTVersion(TokenSource::Queue *const to,
                                      QStack<Token> *const queueOnExit,
                                      const bool isXSLTElement,
                                      const QXmlStreamAttributes *atts,
                                      const bool generateCode,
                                      const bool setGlobalVersion)
{
    const QString ns(isXSLTElement ? QString() : CommonNamespaces::XSLT);
    const QXmlStreamAttributes effectiveAtts(atts ? *atts : attributes());

    if(!effectiveAtts.hasAttribute(ns, QLatin1String("version")))
        return;

    const QString attribute(effectiveAtts.value(ns, QLatin1String("version")).toString());
    const AtomicValue::Ptr number(Decimal::fromLexical(attribute));

    if(number->hasError())
    {
        error(QtXmlPatterns::tr("The value of the XSL-T version attribute "
                                           "must be a value of type %1, which %2 isn't.").arg(formatType(m_namePool, BuiltinTypes::xsDecimal),
                                                                                              formatData(attribute)),
              ReportContext::XTSE0110);
    }
    else
    {

        if(generateCode)
        {
            queueToken(Token(T_XSLT_VERSION, attribute), to);
            queueToken(T_CURLY_LBRACE, to);
        }

        const xsDecimal version = number->as<Numeric>()->toDecimal();
        if(version == 2.0)
            m_processingMode.push(NormalProcessing);
        else if(version == 1.0)
        {
            /* See section 3.6 Stylesheet Element discussing this. */
            warning(QtXmlPatterns::tr("Running an XSL-T 1.0 stylesheet with a 2.0 processor."));
            m_processingMode.push(BackwardsCompatible);

            if(setGlobalVersion)
            {
                m_parseInfo->staticContext->setCompatModeEnabled(true);
                m_parseInfo->isBackwardsCompat.push(true);
            }
        }
        else if(version > 2.0)
            m_processingMode.push(ForwardCompatible);
        else if(version < 2.0)
            m_processingMode.push(BackwardsCompatible);
    }

    if(generateCode)
        queueOnExit->push(T_CURLY_RBRACE);
}

void XSLTTokenizer::handleXMLBase(TokenSource::Queue *const to,
                                  QStack<Token> *const queueOnExit,
                                  const bool isInstruction,
                                  const QXmlStreamAttributes *atts)
{
    const QXmlStreamAttributes effectiveAtts(atts ? *atts : m_currentAttributes);

    if(effectiveAtts.hasAttribute(QLatin1String("xml:base")))
    {
        const QStringRef val(effectiveAtts.value(QLatin1String("xml:base")));

        if(!val.isEmpty())
        {
            if(isInstruction)
            {
                queueToken(T_BASEURI, to);
                queueToken(Token(T_STRING_LITERAL, val.toString()), to);
                queueToken(T_CURLY_LBRACE, to);
                queueOnExit->push(T_CURLY_RBRACE);
            }
            else
            {
                queueToken(T_DECLARE, to);
                queueToken(T_BASEURI, to);
                queueToken(T_INTERNAL, to);
                queueToken(Token(T_STRING_LITERAL, val.toString()), to);
                queueToken(T_SEMI_COLON, to);
            }
        }
    }
}

void XSLTTokenizer::handleStandardAttributes(const bool isXSLTElement)
{
    /* We're not necessarily StartElement, that's why we have atts passed in. */
    Q_ASSERT(tokenType() == QXmlStreamReader::StartElement);

    if(m_hasHandledStandardAttributes)
        return;

    m_hasHandledStandardAttributes = true;

    const QString ns(isXSLTElement ? QString() : CommonNamespaces::XSLT);
    const int len = m_currentAttributes.count();

    for(int i = 0; i < len; ++i)
    {
        const QXmlStreamAttribute &att = m_currentAttributes.at(i);

        if(att.qualifiedName() == QLatin1String("xml:space"))
        {
            /* We raise an error if the value is not recognized.
             *
             * Extensible Markup Language (XML) 1.0 (Fourth Edition), 2.10
             * White Space Handling:
             *
             * 'This specification does not give meaning to any value of
             * xml:space other than "default" and "preserve". It is an error
             * for other values to be specified; the XML processor may report
             * the error or may recover by ignoring the attribute specification
             * or by reporting the (erroneous) value to the application.' */
            m_stripWhitespace.push(readToggleAttribute(QLatin1String("xml:space"),
                                                       QLatin1String("default"),
                                                       QLatin1String("preserve"),
                                                       &m_currentAttributes));
        }

        if(att.namespaceUri() != ns)
            continue;

        switch(toToken(att.name()))
        {
            case Type:
            /* Fallthrough. */
            case Validation:
            /* Fallthrough. */
            case UseAttributeSets:
            /* Fallthrough. */
            case Version:
                /* These are handled by other function such as
                 * handleValidationAttributes() and handleXSLTVersion(). */
                continue;
            default:
            {
                if(!isXSLTElement) /* validateElement() will take care of it, and we
                                    * don't want to flag non-standard XSL-T attributes. */
                {
                    error(QtXmlPatterns::tr("Unknown XSL-T attribute %1.")
                                                      .arg(formatKeyword(att.name())),
                          ReportContext::XTSE0805);
                }
            }
        }
    }
}

void XSLTTokenizer::handleValidationAttributes(const bool isLRE) const
{
    Q_ASSERT(tokenType() == QXmlStreamReader::StartElement);

    const QString ns(isLRE ? QString() : CommonNamespaces::XSLT);

    const bool hasValidation = hasAttribute(ns, QLatin1String("validation"));
    const bool hasType = hasAttribute(ns, QLatin1String("type"));

    if(!hasType && !hasValidation)
        return;

    if(hasType && hasValidation)
    {
        error(QtXmlPatterns::tr("Attribute %1 and %2 are mutually exclusive.")
                                          .arg(formatKeyword(QLatin1String("validation")),
                                               formatKeyword(QLatin1String("type"))),
              ReportContext::XTSE1505);
    }

    /* QXmlStreamReader surely doesn't make this easy. */
    QXmlStreamAttribute validationAttribute;
    int len = m_currentAttributes.count();

    for(int i = 0; i < len; ++i)
    {
        const QXmlStreamAttribute &at = m_currentAttributes.at(i);
        if(at.name() == QLatin1String("validation") && at.namespaceUri() == ns)
            validationAttribute = at;
    }

    Q_ASSERT_X(!validationAttribute.name().isNull(), Q_FUNC_INFO,
               "We should always find the attribute.");

    /* We don't care about the return value, we just want to check it's a valid
     * one. */
    readAlternativeAttribute(m_validationAlternatives,
                             validationAttribute);
}

Tokenizer::Token XSLTTokenizer::nextToken(YYLTYPE *const sourceLocator)
{
    Q_UNUSED(sourceLocator);

    if(m_tokenSource.isEmpty())
    {
        switch(m_state.top())
        {
            case OutsideDocumentElement:
                outsideDocumentElement();
                break;
            case InsideStylesheetModule:
                insideStylesheetModule();
                break;
            case InsideSequenceConstructor:
                insideSequenceConstructor(&m_tokenSource);
                break;
        }

        if(m_tokenSource.isEmpty())
        {
            *sourceLocator = currentSourceLocator();
            return Token(T_END_OF_FILE);
        }
        else
            return m_tokenSource.head()->nextToken(sourceLocator);
    }
    else
    {
        do
        {
            const Token candidate(m_tokenSource.head()->nextToken(sourceLocator));
            if (candidate.type == T_END_OF_FILE)
                m_tokenSource.dequeue();
            else
                return candidate;
        }
        while(!m_tokenSource.isEmpty());

        /* Now we will resume parsing inside the regular XSL-T(XML) file. */
        return nextToken(sourceLocator);
    }
}

bool XSLTTokenizer::isElement(const XSLTTokenLookup::NodeName &name) const
{
    Q_ASSERT(isXSLT());
    Q_ASSERT(tokenType() == QXmlStreamReader::StartElement ||
             tokenType() == QXmlStreamReader::EndElement);

    return currentElementName() == name;
}

inline bool XSLTTokenizer::isXSLT() const
{
    Q_ASSERT_X(tokenType() == QXmlStreamReader::StartElement ||
               tokenType() == QXmlStreamReader::EndElement,
               Q_FUNC_INFO, "The current token state must be StartElement or EndElement.");
    /* Possible optimization: let MaintainingReader set an m_isXSLT which we
     * read. */
    return namespaceUri() == CommonNamespaces::XSLT;
}

void XSLTTokenizer::queueOnExit(QStack<Token> &source,
                                TokenSource::Queue *const destination)
{
    while(!source.isEmpty())
        queueToken(source.pop(), destination);
}

void XSLTTokenizer::outsideDocumentElement()
{
    while(!atEnd())
    {
        switch(readNext())
        {
            case QXmlStreamReader::StartElement:
            {
                /* First, we synthesize one of the built-in templates,
                 * see section 6.6 Built-in Template Rules.
                 *
                 * Note that insideStylesheetModule() can be called multiple
                 * times so we can't do it there.  */
                {
                    /* Start with the one for text nodes and attributes.
                     * declare template matches (text() | @*) mode #all
                     * {
                     *      text{.}
                     * };
                     */

                    /* declare template matches (text() | @*) */
                    queueToken(T_DECLARE, &m_tokenSource);
                    queueToken(T_TEMPLATE, &m_tokenSource);
                    queueToken(T_MATCHES, &m_tokenSource);
                    queueToken(T_LPAREN, &m_tokenSource);
                    queueToken(T_TEXT, &m_tokenSource);
                    queueToken(T_LPAREN, &m_tokenSource);
                    queueToken(T_RPAREN, &m_tokenSource);
                    queueToken(T_BAR, &m_tokenSource);
                    queueToken(T_AT_SIGN, &m_tokenSource);
                    queueToken(T_STAR, &m_tokenSource);
                    queueToken(T_RPAREN, &m_tokenSource);

                    /* mode #all */
                    queueToken(T_MODE, &m_tokenSource);
                    queueToken(Token(T_NCNAME, QLatin1String("#all")), &m_tokenSource);
                    queueToken(T_CURLY_LBRACE, &m_tokenSource);

                    /* text{.} { */
                    queueToken(T_TEXT, &m_tokenSource);
                    queueToken(T_CURLY_LBRACE, &m_tokenSource);
                    queueToken(T_DOT, &m_tokenSource);
                    queueToken(T_CURLY_RBRACE, &m_tokenSource);

                    /* }; */
                    queueToken(T_CURLY_RBRACE, &m_tokenSource);
                    queueToken(T_SEMI_COLON, &m_tokenSource);
                }

                if(isXSLT() && isStylesheetElement())
                {
                    handleStandardAttributes(true);
                    QStack<Token> onExitTokens;
                    handleXMLBase(&m_tokenSource, &onExitTokens, false);
                    handleXSLTVersion(&m_tokenSource, &onExitTokens, true, 0, false, true);
                    validateElement();
                    queueNamespaceDeclarations(&m_tokenSource, 0, true);

                    /* We're a regular stylesheet. */

                    pushState(InsideStylesheetModule);
                    insideStylesheetModule();
                }
                else
                {
                    /* We're a simplified stylesheet. */

                    if(!hasAttribute(CommonNamespaces::XSLT, QLatin1String("version")))
                    {
                        error(QtXmlPatterns::tr("In a simplified stylesheet module, attribute %1 must be present.")
                                                          .arg(formatKeyword(QLatin1String("version"))),
                              ReportContext::XTSE0010);
                    }

                    QStack<Token> onExitTokens;

                    /* We synthesize this as exemplified in
                     * 3.7 Simplified Stylesheet Modules. */
                    queueToken(T_DECLARE, &m_tokenSource);
                    queueToken(T_TEMPLATE, &m_tokenSource);
                    queueToken(T_MATCHES, &m_tokenSource);
                    queueToken(T_LPAREN, &m_tokenSource);
                    queueToken(T_SLASH, &m_tokenSource);
                    queueToken(T_RPAREN, &m_tokenSource);
                    queueToken(T_CURLY_LBRACE, &m_tokenSource);
                    pushState(InsideSequenceConstructor);

                    handleXSLTVersion(&m_tokenSource, &onExitTokens, false, 0, true);
                    handleStandardAttributes(false);

                    insideSequenceConstructor(&m_tokenSource, false);

                    queueOnExit(onExitTokens, &m_tokenSource);
                    queueToken(T_CURLY_RBRACE, &m_tokenSource);
                    queueToken(T_CURLY_RBRACE, &m_tokenSource);
                    queueToken(T_SEMI_COLON, &m_tokenSource);
                }

                queueToken(T_APPLY_TEMPLATE, &m_tokenSource);
                queueToken(T_LPAREN, &m_tokenSource);
                queueToken(T_RPAREN, &m_tokenSource);

                break;
            }
            default:
                /* Do nothing. */;
        }
    }
    checkForParseError();
}

void XSLTTokenizer::queueToken(const Token &token,
                               TokenSource::Queue *const to)
{
    TokenSource::Queue *const effective = to ? to : &m_tokenSource;

    effective->enqueue(TokenSource::Ptr(new SingleTokenContainer(token, currentSourceLocator())));
}

void XSLTTokenizer::pushState(const State nextState)
{
    m_state.push(nextState);
}

void XSLTTokenizer::leaveState()
{
    m_state.pop();
}

void XSLTTokenizer::insideTemplate()
{
    const bool hasPriority  = hasAttribute(QLatin1String("priority"));
    const bool hasMatch     = hasAttribute(QLatin1String("match"));
    const bool hasName      = hasAttribute(QLatin1String("name"));
    const bool hasMode      = hasAttribute(QLatin1String("mode"));
    const bool hasAs        = hasAttribute(QLatin1String("as"));

    if(!hasMatch &&
       (hasMode ||
        hasPriority))
    {
        error(QtXmlPatterns::tr("If element %1 has no attribute %2, it cannot have attribute %3 or %4.")
                         .arg(formatKeyword(QLatin1String("template")),
                              formatKeyword(QLatin1String("match")),
                              formatKeyword(QLatin1String("mode")),
                              formatKeyword(QLatin1String("priority"))),
              ReportContext::XTSE0500);
    }
    else if(!hasMatch && !hasName)
    {
        error(QtXmlPatterns::tr("Element %1 must have at least one of the attributes %2 or %3.")
                         .arg(formatKeyword(QLatin1String("template")),
                              formatKeyword(QLatin1String("name")),
                              formatKeyword(QLatin1String("match"))),
              ReportContext::XTSE0500);
    }

    queueToken(T_DECLARE, &m_tokenSource);
    queueToken(T_TEMPLATE, &m_tokenSource);

    if(hasName)
    {
        queueToken(T_NAME, &m_tokenSource);
        queueToken(Token(T_QNAME, readAttribute(QLatin1String("name"))), &m_tokenSource);
    }

    if(hasMatch)
    {
        queueToken(T_MATCHES, &m_tokenSource);
        queueExpression(readAttribute(QLatin1String("match")), &m_tokenSource);
    }

    if(hasMode)
    {
        const QString modeString(readAttribute(QLatin1String("mode")).simplified());

        if(modeString.isEmpty())
        {
            error(QtXmlPatterns::tr("At least one mode must be specified in the %1-attribute on element %2.")
                             .arg(formatKeyword(QLatin1String("mode")),
                                  formatKeyword(QLatin1String("template"))),
                  ReportContext::XTSE0500);
        }

        queueToken(T_MODE, &m_tokenSource);

        const QStringList modeList(modeString.split(QLatin1Char(' ')));

        for(int i = 0; i < modeList.count(); ++i)
        {
            const QString &mode = modeList.at(i);

            queueToken(Token(mode.contains(QLatin1Char(':')) ? T_QNAME : T_NCNAME, mode), &m_tokenSource);

            if(i < modeList.count() - 1)
                queueToken(T_COMMA, &m_tokenSource);
        }
    }

    if(hasPriority)
    {
        queueToken(T_PRIORITY, &m_tokenSource);
        queueToken(Token(T_STRING_LITERAL, readAttribute(QLatin1String("priority"))), &m_tokenSource);
    }

    QStack<Token> onExitTokens;
    Q_ASSERT(tokenType() == QXmlStreamReader::StartElement);

    /* queueParams moves the reader so we need to freeze the attributes. */
    const QXmlStreamAttributes atts(m_currentAttributes);
    handleStandardAttributes(true);
    queueToken(T_LPAREN, &m_tokenSource);
    queueParams(Template, &m_tokenSource);
    queueToken(T_RPAREN, &m_tokenSource);

    if(hasAs)
    {
        queueToken(T_AS, &m_tokenSource);
        queueSequenceType(atts.value(QLatin1String("as")).toString());
    }

    queueToken(T_CURLY_LBRACE, &m_tokenSource);

    handleXMLBase(&m_tokenSource, &onExitTokens, true, &atts);
    handleXSLTVersion(&m_tokenSource, &onExitTokens, true, &atts);
    pushState(InsideSequenceConstructor);
    startStorageOfCurrent(&m_tokenSource);
    insideSequenceConstructor(&m_tokenSource, onExitTokens, false);
    queueOnExit(onExitTokens, &m_tokenSource);
}

void XSLTTokenizer::queueExpression(const QString &expr,
                                    TokenSource::Queue *const to,
                                    const bool wrapWithParantheses)
{
    TokenSource::Queue *const effectiveTo = to ? to : &m_tokenSource;

    if(wrapWithParantheses)
        queueToken(T_LPAREN, effectiveTo);

    effectiveTo->enqueue(TokenSource::Ptr(new XQueryTokenizer(expr, queryURI())));

    if(wrapWithParantheses)
        queueToken(T_RPAREN, effectiveTo);
}

void XSLTTokenizer::queueAVT(const QString &expr,
                             TokenSource::Queue *const to)
{
    queueToken(T_AVT, to);
    queueToken(T_LPAREN, to);
    to->enqueue(TokenSource::Ptr(new XQueryTokenizer(expr, queryURI(),
                                                               XQueryTokenizer::QuotAttributeContent)));
    queueToken(T_RPAREN, to);
}

void XSLTTokenizer::queueSequenceType(const QString &expr)
{
    m_tokenSource.enqueue(TokenSource::Ptr(new XQueryTokenizer(expr, queryURI(),
                                                                         XQueryTokenizer::ItemType)));
}

void XSLTTokenizer::commencingExpression(bool &hasWrittenExpression,
                                         TokenSource::Queue *const to)
{
    if(hasWrittenExpression)
        queueToken(T_COMMA, to);
    else
        hasWrittenExpression = true;
}

void XSLTTokenizer::queueEmptySequence(TokenSource::Queue *const to)
{
    queueToken(T_LPAREN, to);
    queueToken(T_RPAREN, to);
}

void XSLTTokenizer::insideChoose(TokenSource::Queue *const to)
{
    Q_ASSERT(tokenType() == QXmlStreamReader::StartElement);
    bool hasHandledOtherwise = false;
    bool hasEncounteredAtLeastOneWhen = false;

    while(!atEnd())
    {
        switch(readNext())
        {
            case QXmlStreamReader::StartElement:
            {
                if(isXSLT())
                {
                    QStack<Token> onExitTokens;
                    handleStandardAttributes(true);
                    validateElement();

                    switch(currentElementName())
                    {
                        case When:
                        {
                            if(hasHandledOtherwise)
                            {
                                error(QtXmlPatterns::tr("Element %1 must come last.")
                                                                  .arg(formatKeyword(QLatin1String("otherwise"))),
                                      ReportContext::XTSE0010);
                            }

                            queueToken(T_IF, to);
                            queueToken(T_LPAREN, to);
                            queueExpression(readAttribute(QLatin1String("test")), to);
                            queueToken(T_RPAREN, to);
                            queueToken(T_THEN, to);
                            queueToken(T_LPAREN, to);
                            pushState(InsideSequenceConstructor);
                            insideSequenceConstructor(to);
                            queueToken(T_RPAREN, to);
                            Q_ASSERT(tokenType() == QXmlStreamReader::EndElement);
                            queueToken(T_ELSE, to);
                            hasEncounteredAtLeastOneWhen = true;
                            queueOnExit(onExitTokens, to);
                            break;
                        }
                        case Otherwise:
                        {
                            if(!hasEncounteredAtLeastOneWhen)
                            {
                                error(QtXmlPatterns::tr("At least one %1-element must occur before %2.")
                                                                  .arg(formatKeyword(QLatin1String("when")),
                                                                       formatKeyword(QLatin1String("otherwise"))),
                                      ReportContext::XTSE0010);
                            }
                            else if(hasHandledOtherwise)
                            {
                                error(QtXmlPatterns::tr("Only one %1-element can appear.")
                                                                  .arg(formatKeyword(QLatin1String("otherwise"))),
                                      ReportContext::XTSE0010);
                            }

                            pushState(InsideSequenceConstructor);
                            queueToken(T_LPAREN, to);
                            insideSequenceConstructor(to, to);
                            queueToken(T_RPAREN, to);
                            hasHandledOtherwise = true;
                            queueOnExit(onExitTokens, to);
                            break;
                        }
                        default:
                            unexpectedContent();
                    }
                }
                else
                    unexpectedContent();
                break;
            }
            case QXmlStreamReader::EndElement:
            {
                if(isXSLT())
                {
                    switch(currentElementName())
                    {
                        case Choose:
                        {
                            if(!hasEncounteredAtLeastOneWhen)
                            {
                                error(QtXmlPatterns::tr("At least one %1-element must occur inside %2.")
                                                                  .arg(formatKeyword(QLatin1String("when")),
                                                                       formatKeyword(QLatin1String("choose"))),
                                      ReportContext::XTSE0010);
                            }

                            if(!hasHandledOtherwise)
                                queueEmptySequence(to);
                            return;
                        }
                        case Otherwise:
                            continue;
                        default:
                            unexpectedContent();
                    }
                }
                else
                    unexpectedContent();
                break;
            }
            case QXmlStreamReader::Comment:
            /* Fallthrough. */
            case QXmlStreamReader::ProcessingInstruction:
                continue;
            case QXmlStreamReader::Characters:
            {
                /* We ignore regardless of what xml:space says, see step 4 in
                 * 4.2 Stripping Whitespace from the Stylesheet. */
                if(isWhitespace())
                    continue;
                /* Fallthrough. */
            }
            default:
                /* Fallthrough. */
                unexpectedContent();
                break;
        }
    }
    checkForParseError();
}

bool XSLTTokenizer::queueSelectOrSequenceConstructor(const ReportContext::ErrorCode code,
                                                     const bool emptynessAllowed,
                                                     TokenSource::Queue *const to,
                                                     const QXmlStreamAttributes *const attsP,
                                                     const bool queueEmptyOnEmpty)
{
    Q_ASSERT(tokenType() == QXmlStreamReader::StartElement || attsP);
    const NodeName elementName(currentElementName());
    const QXmlStreamAttributes atts(attsP ? *attsP : m_currentAttributes);

    if(atts.hasAttribute(QLatin1String("select")))
    {
        queueExpression(atts.value(QLatin1String("select")).toString(), to);

        /* First, verify that we don't have a body. */
        if(skipSubTree(true))
        {
            error(QtXmlPatterns::tr("When attribute %1 is present on %2, a sequence "
                                               "constructor cannot be used.").arg(formatKeyword(QLatin1String("select")),
                                                                                  formatKeyword(toString(elementName))),
                  code);
        }

        return true;
    }
    else
    {
        pushState(InsideSequenceConstructor);
        if(!insideSequenceConstructor(to, true, queueEmptyOnEmpty) && !emptynessAllowed)
        {
            error(QtXmlPatterns::tr("Element %1 must have either a %2-attribute "
                                               "or a sequence constructor.").arg(formatKeyword(toString(elementName)),
                                                                                 formatKeyword(QLatin1String("select"))),
                  code);

        }

        return false;
    }
}

void XSLTTokenizer::queueSimpleContentConstructor(const ReportContext::ErrorCode code,
                                                  const bool emptynessAllowed,
                                                  TokenSource::Queue *const to,
                                                  const bool selectOnlyFirst)
{
    queueToken(T_INTERNAL_NAME, to);
    queueToken(Token(T_NCNAME, QLatin1String("generic-string-join")), to);
    queueToken(T_LPAREN, to);

    /* We have to read the attribute before calling
     * queueSelectOrSequenceConstructor(), since it advances the reader. */
    const bool hasSeparator = m_currentAttributes.hasAttribute(QLatin1String("separator"));
    const QString separatorAVT(m_currentAttributes.value(QLatin1String("separator")).toString());

    queueToken(T_LPAREN, to);
    const bool viaSelectAttribute = queueSelectOrSequenceConstructor(code, emptynessAllowed, to);
    queueToken(T_RPAREN, to);

    if(selectOnlyFirst)
    {
        queueToken(T_LBRACKET, to);
        queueToken(Token(T_NUMBER, QChar::fromLatin1('1')), to);
        queueToken(T_RBRACKET, to);
    }

    queueToken(T_COMMA, to);

    if(hasSeparator)
        queueAVT(separatorAVT, to);
    else
    {
        /* The default value depends on whether the value is from @select, or from
         * the sequence constructor. */
        queueToken(Token(T_STRING_LITERAL, viaSelectAttribute ? QString(QLatin1Char(' '))
                                                            : QString()),
                   to);
    }

    queueToken(T_RPAREN, to);
}

void XSLTTokenizer::queueTextConstructor(QString &chars,
                                         bool &hasWrittenExpression,
                                         TokenSource::Queue *const to)
{
    if(!chars.isEmpty())
    {
        commencingExpression(hasWrittenExpression, to);
        queueToken(T_TEXT, to);
        queueToken(T_CURLY_LBRACE, to);
        queueToken(Token(T_STRING_LITERAL, chars), to);
        queueToken(T_CURLY_RBRACE, to);
        chars.clear();
    }
}

void XSLTTokenizer::queueVariableDeclaration(const VariableType variableType,
                                             TokenSource::Queue *const to)
{
    Q_ASSERT(tokenType() == QXmlStreamReader::StartElement);

    if(variableType == VariableInstruction)
    {
        queueToken(T_LET, to);
        queueToken(T_INTERNAL, to);
    }
    else if(variableType == VariableDeclaration || variableType == GlobalParameter)
    {
        queueToken(T_DECLARE, to);
        queueToken(T_VARIABLE, to);
        queueToken(T_INTERNAL, to);
    }

    queueToken(T_DOLLAR, to);

    queueExpression(readAttribute(QLatin1String("name")), to, false);

    const bool hasAs = m_currentAttributes.hasAttribute(QLatin1String("as"));
    if(hasAs)
    {
        queueToken(T_AS, to);
        queueSequenceType(m_currentAttributes.value(QLatin1String("as")).toString());
    }

    if(variableType == FunctionParameter)
    {
        skipBodyOfParam(ReportContext::XTSE0760);
        return;
    }

    /* We must do this here, because queueSelectOrSequenceConstructor()
     * advances the reader. */
    const bool hasSelect = hasAttribute(QLatin1String("select"));
    const bool isRequired = hasAttribute(QLatin1String("required")) ? attributeYesNo(QLatin1String("required")) : false;

    TokenSource::Queue storage;
    queueSelectOrSequenceConstructor(ReportContext::XTSE0620, true, &storage, 0, false);

    /* XSL-T has some wicked rules, see
     * 9.3 Values of Variables and Parameters. */

    const bool hasQueuedContent = !storage.isEmpty();

    /* The syntax for global parameters is:
     *
     * declare variable $var external := 'defaultValue';
     */
    if(variableType == GlobalParameter)
        queueToken(T_EXTERNAL, to);

    if(isRequired)
    {
        if(hasQueuedContent)
        {
            error(QtXmlPatterns::tr("When a parameter is required, a default value "
                                               "cannot be supplied through a %1-attribute or "
                                               "a sequence constructor.").arg(formatKeyword(QLatin1String("select"))),
                  ReportContext::XTSE0010);
        }
    }
    else
    {
        if(hasQueuedContent)
        {
            queueToken(T_ASSIGN, to);

            if(!hasSelect && !hasAs && !hasQueuedContent)
                queueToken(Token(T_STRING_LITERAL, QString()), to);
            else if(hasAs || hasSelect)
                queueToken(T_LPAREN, to);
            else
            {
                queueToken(T_DOCUMENT, to);
                queueToken(T_INTERNAL, to);
                queueToken(T_CURLY_LBRACE, to);
            }
        }
        else
        {
            if(!hasAs)
            {
                queueToken(T_ASSIGN, to);
                queueToken(Token(T_STRING_LITERAL, QString()), to);
            }
            else if(variableType == VariableDeclaration || variableType == VariableInstruction)
            {
                queueToken(T_ASSIGN, to);
                queueEmptySequence(to);
            }
        }

        /* storage has tokens if hasSelect or hasQueuedContent is true. */
        if(hasSelect | hasQueuedContent)
            *to += storage;

        if(hasQueuedContent)
        {
            if(!hasSelect && !hasAs && !hasQueuedContent)
                queueToken(Token(T_STRING_LITERAL, QString()), to);
            else if(hasAs || hasSelect)
                queueToken(T_RPAREN, to);
            else
                queueToken(T_CURLY_RBRACE, to);
        }
    }

    if(variableType == VariableInstruction)
        queueToken(T_RETURN, to);
    else if(variableType == VariableDeclaration || variableType == GlobalParameter)
        queueToken(T_SEMI_COLON, to);
}

void XSLTTokenizer::startStorageOfCurrent(TokenSource::Queue *const to)
{
    queueToken(T_CURRENT, to);
    queueToken(T_CURLY_LBRACE, to);
}

void XSLTTokenizer::endStorageOfCurrent(TokenSource::Queue *const to)
{
    queueToken(T_CURLY_RBRACE, to);
}

void XSLTTokenizer::queueNamespaceDeclarations(TokenSource::Queue *const to,
                                               QStack<Token> *const queueOnExit,
                                               const bool isDeclaration)
{
    Q_ASSERT(tokenType() == QXmlStreamReader::StartElement);
    Q_ASSERT_X(isDeclaration || queueOnExit,
               Q_FUNC_INFO,
               "If isDeclaration is false, queueOnExit must be passed.");

    const QXmlStreamNamespaceDeclarations nss(namespaceDeclarations());

    for(int i = 0; i < nss.count(); ++i)
    {
        const QXmlStreamNamespaceDeclaration &at = nss.at(i);
        queueToken(T_DECLARE, to);
        queueToken(T_NAMESPACE, to);
        queueToken(Token(T_NCNAME, at.prefix().toString()), to);
        queueToken(T_G_EQ, to);
        queueToken(Token(T_STRING_LITERAL, at.namespaceUri().toString()), to);

        if(isDeclaration)
        {
            queueToken(T_INTERNAL, to);
            queueToken(T_SEMI_COLON, to);
        }
        else
        {
            queueToken(T_CURLY_LBRACE, to);
            queueOnExit->push(T_CURLY_RBRACE);
        }
    }
}

bool XSLTTokenizer::insideSequenceConstructor(TokenSource::Queue *const to,
                                              const bool initialAdvance,
                                              const bool queueEmptyOnEmpty)
{
    QStack<Token> onExitTokens;
    return insideSequenceConstructor(to, onExitTokens, initialAdvance, queueEmptyOnEmpty);
}

bool XSLTTokenizer::insideSequenceConstructor(TokenSource::Queue *const to,
                                              QStack<Token> &onExitTokens,
                                              const bool initialAdvance,
                                              const bool queueEmptyOnEmpty)
{
    bool effectiveInitialAdvance = initialAdvance;
    bool hasWrittenExpression = false;

    /* Buffer which all text nodes, that might be split up by comments,
     * processing instructions and CDATA sections, are appended to. */
    QString characters;

    while(!atEnd())
    {
        if(effectiveInitialAdvance)
            readNext();
        else
            effectiveInitialAdvance = true;

        switch(tokenType())
        {
            case QXmlStreamReader::StartElement:
            {
                queueTextConstructor(characters, hasWrittenExpression, to);
                handleXMLBase(to, &onExitTokens);

                pushState(InsideSequenceConstructor);

                commencingExpression(hasWrittenExpression, to);

                if(isXSLT())
                {
                    handleXSLTVersion(&m_tokenSource, &onExitTokens, true);
                    handleStandardAttributes(true);
                    validateElement();

                    queueNamespaceDeclarations(to, &onExitTokens);

                    switch(currentElementName())
                    {
                        case If:
                        {
                            queueToken(T_IF, to);
                            queueToken(T_LPAREN, to);

                            queueExpression(readAttribute(QLatin1String("test")), to);
                            queueToken(T_RPAREN, to);
                            queueToken(T_THEN, to);

                            queueToken(T_LPAREN, to);
                            pushState(InsideSequenceConstructor);
                            insideSequenceConstructor(to);

                            break;
                        }
                        case Choose:
                        {
                            insideChoose(to);
                            break;
                        }
                        case ValueOf:
                        {
                            /* We generate a computed text node constructor. */
                            queueToken(T_TEXT, to);
                            queueToken(T_CURLY_LBRACE, to);

                            queueSimpleContentConstructor(ReportContext::XTSE0870, true, to,
                                                          !hasAttribute(QLatin1String("separator")) && m_processingMode.top() == BackwardsCompatible);
                            queueToken(T_CURLY_RBRACE, to);
                            break;
                        }
                        case Sequence:
                        {
                            queueExpression(readAttribute(QLatin1String("select")), to);
                            parseFallbacksOnly();
                            break;
                        }
                        case Text:
                        {
                            queueToken(T_TEXT, to);
                            queueToken(T_CURLY_LBRACE, to);

                            queueToken(Token(T_STRING_LITERAL, readElementText()), to);
                            queueToken(T_CURLY_RBRACE, to);
                            break;
                        }
                        case Variable:
                        {
                            queueVariableDeclaration(VariableInstruction, to);

                            /* We wrap the children in parantheses since we may
                             * queue several expressions using the comma operator,
                             * and in that case the let-binding is only in-scope
                             * for the first expression. */
                            queueToken(T_LPAREN, to);

                            /* We don't want a comma outputted, we're expecting an
                             * expression now. */
                            hasWrittenExpression = false;

                            onExitTokens.push(T_RPAREN);

                            break;
                        }
                        case CallTemplate:
                        {
                            queueToken(T_CALL_TEMPLATE, to);
                            queueToken(Token(T_QNAME, readAttribute(QLatin1String("name"))), to);
                            queueToken(T_LPAREN, to);
                            queueWithParams(CallTemplate, to);
                            queueToken(T_RPAREN, to);
                            break;
                        }
                        case ForEach:
                        {
                            queueExpression(readAttribute(QLatin1String("select")), to);
                            queueToken(T_MAP, to);
                            pushState(InsideSequenceConstructor);

                            TokenSource::Queue sorts;
                            queueSorting(false, &sorts);


                            if(sorts.isEmpty())
                            {
                                startStorageOfCurrent(to);
                                insideSequenceConstructor(to, false);
                                endStorageOfCurrent(to);
                            }
                            else
                            {
                                queueToken(T_SORT, to);
                                *to += sorts;
                                queueToken(T_RETURN, to);
                                startStorageOfCurrent(to);
                                insideSequenceConstructor(to, false);
                                endStorageOfCurrent(to);
                                queueToken(T_END_SORT, to);
                            }

                            break;
                        }
                        case XSLTTokenLookup::Comment:
                        {
                            queueToken(T_COMMENT, to);
                            queueToken(T_INTERNAL, to);
                            queueToken(T_CURLY_LBRACE, to);
                            queueSelectOrSequenceConstructor(ReportContext::XTSE0940, true, to);
                            queueToken(T_CURLY_RBRACE, to);
                            break;
                        }
                        case CopyOf:
                        {
                            queueExpression(readAttribute(QLatin1String("select")), to);
                            // TODO

                            if(readNext() == QXmlStreamReader::EndElement)
                                break;
                            else
                            {
                                error(QtXmlPatterns::tr("Element %1 cannot have children.").arg(formatKeyword(QLatin1String("copy-of"))),
                                      ReportContext::XTSE0010);
                            }
                            break;
                        }
                        case AnalyzeString:
                        {
                            // TODO
                            skipSubTree();
                            break;
                        }
                        case ResultDocument:
                        {
                            // TODO
                            pushState(InsideSequenceConstructor);
                            insideSequenceConstructor(to);
                            break;
                        }
                        case Copy:
                        {
                            /* We translate:
                             *      <xsl:copy>expr</xsl:copy>
                             * into:
                             *
                             *  let $body := expr
                             *  return
                             *      if(self::element()) then
                             *          element internal {node-name()} {$body}
                             *      else if(self::document-node()) then
                             *          document internal {$body}
                             *      else (: This includes comments, processing-instructions,
                             *              attributes, and comments. :)
                             *          .
                             *
                             * TODO node identity is the same as the old node.
                             * TODO namespace bindings are lost when elements are constructed
                             */

                            /* let $body := expr */
                            queueToken(T_LET, to);
                            queueToken(T_INTERNAL, to);
                            queueToken(T_DOLLAR, to);
                            queueToken(Token(T_NCNAME, QString(QLatin1Char('b'))), to); // TODO we need an internal name
                            queueToken(T_ASSIGN, to);
                            queueToken(T_LPAREN, to);
                            pushState(InsideSequenceConstructor);
                            /* Don't queue an empty sequence, we want the dot. */
                            insideSequenceConstructor(to);
                            queueToken(T_RPAREN, to);
                            queueToken(T_RETURN, to);

                            /* if(self::element()) then */
                            queueToken(T_IF, to);
                            queueToken(T_LPAREN, to);
                            queueToken(T_SELF, to);
                            queueToken(T_COLONCOLON, to);
                            queueToken(T_ELEMENT, to);
                            queueToken(T_LPAREN, to);
                            queueToken(T_RPAREN, to);
                            queueToken(T_RPAREN, to);
                            queueToken(T_THEN, to);

                            /* element internal {node-name()} {$body} */
                            queueToken(T_ELEMENT, to);
                            queueToken(T_INTERNAL, to);
                            queueToken(T_CURLY_LBRACE, to);
                            queueToken(Token(T_NCNAME, QLatin1String("node-name")), to); // TODO what if the default ns changes?
                            queueToken(T_LPAREN, to);
                            queueToken(T_DOT, to);
                            queueToken(T_RPAREN, to);
                            queueToken(T_CURLY_RBRACE, to);
                            queueToken(T_CURLY_LBRACE, to);
                            queueToken(T_DOLLAR, to);
                            queueToken(Token(T_NCNAME, QString(QLatin1Char('b'))), to); // TODO we need an internal name
                            queueToken(T_CURLY_RBRACE, to);

                            /* else if(self::document-node()) then */
                            queueToken(T_ELSE, to);
                            queueToken(T_IF, to);
                            queueToken(T_LPAREN, to);
                            queueToken(T_SELF, to);
                            queueToken(T_COLONCOLON, to);
                            queueToken(T_DOCUMENT_NODE, to);
                            queueToken(T_LPAREN, to);
                            queueToken(T_RPAREN, to);
                            queueToken(T_RPAREN, to);
                            queueToken(T_THEN, to);

                            /* document internal {$body} */
                            queueToken(T_DOCUMENT, to);
                            queueToken(T_INTERNAL, to);
                            queueToken(T_CURLY_LBRACE, to);
                            queueToken(T_DOLLAR, to);
                            queueToken(Token(T_NCNAME, QString(QLatin1Char('b'))), to); // TODO we need an internal name
                            queueToken(T_CURLY_RBRACE, to);

                            /* else . */
                            queueToken(T_ELSE, to);
                            queueToken(T_DOT, to);

                            break;
                        }
                        case XSLTTokenLookup::ProcessingInstruction:
                        {
                            queueToken(T_PROCESSING_INSTRUCTION, to);
                            queueToken(T_CURLY_LBRACE, to);
                            queueAVT(readAttribute(QLatin1String("name")), to);
                            queueToken(T_CURLY_RBRACE, to);
                            queueToken(T_CURLY_LBRACE, to);
                            queueSelectOrSequenceConstructor(ReportContext::XTSE0880, true, to);
                            queueToken(T_CURLY_RBRACE, to);
                            break;
                        }
                        case Document:
                        {
                            handleValidationAttributes(false);

                            // TODO base-URI
                            queueToken(T_DOCUMENT, to);
                            queueToken(T_INTERNAL, to);
                            queueToken(T_CURLY_LBRACE, to);
                            pushState(InsideSequenceConstructor);
                            insideSequenceConstructor(to);
                            queueToken(T_CURLY_RBRACE, to);
                            break;
                        }
                        case Element:
                        {
                            handleValidationAttributes(false);

                            // TODO base-URI
                            queueToken(T_ELEMENT, to);
                            queueToken(T_INTERNAL, to);

                            /* The name. */
                            queueToken(T_CURLY_LBRACE, to);
                            // TODO only strings allowed, not qname values.
                            queueAVT(readAttribute(QLatin1String("name")), to);
                            queueToken(T_CURLY_RBRACE, to);

                            /* The sequence constructor. */
                            queueToken(T_CURLY_LBRACE, to);
                            pushState(InsideSequenceConstructor);
                            insideSequenceConstructor(to);
                            queueToken(T_CURLY_RBRACE, to);
                            break;
                        }
                        case Attribute:
                        {
                            handleValidationAttributes(false);

                            // TODO base-URI
                            queueToken(T_ATTRIBUTE, to);
                            queueToken(T_INTERNAL, to);

                            /* The name. */
                            queueToken(T_CURLY_LBRACE, to);
                            // TODO only strings allowed, not qname values.
                            queueAVT(readAttribute(QLatin1String("name")), to);
                            queueToken(T_CURLY_RBRACE, to);

                            /* The sequence constructor. */
                            queueToken(T_CURLY_LBRACE, to);
                            queueSimpleContentConstructor(ReportContext::XTSE0840,
                                                          true, to);
                            queueToken(T_CURLY_RBRACE, to);
                            break;
                        }
                        case Namespace:
                        {
                            queueToken(T_NAMESPACE, to);

                            /* The name. */
                            queueToken(T_CURLY_LBRACE, to);
                            queueAVT(readAttribute(QLatin1String("name")), to);
                            queueToken(T_CURLY_RBRACE, to);

                            /* The sequence constructor. */
                            queueToken(T_CURLY_LBRACE, to);
                            queueSelectOrSequenceConstructor(ReportContext::XTSE0910,
                                                             false, to);
                            queueToken(T_CURLY_RBRACE, to);
                            break;
                        }
                        case PerformSort:
                        {
                            /* For:
                             * <xsl:perform-sort select="$in">
                             *      <xsl:sort select="@key"/>
                             * </xsl:perform-sort>
                             *
                             * we generate:
                             *
                             * $in map sort order by @key
                             *         return .
                             *         end_sort
                             */

                            /* In XQuery, the sort keys appear after the expression
                             * supplying the initial sequence, while in
                             * xsl:perform-sort, if a sequence constructor is used,
                             * they appear in the opposite order. Hence, we need to
                             * reorder it. */

                            /* We store the attributes of xsl:perform-sort, before
                             * queueSorting() advances the reader. */
                            const QXmlStreamAttributes atts(m_currentAttributes);

                            TokenSource::Queue sorts;
                            queueSorting(true, &sorts);
                            queueSelectOrSequenceConstructor(ReportContext::XTSE1040,
                                                             true,
                                                             to,
                                                             &atts);
                            /* queueSelectOrSequenceConstructor() positions us on EndElement. */
                            effectiveInitialAdvance = false;
                            queueToken(T_MAP, to);
                            queueToken(T_SORT, to);
                            *to += sorts;
                            queueToken(T_RETURN, to);
                            queueToken(T_DOT, to);
                            queueToken(T_END_SORT, to);

                            break;
                        }
                        case Message:
                        {
                            // TODO
                            queueEmptySequence(to);
                            skipSubTree();
                            break;
                        }
                        case ApplyTemplates:
                        {
                            if(hasAttribute(QLatin1String("select")))
                                queueExpression(readAttribute(QLatin1String("select")), to);
                            else
                            {
                                queueToken(T_CHILD, to);
                                queueToken(T_COLONCOLON, to);
                                queueToken(T_NODE, to);
                                queueToken(T_LPAREN, to);
                                queueToken(T_RPAREN, to);
                            }

                            bool hasMode = hasAttribute(QLatin1String("mode"));
                            QString mode;

                            if(hasMode)
                                mode = readAttribute(QLatin1String("mode")).trimmed();

                            queueToken(T_FOR_APPLY_TEMPLATE, to);

                            TokenSource::Queue sorts;
                            queueSorting(false, &sorts, true);

                            if(!sorts.isEmpty())
                            {
                                queueToken(T_SORT, to);
                                *to += sorts;
                                queueToken(T_RETURN, to);
                            }

                            queueToken(T_APPLY_TEMPLATE, to);

                            if(hasMode)
                            {
                                queueToken(T_MODE, to);
                                queueToken(Token(mode.startsWith(QLatin1Char('#')) ? T_NCNAME : T_QNAME, mode), to);
                            }

                            queueToken(T_LPAREN, to);
                            queueWithParams(ApplyTemplates, to, false);
                            queueToken(T_RPAREN, to);

                            if(!sorts.isEmpty())
                                queueToken(T_END_SORT, to);

                            break;
                        }
                        default:
                            unexpectedContent();
                    }
                    continue;
                }
                else
                {
                    handleXSLTVersion(&m_tokenSource, &onExitTokens, true);
                    handleStandardAttributes(false);
                    handleValidationAttributes(false);

                    /* We're generating an element constructor. */
                    queueNamespaceDeclarations(to, &onExitTokens); // TODO same in the isXSLT() branch
                    queueToken(T_ELEMENT, to);
                    queueToken(T_INTERNAL, to);
                    queueToken(Token(T_QNAME, qualifiedName().toString()), to);
                    queueToken(T_CURLY_LBRACE, to);
                    const int len = m_currentAttributes.count();

                    for(int i = 0; i < len; ++i)
                    {
                        const QXmlStreamAttribute &at = m_currentAttributes.at(i);

                        /* We don't want to generate constructors for XSL-T attributes. */
                        if(at.namespaceUri() == CommonNamespaces::XSLT)
                            continue;

                        queueToken(T_ATTRIBUTE, to);
                        queueToken(T_INTERNAL, to);

                        queueToken(Token(at.prefix().isEmpty() ? T_NCNAME : T_QNAME, at.qualifiedName().toString()), to);
                        queueToken(T_CURLY_LBRACE, to);
                        queueAVT(at.value().toString(), to);
                        queueToken(T_CURLY_RBRACE, to);
                        queueToken(T_COMMA, to);
                    }

                    pushState(InsideSequenceConstructor);
                    insideSequenceConstructor(to);
                    Q_ASSERT(tokenType() == QXmlStreamReader::EndElement || hasError());
                    continue;
                }

                unexpectedContent();
                break;
            }
            case QXmlStreamReader::EndElement:
            {
                queueTextConstructor(characters, hasWrittenExpression, to);
                leaveState();

                if(!hasWrittenExpression && queueEmptyOnEmpty)
                    queueEmptySequence(to);

                queueOnExit(onExitTokens, to);

                if(isXSLT())
                {
                    Q_ASSERT(!isElement(Sequence));

                    switch(currentElementName())
                    {
                        /* Fallthrough all these. */
                        case When:
                        case Choose:
                        case ForEach:
                        case Otherwise:
                        case PerformSort:
                        case Message:
                        case ResultDocument:
                        case Copy:
                        case CallTemplate:
                        case Text:
                        case ValueOf:
                        {
                            hasWrittenExpression = true;
                            break;
                        }
                        case If:
                        {
                            queueToken(T_RPAREN, to);
                            queueToken(T_ELSE, to);
                            queueEmptySequence(to);
                            break;
                        }
                        case Function:
                        {
                            queueToken(T_CURLY_RBRACE, to);
                            queueToken(T_SEMI_COLON, to);
                            break;
                        }
                        case Template:
                        {
                            endStorageOfCurrent(&m_tokenSource);
                            /* TODO, fallthrough to Function. */
                            queueToken(T_CURLY_RBRACE, to);
                            queueToken(T_SEMI_COLON, to);
                            break;
                        }
                        default:
                            ;
                    }
                }
                else
                {
                    /* We're closing a direct element constructor. */
                    hasWrittenExpression = true;
                    queueToken(T_CURLY_RBRACE, to);
                }

                return hasWrittenExpression;
            }
            case QXmlStreamReader::ProcessingInstruction:
            /* Fallthrough. */
            case QXmlStreamReader::Comment:
                /* We do nothing, we just ignore them. */
                continue;
            case QXmlStreamReader::Characters:
            {
                if(whitespaceToSkip())
                    continue;
                else
                {
                    characters += text().toString();
                    continue;
                }
            }
            default:
                ;
        }
    }

    leaveState();
    return hasWrittenExpression;
}

bool XSLTTokenizer::isStylesheetElement() const
{
    Q_ASSERT(isXSLT());
    Q_ASSERT(tokenType() == QXmlStreamReader::StartElement ||
             tokenType() == QXmlStreamReader::EndElement);

    const NodeName name = currentElementName();
    return name == Stylesheet || name == Transform;
}

void XSLTTokenizer::skipBodyOfParam(const ReportContext::ErrorCode code)
{
    Q_ASSERT(isXSLT());
    Q_ASSERT(tokenType() == QXmlStreamReader::StartElement);
    const NodeName name(currentElementName());

    if(skipSubTree())
    {
        error(QtXmlPatterns::tr("Element %1 cannot have a sequence constructor.")
                                          .arg(formatKeyword(toString(name))),
              code);
    }
}

void XSLTTokenizer::queueWithParams(const XSLTTokenLookup::NodeName parentName,
                                    TokenSource::Queue *const to,
                                    const bool initialAdvance)
{
    Q_ASSERT(parentName == ApplyTemplates || parentName == CallTemplate);

    bool effectiveInitialAdvance = initialAdvance;
    bool hasQueuedParam = false;

    while(!atEnd())
    {
        if(effectiveInitialAdvance)
            readNext();
        else
            effectiveInitialAdvance = true;

        switch(tokenType())
        {
            case QXmlStreamReader::StartElement:
            {
                if(hasQueuedParam)
                    queueToken(T_COMMA, to);

                if(isXSLT() && isElement(WithParam))
                {
                    if(hasAttribute(QLatin1String("tunnel")) && attributeYesNo(QLatin1String("tunnel")))
                        queueToken(T_TUNNEL, to);

                    queueVariableDeclaration(WithParamVariable, to);
                    hasQueuedParam = true;
                    continue;
                }
                else
                    unexpectedContent();
            }
            case QXmlStreamReader::EndElement:
            {
                if(isElement(parentName))
                    return;
                else
                    continue;
            }
            case QXmlStreamReader::ProcessingInstruction:
            /* Fallthrough. */
            case QXmlStreamReader::Comment:
                continue;
            case QXmlStreamReader::Characters:
                if(whitespaceToSkip())
                    continue;
                else
                    return;
            default:
                unexpectedContent();
        }
    }
    unexpectedContent();
}

void XSLTTokenizer::queueParams(const XSLTTokenLookup::NodeName parentName,
                                TokenSource::Queue *const to)
{
    bool hasQueuedParam = false;

    Q_ASSERT(tokenType() == QXmlStreamReader::StartElement);

    while(!atEnd())
    {
        switch(readNext())
        {
            case QXmlStreamReader::StartElement:
            {
                if(isXSLT() && isElement(Param))
                {
                    if(hasQueuedParam)
                        queueToken(T_COMMA, to);

                    validateElement();

                    if(parentName == Function && m_currentAttributes.hasAttribute(QLatin1String("select")))
                    {
                        error(QtXmlPatterns::tr("The attribute %1 cannot appear on %2, when it is a child of %3.")
                                         .arg(formatKeyword(QLatin1String("select")),
                                              formatKeyword(QLatin1String("param")),
                                              formatKeyword(QLatin1String("function"))),
                              ReportContext::XTSE0760);
                    }

                    if(parentName == Function && m_currentAttributes.hasAttribute(QLatin1String("required")))
                    {
                        error(QtXmlPatterns::tr("The attribute %1 cannot appear on %2, when it is a child of %3.")
                                         .arg(formatKeyword(QLatin1String("required")),
                                              formatKeyword(QLatin1String("param")),
                                              formatKeyword(QLatin1String("function"))),
                              ReportContext::XTSE0010);
                    }

                    const bool hasTunnel = m_currentAttributes.hasAttribute(QLatin1String("tunnel"));
                    const bool isTunnel = hasTunnel ? attributeYesNo(QLatin1String("tunnel")) : false;

                    if(isTunnel)
                    {
                        if(parentName == Function)
                        {
                            /* See W3C public report 5650: http://www.w3.org/Bugs/Public/show_bug.cgi?id=5650 */
                            error(QtXmlPatterns::tr("A parameter in a function cannot be declared to be a tunnel."),
                                  ReportContext::XTSE0010);
                        }
                        else
                            queueToken(T_TUNNEL, to);
                    }

                    hasQueuedParam = true;
                    queueVariableDeclaration(parentName == Function ? FunctionParameter : TemplateParameter, to);
                    continue;
                }
                else
                    return;
            }
            case QXmlStreamReader::Characters:
            {
                if(whitespaceToSkip())
                    continue;
                /* Fallthrough. */
            }
            case QXmlStreamReader::EndElement:
                return;
            default:
                ;
        }
    }
}

bool XSLTTokenizer::skipSubTree(const bool exitOnContent)
{
    bool hasContent = false;
    int depth = 0;

    while(!atEnd())
    {
        switch(readNext())
        {
            case QXmlStreamReader::Characters:
            {
                if(whitespaceToSkip())
                    continue;
                else
                {
                    hasContent = true;
                    if(exitOnContent)
                        return true;

                    break;
                }
            }
            case QXmlStreamReader::StartElement:
            {
                hasContent = true;
                if(exitOnContent)
                    return true;

                ++depth;
                break;
            }
            case QXmlStreamReader::EndElement:
            {
                --depth;
                break;
            }
            default:
                continue;
        }

        if(depth == -1)
            return hasContent;
    }

    checkForParseError();
    return hasContent;
}

void XSLTTokenizer::parseFallbacksOnly()
{
    Q_ASSERT(isXSLT());
    Q_ASSERT(tokenType() == QXmlStreamReader::StartElement);

    skipSubTree();
    Q_ASSERT(tokenType() == QXmlStreamReader::EndElement);
}

void XSLTTokenizer::insideAttributeSet()
{
    while(!atEnd())
    {
        switch(readNext())
        {
            case QXmlStreamReader::StartElement:
            {
                if(isXSLT() && isElement(AttributeSet))
                {
                    // TODO
                    skipSubTree();
                }
                else
                    unexpectedContent();
            }
            case QXmlStreamReader::EndElement:
                return;
            case QXmlStreamReader::ProcessingInstruction:
            /* Fallthrough. */
            case QXmlStreamReader::Comment:
                continue;
            case QXmlStreamReader::Characters:
                if(whitespaceToSkip())
                    continue;
                /* Fallthrough. */
            default:
                unexpectedContent();
        }
    }
    unexpectedContent();
}

void XSLTTokenizer::insideStylesheetModule()
{
    while(!atEnd())
    {
        switch(readNext())
        {
            case QXmlStreamReader::StartElement:
            {
                if(isXSLT())
                {
                    handleStandardAttributes(true);
                    handleXSLTVersion(0, 0, true, 0, false);
                    validateElement();

                    /* Handle the various declarations. */
                    switch(currentElementName())
                    {
                        case Template:
                            insideTemplate();
                            break;
                        case Function:
                            insideFunction();
                            break;
                        case Variable:
                            queueVariableDeclaration(VariableDeclaration, &m_tokenSource);
                            break;
                        case Param:
                            queueVariableDeclaration(GlobalParameter, &m_tokenSource);
                            break;
                        case ImportSchema:
                        {
                            error(QtXmlPatterns::tr("This processor is not Schema-aware and "
                                                               "therefore %1 cannot be used.").arg(formatKeyword(toString(ImportSchema))),
                                  ReportContext::XTSE1660);
                            break;
                        }
                        case Output:
                        {
                            // TODO
                            skipSubTree();
                            break;
                        }
                        case StripSpace:
                        /* Fallthrough. */
                        case PreserveSpace:
                        {
                            // TODO @elements
                            skipSubTree(true);
                            readNext();

                            if(!isEndElement())
                                unexpectedContent();
                            break;
                        }
                        case Include:
                        {
                            // TODO
                            if(skipSubTree(true))
                                unexpectedContent();
                            break;
                        }
                        case Import:
                        {
                            // TODO
                            if(skipSubTree(true))
                                unexpectedContent();
                            break;
                        }
                        case Key:
                        {
                            // TODO
                            skipSubTree();
                            break;
                        }
                        case AttributeSet:
                            insideAttributeSet();
                            break;
                        default:
                            if(m_processingMode.top() != ForwardCompatible)
                                unexpectedContent();
                    }
                }
                else
                {
                    /* We have a user-defined data element. See section 3.6.2. */

                    if(namespaceUri().isEmpty())
                    {
                        error(QtXmlPatterns::tr("Top level stylesheet elements must be "
                                                           "in a non-null namespace, which %1 isn't.").arg(formatKeyword(name())),
                              ReportContext::XTSE0130);
                    }
                    else
                        skipSubTree();
                }
                break;
            }
            case QXmlStreamReader::Characters:
            {
                /* Regardless of xml:space, we skip whitespace, see step 4 in
                 * 4.2 Stripping Whitespace from the Stylesheet. */
                if(isWhitespace())
                    continue;

                unexpectedContent(ReportContext::XTSE0120);
                break;
            }
            case QXmlStreamReader::EndElement:
            {
                if(isXSLT())
                    leaveState();

                break;
            }
            default:
                ;
        }
    }
    checkForParseError();
}

bool XSLTTokenizer::readToggleAttribute(const QString &localName,
                                        const QString &isTrue,
                                        const QString &isFalse,
                                        const QXmlStreamAttributes *const attsP) const
{
    const QXmlStreamAttributes atts(attsP ? *attsP : m_currentAttributes);
    Q_ASSERT(atts.hasAttribute(localName));
    const QString value(atts.value(localName).toString());

    if(value == isTrue)
        return true;
    else if(value == isFalse)
        return false;
    else
    {
        error(QtXmlPatterns::tr("The value for attribute %1 on element %2 must either "
                                           "be %3 or %4, not %5.").arg(formatKeyword(localName),
                                                                       formatKeyword(name()),
                                                                       formatData(isTrue),
                                                                       formatData(isFalse),
                                                                       formatData(value)),
              ReportContext::XTSE0020);
        /* Silences a compiler warning. */
        return false;
    }
}

int XSLTTokenizer::readAlternativeAttribute(const QHash<QString, int> &alternatives,
                                            const QXmlStreamAttribute &attr) const
{
    const QString value(attr.value().toString().trimmed());

    if(alternatives.contains(value))
        return alternatives[value];

    error(QtXmlPatterns::tr("Attribute %1 cannot have the value %2.")
                                       .arg(formatKeyword(attr.name().toString()),
                                            formatData(attr.value().toString())),
          ReportContext::XTSE0020);
    return 0; /* Silence compiler warning. */
}

bool XSLTTokenizer::attributeYesNo(const QString &localName) const
{
    return readToggleAttribute(localName, QLatin1String("yes"), QLatin1String("no"));
}

void XSLTTokenizer::queueSorting(const bool oneSortRequired,
                                 TokenSource::Queue *const to,
                                 const bool speciallyTreatWhitespace)
{
    Q_ASSERT(tokenType() == QXmlStreamReader::StartElement);

    const NodeName elementName(currentElementName());
    bool hasQueuedOneSort = false;

    while(!atEnd())
    {
        switch(readNext())
        {
            case QXmlStreamReader::EndElement:
            {
                /* Let's say we have no sequence constructor, but only
                 * ignorable space. In that case we will actually loop
                 * infinitely if we don't have this check. */
                if(isXSLT())
                {
                    switch(currentElementName())
                    {
                        case PerformSort:
                        /* Fallthrough. */
                        case ForEach:
                        /* Fallthrough. */
                        case ApplyTemplates:
                            return;
                        default:
                            ;
                    }
                }
                continue;
            }
            case QXmlStreamReader::StartElement:
            {
                if(isXSLT() && isElement(Sort))
                {
                    if(hasQueuedOneSort)
                        queueToken(T_COMMA, to);

                    /* sorts are by default stable. */
                    if(hasAttribute(QLatin1String("stable")))
                    {
                        if(hasQueuedOneSort)
                        {
                            error(QtXmlPatterns::tr("The attribute %1 can only appear on "
                                                               "the first %2 element.").arg(formatKeyword(QLatin1String("stable")),
                                                                                            formatKeyword(QLatin1String("sort"))),
                                  ReportContext::XTSE0020);
                        }

                        if(attributeYesNo(QLatin1String("stable")))
                            queueToken(T_STABLE, to);
                    }

                    if(!hasQueuedOneSort)
                    {
                        queueToken(T_ORDER, to);
                        queueToken(T_BY, to);
                    }

                    /* We store a copy such that we can use them after
                     * queueSelectOrSequenceConstructor() advances the reader. */
                    const QXmlStreamAttributes atts(m_currentAttributes);

                    const int before = to->count();

                    // TODO This doesn't work as is. @data-type can be an AVT.
                    if(atts.hasAttribute(QLatin1String("data-type")))
                    {
                        if(readToggleAttribute(QLatin1String("data-type"),
                                               QLatin1String("text"),
                                               QLatin1String("number"),
                                               &atts))
                            queueToken(Token(T_NCNAME, QLatin1String("string")), to);
                        else
                            queueToken(Token(T_NCNAME, QLatin1String("number")), to);
                    }
                    /* We queue these parantheses for the sake of the function
                     * call for attribute data-type. In the case we don't have
                     * such an attribute, the parantheses are just redundant. */
                    queueToken(T_LPAREN, to);
                    queueSelectOrSequenceConstructor(ReportContext::XTSE1015,
                                                     true,
                                                     to,
                                                     0,
                                                     false);
                    /* If neither a select attribute or a sequence constructor is supplied,
                     * we're supposed to use the context item. */
                    queueToken(T_RPAREN, to);
                    if(before == to->count())
                        queueToken(T_DOT, to);

                    // TODO case-order
                    // TODO lang

                    // TODO This doesn't work as is. @order can be an AVT, and so can case-order and lang.
                    if(atts.hasAttribute(QLatin1String("order")) && readToggleAttribute(QLatin1String("order"),
                                                                                       QLatin1String("descending"),
                                                                                       QLatin1String("ascending"),
                                                                                       &atts))
                    {
                        queueToken(T_DESCENDING, to);
                    }
                    else
                    {
                        /* This is the default. */
                        queueToken(T_ASCENDING, to);
                    }

                    if(atts.hasAttribute(QLatin1String("collation")))
                    {
                        queueToken(T_INTERNAL, to);
                        queueToken(T_COLLATION, to);
                        queueAVT(atts.value(QLatin1String("collation")).toString(), to);
                    }

                    hasQueuedOneSort = true;
                    continue;
                }
                else
                    break;
            }
            case QXmlStreamReader::Characters:
            {
                if(speciallyTreatWhitespace && isWhitespace())
                    continue;

                if(QXmlStreamReader::Characters && whitespaceToSkip())
                    continue;

                /* We have an instruction which is a text node, we're done. */
                break;
            }
            case QXmlStreamReader::ProcessingInstruction:
            /* Fallthrough. */
            case QXmlStreamReader::Comment:
                continue;
            default:
                unexpectedContent();

        };
        if(oneSortRequired && !hasQueuedOneSort)
        {
            error(QtXmlPatterns::tr("At least one %1 element must appear as child of %2.")
                                              .arg(formatKeyword(QLatin1String("sort")), formatKeyword(toString(elementName))),
                  ReportContext::XTSE0010);
        }
        else
            return;
    }
    checkForParseError();
}

void XSLTTokenizer::insideFunction()
{
    queueToken(T_DECLARE, &m_tokenSource);
    queueToken(T_FUNCTION, &m_tokenSource);
    queueToken(T_INTERNAL, &m_tokenSource);
    queueToken(Token(T_QNAME, readAttribute(QLatin1String("name"))), &m_tokenSource);
    queueToken(T_LPAREN, &m_tokenSource);
    const QString expectedType(hasAttribute(QLatin1String("as")) ? readAttribute(QLatin1String("as")): QString());

    if(hasAttribute(QLatin1String("override")))
    {
        /* We currently have no external functions, so we don't pass it on currently. */
        attributeYesNo(QLatin1String("override"));
    }

    queueParams(Function, &m_tokenSource);

    queueToken(T_RPAREN, &m_tokenSource);

    if(!expectedType.isNull())
    {
        queueToken(T_AS, &m_tokenSource);
        queueSequenceType(expectedType);
    }

    QStack<Token> onExitTokens;
    handleXMLBase(&m_tokenSource, &onExitTokens, true, &m_currentAttributes);
    handleXSLTVersion(&m_tokenSource, &onExitTokens, true);
    queueToken(T_CURLY_LBRACE, &m_tokenSource);

    pushState(InsideSequenceConstructor);
    insideSequenceConstructor(&m_tokenSource, onExitTokens, false);
    /* We don't queue CURLY_RBRACE, because it's done in
     * insideSequenceConstructor(). */
}

YYLTYPE XSLTTokenizer::currentSourceLocator() const
{
    YYLTYPE retval;
    retval.first_line = lineNumber();
    retval.first_column = columnNumber();
    return retval;
}

QT_END_NAMESPACE
