summaryrefslogtreecommitdiff
path: root/src/xmlpatterns/parser/qxslttokenizer.cpp
diff options
context:
space:
mode:
authorQt by Nokia <qt-info@nokia.com>2011-04-27 12:05:43 +0200
committeraxis <qt-info@nokia.com>2011-04-27 12:05:43 +0200
commite1b2c9deb5943faae2b29be6a5c006f75bb73f06 (patch)
treefc79e45367c0a8fc71185e9afc33f7503a58653c /src/xmlpatterns/parser/qxslttokenizer.cpp
downloadqtxmlpatterns-e1b2c9deb5943faae2b29be6a5c006f75bb73f06.tar.gz
Initial import from the monolithic Qt.
This is the beginning of revision history for this module. If you want to look at revision history older than this, please refer to the Qt Git wiki for how to use Git history grafting. At the time of writing, this wiki is located here: http://qt.gitorious.org/qt/pages/GitIntroductionWithQt If you have already performed the grafting and you don't see any history beyond this commit, try running "git log" with the "--follow" argument. Branched from the monolithic repo, Qt master branch, at commit 896db169ea224deb96c59ce8af800d019de63f12
Diffstat (limited to 'src/xmlpatterns/parser/qxslttokenizer.cpp')
-rw-r--r--src/xmlpatterns/parser/qxslttokenizer.cpp2717
1 files changed, 2717 insertions, 0 deletions
diff --git a/src/xmlpatterns/parser/qxslttokenizer.cpp b/src/xmlpatterns/parser/qxslttokenizer.cpp
new file mode 100644
index 0000000..81498ce
--- /dev/null
+++ b/src/xmlpatterns/parser/qxslttokenizer.cpp
@@ -0,0 +1,2717 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtXmlPatterns module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QStringList>
+
+#include "qbuiltintypes_p.h"
+#include "qcommonnamespaces_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(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(XSLT_VERSION, attribute), to);
+ queueToken(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(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(BASEURI, to);
+ queueToken(Token(STRING_LITERAL, val.toString()), to);
+ queueToken(CURLY_LBRACE, to);
+ queueOnExit->push(CURLY_RBRACE);
+ }
+ else
+ {
+ queueToken(DECLARE, to);
+ queueToken(BASEURI, to);
+ queueToken(INTERNAL, to);
+ queueToken(Token(STRING_LITERAL, val.toString()), to);
+ queueToken(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"))
+ {
+ const QStringRef val(m_currentAttributes.value(CommonNamespaces::XML, QLatin1String("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(END_OF_FILE);
+ }
+ else
+ return m_tokenSource.head()->nextToken(sourceLocator);
+ }
+ else
+ {
+ do
+ {
+ const Token candidate(m_tokenSource.head()->nextToken(sourceLocator));
+ if(candidate.type == 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(DECLARE, &m_tokenSource);
+ queueToken(TEMPLATE, &m_tokenSource);
+ queueToken(MATCHES, &m_tokenSource);
+ queueToken(LPAREN, &m_tokenSource);
+ queueToken(TEXT, &m_tokenSource);
+ queueToken(LPAREN, &m_tokenSource);
+ queueToken(RPAREN, &m_tokenSource);
+ queueToken(BAR, &m_tokenSource);
+ queueToken(AT_SIGN, &m_tokenSource);
+ queueToken(STAR, &m_tokenSource);
+ queueToken(RPAREN, &m_tokenSource);
+
+ /* mode #all */
+ queueToken(MODE, &m_tokenSource);
+ queueToken(Token(NCNAME, QLatin1String("#all")), &m_tokenSource);
+ queueToken(CURLY_LBRACE, &m_tokenSource);
+
+ /* text{.} { */
+ queueToken(TEXT, &m_tokenSource);
+ queueToken(CURLY_LBRACE, &m_tokenSource);
+ queueToken(DOT, &m_tokenSource);
+ queueToken(CURLY_RBRACE, &m_tokenSource);
+
+ /* }; */
+ queueToken(CURLY_RBRACE, &m_tokenSource);
+ queueToken(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(DECLARE, &m_tokenSource);
+ queueToken(TEMPLATE, &m_tokenSource);
+ queueToken(MATCHES, &m_tokenSource);
+ queueToken(LPAREN, &m_tokenSource);
+ queueToken(SLASH, &m_tokenSource);
+ queueToken(RPAREN, &m_tokenSource);
+ queueToken(CURLY_LBRACE, &m_tokenSource);
+ pushState(InsideSequenceConstructor);
+
+ handleXSLTVersion(&m_tokenSource, &onExitTokens, false, 0, true);
+ handleStandardAttributes(false);
+
+ insideSequenceConstructor(&m_tokenSource, false);
+
+ queueOnExit(onExitTokens, &m_tokenSource);
+ queueToken(CURLY_RBRACE, &m_tokenSource);
+ queueToken(CURLY_RBRACE, &m_tokenSource);
+ queueToken(SEMI_COLON, &m_tokenSource);
+ }
+
+ queueToken(APPLY_TEMPLATE, &m_tokenSource);
+ queueToken(LPAREN, &m_tokenSource);
+ queueToken(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(DECLARE, &m_tokenSource);
+ queueToken(TEMPLATE, &m_tokenSource);
+
+ if(hasName)
+ {
+ queueToken(NAME, &m_tokenSource);
+ queueToken(Token(QNAME, readAttribute(QLatin1String("name"))), &m_tokenSource);
+ }
+
+ if(hasMatch)
+ {
+ queueToken(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(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(':')) ? QNAME : NCNAME, mode), &m_tokenSource);
+
+ if(i < modeList.count() - 1)
+ queueToken(COMMA, &m_tokenSource);
+ }
+ }
+
+ if(hasPriority)
+ {
+ queueToken(PRIORITY, &m_tokenSource);
+ queueToken(Token(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(LPAREN, &m_tokenSource);
+ queueParams(Template, &m_tokenSource);
+ queueToken(RPAREN, &m_tokenSource);
+
+ if(hasAs)
+ {
+ queueToken(AS, &m_tokenSource);
+ queueSequenceType(atts.value(QLatin1String("as")).toString());
+ }
+
+ queueToken(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(LPAREN, effectiveTo);
+
+ effectiveTo->enqueue(TokenSource::Ptr(new XQueryTokenizer(expr, queryURI())));
+
+ if(wrapWithParantheses)
+ queueToken(RPAREN, effectiveTo);
+}
+
+void XSLTTokenizer::queueAVT(const QString &expr,
+ TokenSource::Queue *const to)
+{
+ queueToken(AVT, to);
+ queueToken(LPAREN, to);
+ to->enqueue(TokenSource::Ptr(new XQueryTokenizer(expr, queryURI(),
+ XQueryTokenizer::QuotAttributeContent)));
+ queueToken(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(COMMA, to);
+ else
+ hasWrittenExpression = true;
+}
+
+void XSLTTokenizer::queueEmptySequence(TokenSource::Queue *const to)
+{
+ queueToken(LPAREN, to);
+ queueToken(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(IF, to);
+ queueToken(LPAREN, to);
+ queueExpression(readAttribute(QLatin1String("test")), to);
+ queueToken(RPAREN, to);
+ queueToken(THEN, to);
+ queueToken(LPAREN, to);
+ pushState(InsideSequenceConstructor);
+ insideSequenceConstructor(to);
+ queueToken(RPAREN, to);
+ Q_ASSERT(tokenType() == QXmlStreamReader::EndElement);
+ queueToken(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(LPAREN, to);
+ insideSequenceConstructor(to, to);
+ queueToken(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(INTERNAL_NAME, to);
+ queueToken(Token(NCNAME, QLatin1String("generic-string-join")), to);
+ queueToken(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(LPAREN, to);
+ const bool viaSelectAttribute = queueSelectOrSequenceConstructor(code, emptynessAllowed, to);
+ queueToken(RPAREN, to);
+
+ if(selectOnlyFirst)
+ {
+ queueToken(LBRACKET, to);
+ queueToken(Token(NUMBER, QChar::fromLatin1('1')), to);
+ queueToken(RBRACKET, to);
+ }
+
+ queueToken(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(STRING_LITERAL, viaSelectAttribute ? QString(QLatin1Char(' '))
+ : QString()),
+ to);
+ }
+
+ queueToken(RPAREN, to);
+}
+
+void XSLTTokenizer::queueTextConstructor(QString &chars,
+ bool &hasWrittenExpression,
+ TokenSource::Queue *const to)
+{
+ if(!chars.isEmpty())
+ {
+ commencingExpression(hasWrittenExpression, to);
+ queueToken(TEXT, to);
+ queueToken(CURLY_LBRACE, to);
+ queueToken(Token(STRING_LITERAL, chars), to);
+ queueToken(CURLY_RBRACE, to);
+ chars.clear();
+ }
+}
+
+void XSLTTokenizer::queueVariableDeclaration(const VariableType variableType,
+ TokenSource::Queue *const to)
+{
+ Q_ASSERT(tokenType() == QXmlStreamReader::StartElement);
+
+ if(variableType == VariableInstruction)
+ {
+ queueToken(LET, to);
+ queueToken(INTERNAL, to);
+ }
+ else if(variableType == VariableDeclaration || variableType == GlobalParameter)
+ {
+ queueToken(DECLARE, to);
+ queueToken(VARIABLE, to);
+ queueToken(INTERNAL, to);
+ }
+
+ queueToken(DOLLAR, to);
+
+ queueExpression(readAttribute(QLatin1String("name")), to, false);
+
+ const bool hasAs = m_currentAttributes.hasAttribute(QLatin1String("as"));
+ if(hasAs)
+ {
+ queueToken(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(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(ASSIGN, to);
+
+ if(!hasSelect && !hasAs && !hasQueuedContent)
+ queueToken(Token(STRING_LITERAL, QString()), to);
+ else if(hasAs || hasSelect)
+ queueToken(LPAREN, to);
+ else
+ {
+ queueToken(DOCUMENT, to);
+ queueToken(INTERNAL, to);
+ queueToken(CURLY_LBRACE, to);
+ }
+ }
+ else
+ {
+ if(!hasAs)
+ {
+ queueToken(ASSIGN, to);
+ queueToken(Token(STRING_LITERAL, QString()), to);
+ }
+ else if(variableType == VariableDeclaration || variableType == VariableInstruction)
+ {
+ queueToken(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(STRING_LITERAL, QString()), to);
+ else if(hasAs || hasSelect)
+ queueToken(RPAREN, to);
+ else
+ queueToken(CURLY_RBRACE, to);
+ }
+ }
+
+ if(variableType == VariableInstruction)
+ queueToken(RETURN, to);
+ else if(variableType == VariableDeclaration || variableType == GlobalParameter)
+ queueToken(SEMI_COLON, to);
+}
+
+void XSLTTokenizer::startStorageOfCurrent(TokenSource::Queue *const to)
+{
+ queueToken(CURRENT, to);
+ queueToken(CURLY_LBRACE, to);
+}
+
+void XSLTTokenizer::endStorageOfCurrent(TokenSource::Queue *const to)
+{
+ queueToken(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(DECLARE, to);
+ queueToken(NAMESPACE, to);
+ queueToken(Token(NCNAME, at.prefix().toString()), to);
+ queueToken(G_EQ, to);
+ queueToken(Token(STRING_LITERAL, at.namespaceUri().toString()), to);
+
+ if(isDeclaration)
+ {
+ queueToken(INTERNAL, to);
+ queueToken(SEMI_COLON, to);
+ }
+ else
+ {
+ queueToken(CURLY_LBRACE, to);
+ queueOnExit->push(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(IF, to);
+ queueToken(LPAREN, to);
+
+ queueExpression(readAttribute(QLatin1String("test")), to);
+ queueToken(RPAREN, to);
+ queueToken(THEN, to);
+
+ queueToken(LPAREN, to);
+ pushState(InsideSequenceConstructor);
+ insideSequenceConstructor(to);
+
+ break;
+ }
+ case Choose:
+ {
+ insideChoose(to);
+ break;
+ }
+ case ValueOf:
+ {
+ /* We generate a computed text node constructor. */
+ queueToken(TEXT, to);
+ queueToken(CURLY_LBRACE, to);
+
+ queueSimpleContentConstructor(ReportContext::XTSE0870, true, to,
+ !hasAttribute(QLatin1String("separator")) && m_processingMode.top() == BackwardsCompatible);
+ queueToken(CURLY_RBRACE, to);
+ break;
+ }
+ case Sequence:
+ {
+ queueExpression(readAttribute(QLatin1String("select")), to);
+ parseFallbacksOnly();
+ break;
+ }
+ case Text:
+ {
+ queueToken(TEXT, to);
+ queueToken(CURLY_LBRACE, to);
+
+ queueToken(Token(STRING_LITERAL, readElementText()), to);
+ queueToken(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(LPAREN, to);
+
+ /* We don't want a comma outputted, we're expecting an
+ * expression now. */
+ hasWrittenExpression = false;
+
+ onExitTokens.push(RPAREN);
+
+ break;
+ }
+ case CallTemplate:
+ {
+ queueToken(CALL_TEMPLATE, to);
+ queueToken(Token(QNAME, readAttribute(QLatin1String("name"))), to);
+ queueToken(LPAREN, to);
+ queueWithParams(CallTemplate, to);
+ queueToken(RPAREN, to);
+ break;
+ }
+ case ForEach:
+ {
+ queueExpression(readAttribute(QLatin1String("select")), to);
+ queueToken(MAP, to);
+ pushState(InsideSequenceConstructor);
+
+ TokenSource::Queue sorts;
+ queueSorting(false, &sorts);
+
+
+ if(sorts.isEmpty())
+ {
+ startStorageOfCurrent(to);
+ insideSequenceConstructor(to, false);
+ endStorageOfCurrent(to);
+ }
+ else
+ {
+ queueToken(SORT, to);
+ *to += sorts;
+ queueToken(RETURN, to);
+ startStorageOfCurrent(to);
+ insideSequenceConstructor(to, false);
+ endStorageOfCurrent(to);
+ queueToken(END_SORT, to);
+ }
+
+ break;
+ }
+ case XSLTTokenLookup::Comment:
+ {
+ queueToken(COMMENT, to);
+ queueToken(INTERNAL, to);
+ queueToken(CURLY_LBRACE, to);
+ queueSelectOrSequenceConstructor(ReportContext::XTSE0940, true, to);
+ queueToken(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(LET, to);
+ queueToken(INTERNAL, to);
+ queueToken(DOLLAR, to);
+ queueToken(Token(NCNAME, QString(QLatin1Char('b'))), to); // TODO we need an internal name
+ queueToken(ASSIGN, to);
+ queueToken(LPAREN, to);
+ pushState(InsideSequenceConstructor);
+ /* Don't queue an empty sequence, we want the dot. */
+ insideSequenceConstructor(to);
+ queueToken(RPAREN, to);
+ queueToken(RETURN, to);
+
+ /* if(self::element()) then */
+ queueToken(IF, to);
+ queueToken(LPAREN, to);
+ queueToken(SELF, to);
+ queueToken(COLONCOLON, to);
+ queueToken(ELEMENT, to);
+ queueToken(LPAREN, to);
+ queueToken(RPAREN, to);
+ queueToken(RPAREN, to);
+ queueToken(THEN, to);
+
+ /* element internal {node-name()} {$body} */
+ queueToken(ELEMENT, to);
+ queueToken(INTERNAL, to);
+ queueToken(CURLY_LBRACE, to);
+ queueToken(Token(NCNAME, QLatin1String("node-name")), to); // TODO what if the default ns changes?
+ queueToken(LPAREN, to);
+ queueToken(DOT, to);
+ queueToken(RPAREN, to);
+ queueToken(CURLY_RBRACE, to);
+ queueToken(CURLY_LBRACE, to);
+ queueToken(DOLLAR, to);
+ queueToken(Token(NCNAME, QString(QLatin1Char('b'))), to); // TODO we need an internal name
+ queueToken(CURLY_RBRACE, to);
+
+ /* else if(self::document-node()) then */
+ queueToken(ELSE, to);
+ queueToken(IF, to);
+ queueToken(LPAREN, to);
+ queueToken(SELF, to);
+ queueToken(COLONCOLON, to);
+ queueToken(DOCUMENT_NODE, to);
+ queueToken(LPAREN, to);
+ queueToken(RPAREN, to);
+ queueToken(RPAREN, to);
+ queueToken(THEN, to);
+
+ /* document internal {$body} */
+ queueToken(DOCUMENT, to);
+ queueToken(INTERNAL, to);
+ queueToken(CURLY_LBRACE, to);
+ queueToken(DOLLAR, to);
+ queueToken(Token(NCNAME, QString(QLatin1Char('b'))), to); // TODO we need an internal name
+ queueToken(CURLY_RBRACE, to);
+
+ /* else . */
+ queueToken(ELSE, to);
+ queueToken(DOT, to);
+
+ break;
+ }
+ case XSLTTokenLookup::ProcessingInstruction:
+ {
+ queueToken(PROCESSING_INSTRUCTION, to);
+ queueToken(CURLY_LBRACE, to);
+ queueAVT(readAttribute(QLatin1String("name")), to);
+ queueToken(CURLY_RBRACE, to);
+ queueToken(CURLY_LBRACE, to);
+ queueSelectOrSequenceConstructor(ReportContext::XTSE0880, true, to);
+ queueToken(CURLY_RBRACE, to);
+ break;
+ }
+ case Document:
+ {
+ handleValidationAttributes(false);
+
+ // TODO base-URI
+ queueToken(DOCUMENT, to);
+ queueToken(INTERNAL, to);
+ queueToken(CURLY_LBRACE, to);
+ pushState(InsideSequenceConstructor);
+ insideSequenceConstructor(to);
+ queueToken(CURLY_RBRACE, to);
+ break;
+ }
+ case Element:
+ {
+ handleValidationAttributes(false);
+
+ // TODO base-URI
+ queueToken(ELEMENT, to);
+ queueToken(INTERNAL, to);
+
+ /* The name. */
+ queueToken(CURLY_LBRACE, to);
+ // TODO only strings allowed, not qname values.
+ queueAVT(readAttribute(QLatin1String("name")), to);
+ queueToken(CURLY_RBRACE, to);
+
+ /* The sequence constructor. */
+ queueToken(CURLY_LBRACE, to);
+ pushState(InsideSequenceConstructor);
+ insideSequenceConstructor(to);
+ queueToken(CURLY_RBRACE, to);
+ break;
+ }
+ case Attribute:
+ {
+ handleValidationAttributes(false);
+
+ // TODO base-URI
+ queueToken(ATTRIBUTE, to);
+ queueToken(INTERNAL, to);
+
+ /* The name. */
+ queueToken(CURLY_LBRACE, to);
+ // TODO only strings allowed, not qname values.
+ queueAVT(readAttribute(QLatin1String("name")), to);
+ queueToken(CURLY_RBRACE, to);
+
+ /* The sequence constructor. */
+ queueToken(CURLY_LBRACE, to);
+ queueSimpleContentConstructor(ReportContext::XTSE0840,
+ true, to);
+ queueToken(CURLY_RBRACE, to);
+ break;
+ }
+ case Namespace:
+ {
+ queueToken(NAMESPACE, to);
+
+ /* The name. */
+ queueToken(CURLY_LBRACE, to);
+ queueAVT(readAttribute(QLatin1String("name")), to);
+ queueToken(CURLY_RBRACE, to);
+
+ /* The sequence constructor. */
+ queueToken(CURLY_LBRACE, to);
+ queueSelectOrSequenceConstructor(ReportContext::XTSE0910,
+ false, to);
+ queueToken(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(MAP, to);
+ queueToken(SORT, to);
+ *to += sorts;
+ queueToken(RETURN, to);
+ queueToken(DOT, to);
+ queueToken(END_SORT, to);
+
+ break;
+ }
+ case Message:
+ {
+ // TODO
+ queueEmptySequence(to);
+ skipSubTree();
+ break;
+ }
+ case ApplyTemplates:
+ {
+ if(hasAttribute(QLatin1String("select")))
+ queueExpression(readAttribute(QLatin1String("select")), to);
+ else
+ {
+ queueToken(CHILD, to);
+ queueToken(COLONCOLON, to);
+ queueToken(NODE, to);
+ queueToken(LPAREN, to);
+ queueToken(RPAREN, to);
+ }
+
+ bool hasMode = hasAttribute(QLatin1String("mode"));
+ QString mode;
+
+ if(hasMode)
+ mode = readAttribute(QLatin1String("mode")).trimmed();
+
+ queueToken(FOR_APPLY_TEMPLATE, to);
+
+ TokenSource::Queue sorts;
+ queueSorting(false, &sorts, true);
+
+ if(!sorts.isEmpty())
+ {
+ queueToken(SORT, to);
+ *to += sorts;
+ queueToken(RETURN, to);
+ }
+
+ queueToken(APPLY_TEMPLATE, to);
+
+ if(hasMode)
+ {
+ queueToken(MODE, to);
+ queueToken(Token(mode.startsWith(QLatin1Char('#')) ? NCNAME : QNAME, mode), to);
+ }
+
+ queueToken(LPAREN, to);
+ queueWithParams(ApplyTemplates, to, false);
+ queueToken(RPAREN, to);
+
+ if(!sorts.isEmpty())
+ queueToken(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(ELEMENT, to);
+ queueToken(INTERNAL, to);
+ queueToken(Token(QNAME, qualifiedName().toString()), to);
+ queueToken(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(ATTRIBUTE, to);
+ queueToken(INTERNAL, to);
+
+ queueToken(Token(at.prefix().isEmpty() ? NCNAME : QNAME, at.qualifiedName().toString()), to);
+ queueToken(CURLY_LBRACE, to);
+ queueAVT(at.value().toString(), to);
+ queueToken(CURLY_RBRACE, to);
+ queueToken(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(RPAREN, to);
+ queueToken(ELSE, to);
+ queueEmptySequence(to);
+ break;
+ }
+ case Function:
+ {
+ queueToken(CURLY_RBRACE, to);
+ queueToken(SEMI_COLON, to);
+ break;
+ }
+ case Template:
+ {
+ endStorageOfCurrent(&m_tokenSource);
+ /* TODO, fallthrough to Function. */
+ queueToken(CURLY_RBRACE, to);
+ queueToken(SEMI_COLON, to);
+ break;
+ }
+ default:
+ ;
+ }
+ }
+ else
+ {
+ /* We're closing a direct element constructor. */
+ hasWrittenExpression = true;
+ queueToken(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(COMMA, to);
+
+ if(isXSLT() && isElement(WithParam))
+ {
+ if(hasAttribute(QLatin1String("tunnel")) && attributeYesNo(QLatin1String("tunnel")))
+ queueToken(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(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(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(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(STABLE, to);
+ }
+
+ if(!hasQueuedOneSort)
+ {
+ queueToken(ORDER, to);
+ queueToken(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(NCNAME, QLatin1String("string")), to);
+ else
+ queueToken(Token(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(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(RPAREN, to);
+ if(before == to->count())
+ queueToken(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(DESCENDING, to);
+ }
+ else
+ {
+ /* This is the default. */
+ queueToken(ASCENDING, to);
+ }
+
+ if(atts.hasAttribute(QLatin1String("collation")))
+ {
+ queueToken(INTERNAL, to);
+ queueToken(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(DECLARE, &m_tokenSource);
+ queueToken(FUNCTION, &m_tokenSource);
+ queueToken(INTERNAL, &m_tokenSource);
+ queueToken(Token(QNAME, readAttribute(QLatin1String("name"))), &m_tokenSource);
+ queueToken(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(RPAREN, &m_tokenSource);
+
+ if(!expectedType.isNull())
+ {
+ queueToken(AS, &m_tokenSource);
+ queueSequenceType(expectedType);
+ }
+
+ QStack<Token> onExitTokens;
+ handleXMLBase(&m_tokenSource, &onExitTokens, true, &m_currentAttributes);
+ handleXSLTVersion(&m_tokenSource, &onExitTokens, true);
+ queueToken(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
+