/**************************************************************************** ** ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** 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 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 http://qt.nokia.com/contact. ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #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(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::validateElement(currentElementName()); } QSet XSLTTokenizer::createStandardAttributes() { QSet 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::Hash XSLTTokenizer::createElementDescriptions() { ElementDescription::Hash result; enum { ReservedForElements = 40 }; result.reserve(ReservedForElements); /* xsl:apply-templates */ { ElementDescription &e = result[ApplyTemplates]; e.optionalAttributes.insert(Select); e.optionalAttributes.insert(Mode); } /* xsl:template */ { ElementDescription &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 &e = result[Text]; result.insert(Choose, e); result.insert(Otherwise, e); } /* xsl:stylesheet */ { ElementDescription &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 &e = result[ValueOf]; e.optionalAttributes.insert(Separator); e.optionalAttributes.insert(Select); } /* xsl:variable */ { ElementDescription &e = result[Variable]; e.requiredAttributes.insert(Name); e.optionalAttributes.insert(Select); e.optionalAttributes.insert(As); } /* xsl:when & xsl:if */ { ElementDescription &e = result[When]; e.requiredAttributes.insert(Test); result.insert(If, e); } /* xsl:sequence, xsl:for-each */ { ElementDescription &e = result[Sequence]; e.requiredAttributes.insert(Select); result.insert(ForEach, e); } /* xsl:comment */ { ElementDescription &e = result[XSLTTokenLookup::Comment]; e.optionalAttributes.insert(Select); } /* xsl:processing-instruction */ { ElementDescription &e = result[XSLTTokenLookup::ProcessingInstruction]; e.requiredAttributes.insert(Name); e.optionalAttributes.insert(Select); } /* xsl:document */ { ElementDescription &e = result[Document]; e.optionalAttributes.insert(Validation); e.optionalAttributes.insert(Type); } /* xsl:element */ { ElementDescription &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 &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 &e = result[Function]; e.requiredAttributes.insert(Name); e.optionalAttributes.insert(As); e.optionalAttributes.insert(Override); } /* xsl:param */ { ElementDescription &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 &e = result[Namespace]; e.requiredAttributes.insert(Name); e.optionalAttributes.insert(Select); } /* xsl:call-template */ { ElementDescription &e = result[CallTemplate]; e.requiredAttributes.insert(Name); } /* xsl:perform-sort */ { ElementDescription &e = result[PerformSort]; e.requiredAttributes.insert(Select); } /* xsl:sort */ { ElementDescription &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 &e = result[ImportSchema]; e.optionalAttributes.reserve(2); e.optionalAttributes.insert(Namespace); e.optionalAttributes.insert(SchemaLocation); } /* xsl:message */ { ElementDescription &e = result[Message]; e.optionalAttributes.reserve(2); e.optionalAttributes.insert(Select); e.optionalAttributes.insert(Terminate); } /* xsl:copy-of */ { ElementDescription &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 &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 &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 &e = result[AttributeSet]; e.requiredAttributes.insert(Name); e.optionalAttributes.insert(UseAttributeSets); } /* xsl:include and xsl:import. */ { ElementDescription &e = result[Include]; e.requiredAttributes.insert(Href); result[Import] = e; } /* xsl:with-param */ { ElementDescription &e = result[WithParam]; e.requiredAttributes.insert(Name); e.optionalAttributes.insert(Select); e.optionalAttributes.insert(As); e.optionalAttributes.insert(Tunnel); } /* xsl:strip-space */ { ElementDescription &e = result[StripSpace]; e.requiredAttributes.insert(Elements); result.insert(PreserveSpace, e); } /* xsl:result-document */ { ElementDescription &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 &e = result[Key]; e.requiredAttributes.insert(Name); e.requiredAttributes.insert(Match); e.optionalAttributes.insert(Use); e.optionalAttributes.insert(Collation); } /* xsl:analyze-string */ { ElementDescription &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 XSLTTokenizer::createValidationAlternatives() { QHash 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 *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()->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 *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 &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 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 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 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 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 *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 onExitTokens; return insideSequenceConstructor(to, onExitTokens, initialAdvance, queueEmptyOnEmpty); } bool XSLTTokenizer::insideSequenceConstructor(TokenSource::Queue *const to, QStack &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: * expr * 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: * * * * * 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 &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 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