summaryrefslogtreecommitdiffstats
path: root/src/xmlpatterns/parser/qxslttokenizer.cpp
diff options
context:
space:
mode:
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 0000000000..11d12f87fd
--- /dev/null
+++ b/src/xmlpatterns/parser/qxslttokenizer.cpp
@@ -0,0 +1,2717 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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
+