| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtXmlPatterns module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include <QStringList> |
| |
| #include "qbuiltintypes_p.h" |
| #include "qcommonnamespaces_p.h" |
| #include "qparsercontext_p.h" |
| #include "qquerytransformparser_p.h" |
| #include "qxquerytokenizer_p.h" |
| #include "qpatternistlocale_p.h" |
| |
| #include "qxslttokenizer_p.h" |
| |
| QT_BEGIN_NAMESPACE |
| |
| using namespace QPatternist; |
| |
| Tokenizer::Token SingleTokenContainer::nextToken(XPATHLTYPE *const location) |
| { |
| if(m_hasDelivered) |
| return Tokenizer::Token(T_END_OF_FILE); |
| else |
| { |
| *location = m_location; |
| m_hasDelivered = true; |
| return m_token; |
| } |
| } |
| |
| XSLTTokenizer::XSLTTokenizer(QIODevice *const queryDevice, |
| const QUrl &location, |
| const ReportContext::Ptr &context, |
| const NamePool::Ptr &np) : Tokenizer(location) |
| , MaintainingReader<XSLTTokenLookup>(createElementDescriptions(), createStandardAttributes(), context, queryDevice) |
| , m_location(location) |
| , m_namePool(np) |
| /* We initialize after all name constants. */ |
| , m_validationAlternatives(createValidationAlternatives()) |
| , m_parseInfo(0) |
| { |
| Q_ASSERT(m_namePool); |
| |
| pushState(OutsideDocumentElement); |
| } |
| |
| bool XSLTTokenizer::isAnyAttributeAllowed() const |
| { |
| return m_processingMode.top() == ForwardCompatible; |
| } |
| |
| void XSLTTokenizer::setParserContext(const ParserContext::Ptr &parseInfo) |
| { |
| m_parseInfo = parseInfo; |
| } |
| |
| void XSLTTokenizer::validateElement() const |
| { |
| MaintainingReader<XSLTTokenLookup>::validateElement(currentElementName()); |
| } |
| |
| QSet<XSLTTokenizer::NodeName> XSLTTokenizer::createStandardAttributes() |
| { |
| QSet<NodeName> retval; |
| enum |
| { |
| ReservedForAttributes = 6 |
| }; |
| |
| retval.reserve(6); |
| |
| retval.insert(DefaultCollation); |
| retval.insert(ExcludeResultPrefixes); |
| retval.insert(ExtensionElementPrefixes); |
| retval.insert(UseWhen); |
| retval.insert(Version); |
| retval.insert(XpathDefaultNamespace); |
| |
| Q_ASSERT(retval.count() == ReservedForAttributes); |
| |
| return retval; |
| } |
| |
| ElementDescription<XSLTTokenLookup>::Hash XSLTTokenizer::createElementDescriptions() |
| { |
| ElementDescription<XSLTTokenLookup>::Hash result; |
| enum |
| { |
| ReservedForElements = 40 |
| }; |
| result.reserve(ReservedForElements); |
| |
| /* xsl:apply-templates */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[ApplyTemplates]; |
| e.optionalAttributes.insert(Select); |
| e.optionalAttributes.insert(Mode); |
| } |
| |
| /* xsl:template */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[Template]; |
| e.optionalAttributes.insert(Match); |
| e.optionalAttributes.insert(Name); |
| e.optionalAttributes.insert(Mode); |
| e.optionalAttributes.insert(Priority); |
| e.optionalAttributes.insert(As); |
| } |
| |
| /* xsl:text, xsl:choose and xsl:otherwise */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[Text]; |
| result.insert(Choose, e); |
| result.insert(Otherwise, e); |
| } |
| |
| /* xsl:stylesheet */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[Stylesheet]; |
| |
| e.requiredAttributes.insert(Version); |
| |
| e.optionalAttributes.insert(Id); |
| e.optionalAttributes.insert(ExtensionElementPrefixes); |
| e.optionalAttributes.insert(ExcludeResultPrefixes); |
| e.optionalAttributes.insert(XpathDefaultNamespace); |
| e.optionalAttributes.insert(DefaultValidation); |
| e.optionalAttributes.insert(DefaultCollation); |
| e.optionalAttributes.insert(InputTypeAnnotations); |
| } |
| |
| /* xsl:transform */ |
| { |
| result[Transform] = result[Stylesheet]; |
| } |
| |
| /* xsl:value-of */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[ValueOf]; |
| e.optionalAttributes.insert(Separator); |
| e.optionalAttributes.insert(Select); |
| } |
| |
| /* xsl:variable */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[Variable]; |
| |
| e.requiredAttributes.insert(Name); |
| |
| e.optionalAttributes.insert(Select); |
| e.optionalAttributes.insert(As); |
| } |
| |
| /* xsl:when & xsl:if */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[When]; |
| |
| e.requiredAttributes.insert(Test); |
| |
| result.insert(If, e); |
| } |
| |
| /* xsl:sequence, xsl:for-each */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[Sequence]; |
| |
| e.requiredAttributes.insert(Select); |
| |
| result.insert(ForEach, e); |
| } |
| |
| /* xsl:comment */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[XSLTTokenLookup::Comment]; |
| |
| e.optionalAttributes.insert(Select); |
| } |
| |
| /* xsl:processing-instruction */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[XSLTTokenLookup::ProcessingInstruction]; |
| |
| e.requiredAttributes.insert(Name); |
| e.optionalAttributes.insert(Select); |
| } |
| |
| /* xsl:document */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[Document]; |
| |
| e.optionalAttributes.insert(Validation); |
| e.optionalAttributes.insert(Type); |
| } |
| |
| /* xsl:element */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[Element]; |
| |
| e.requiredAttributes.insert(Name); |
| |
| e.optionalAttributes.insert(Namespace); |
| e.optionalAttributes.insert(InheritNamespaces); |
| e.optionalAttributes.insert(UseAttributeSets); |
| e.optionalAttributes.insert(Validation); |
| e.optionalAttributes.insert(Type); |
| } |
| |
| /* xsl:attribute */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[Attribute]; |
| |
| e.requiredAttributes.insert(Name); |
| |
| e.optionalAttributes.insert(Namespace); |
| e.optionalAttributes.insert(Select); |
| e.optionalAttributes.insert(Separator); |
| e.optionalAttributes.insert(Validation); |
| e.optionalAttributes.insert(Type); |
| } |
| |
| /* xsl:function */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[Function]; |
| |
| e.requiredAttributes.insert(Name); |
| |
| e.optionalAttributes.insert(As); |
| e.optionalAttributes.insert(Override); |
| } |
| |
| /* xsl:param */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[Param]; |
| |
| e.requiredAttributes.insert(Name); |
| |
| e.optionalAttributes.insert(Select); |
| e.optionalAttributes.insert(As); |
| e.optionalAttributes.insert(Required); |
| e.optionalAttributes.insert(Tunnel); |
| } |
| |
| /* xsl:namespace */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[Namespace]; |
| |
| e.requiredAttributes.insert(Name); |
| e.optionalAttributes.insert(Select); |
| } |
| |
| /* xsl:call-template */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[CallTemplate]; |
| e.requiredAttributes.insert(Name); |
| } |
| |
| /* xsl:perform-sort */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[PerformSort]; |
| e.requiredAttributes.insert(Select); |
| } |
| |
| /* xsl:sort */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[Sort]; |
| |
| e.optionalAttributes.reserve(7); |
| e.optionalAttributes.insert(Select); |
| e.optionalAttributes.insert(Lang); |
| e.optionalAttributes.insert(Order); |
| e.optionalAttributes.insert(Collation); |
| e.optionalAttributes.insert(Stable); |
| e.optionalAttributes.insert(CaseOrder); |
| e.optionalAttributes.insert(DataType); |
| } |
| |
| /* xsl:import-schema */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[ImportSchema]; |
| |
| e.optionalAttributes.reserve(2); |
| e.optionalAttributes.insert(Namespace); |
| e.optionalAttributes.insert(SchemaLocation); |
| } |
| |
| /* xsl:message */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[Message]; |
| |
| e.optionalAttributes.reserve(2); |
| e.optionalAttributes.insert(Select); |
| e.optionalAttributes.insert(Terminate); |
| } |
| |
| /* xsl:copy-of */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[CopyOf]; |
| |
| e.requiredAttributes.insert(Select); |
| |
| e.optionalAttributes.reserve(2); |
| e.optionalAttributes.insert(CopyNamespaces); |
| e.optionalAttributes.insert(Type); |
| e.optionalAttributes.insert(Validation); |
| } |
| |
| /* xsl:copy */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[Copy]; |
| |
| e.optionalAttributes.reserve(5); |
| e.optionalAttributes.insert(CopyNamespaces); |
| e.optionalAttributes.insert(InheritNamespaces); |
| e.optionalAttributes.insert(UseAttributeSets); |
| e.optionalAttributes.insert(Type); |
| e.optionalAttributes.insert(Validation); |
| } |
| |
| /* xsl:output */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[Output]; |
| |
| e.optionalAttributes.reserve(17); |
| e.optionalAttributes.insert(Name); |
| e.optionalAttributes.insert(Method); |
| e.optionalAttributes.insert(ByteOrderMark); |
| e.optionalAttributes.insert(CdataSectionElements); |
| e.optionalAttributes.insert(DoctypePublic); |
| e.optionalAttributes.insert(DoctypeSystem); |
| e.optionalAttributes.insert(Encoding); |
| e.optionalAttributes.insert(EscapeUriAttributes); |
| e.optionalAttributes.insert(IncludeContentType); |
| e.optionalAttributes.insert(Indent); |
| e.optionalAttributes.insert(MediaType); |
| e.optionalAttributes.insert(NormalizationForm); |
| e.optionalAttributes.insert(OmitXmlDeclaration); |
| e.optionalAttributes.insert(Standalone); |
| e.optionalAttributes.insert(UndeclarePrefixes); |
| e.optionalAttributes.insert(UseCharacterMaps); |
| e.optionalAttributes.insert(Version); |
| } |
| |
| /* xsl:attribute-set */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[AttributeSet]; |
| |
| e.requiredAttributes.insert(Name); |
| e.optionalAttributes.insert(UseAttributeSets); |
| } |
| |
| /* xsl:include and xsl:import. */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[Include]; |
| e.requiredAttributes.insert(Href); |
| result[Import] = e; |
| } |
| |
| /* xsl:with-param */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[WithParam]; |
| e.requiredAttributes.insert(Name); |
| |
| e.optionalAttributes.insert(Select); |
| e.optionalAttributes.insert(As); |
| e.optionalAttributes.insert(Tunnel); |
| } |
| |
| /* xsl:strip-space */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[StripSpace]; |
| e.requiredAttributes.insert(Elements); |
| |
| result.insert(PreserveSpace, e); |
| } |
| |
| /* xsl:result-document */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[ResultDocument]; |
| |
| e.optionalAttributes.insert(ByteOrderMark); |
| e.optionalAttributes.insert(CdataSectionElements); |
| e.optionalAttributes.insert(DoctypePublic); |
| e.optionalAttributes.insert(DoctypeSystem); |
| e.optionalAttributes.insert(Encoding); |
| e.optionalAttributes.insert(EscapeUriAttributes); |
| e.optionalAttributes.insert(Format); |
| e.optionalAttributes.insert(Href); |
| e.optionalAttributes.insert(IncludeContentType); |
| e.optionalAttributes.insert(Indent); |
| e.optionalAttributes.insert(MediaType); |
| e.optionalAttributes.insert(Method); |
| e.optionalAttributes.insert(NormalizationForm); |
| e.optionalAttributes.insert(OmitXmlDeclaration); |
| e.optionalAttributes.insert(OutputVersion); |
| e.optionalAttributes.insert(Standalone); |
| e.optionalAttributes.insert(Type); |
| e.optionalAttributes.insert(UndeclarePrefixes); |
| e.optionalAttributes.insert(UseCharacterMaps); |
| e.optionalAttributes.insert(Validation); |
| } |
| |
| /* xsl:key */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[Key]; |
| |
| e.requiredAttributes.insert(Name); |
| e.requiredAttributes.insert(Match); |
| |
| e.optionalAttributes.insert(Use); |
| e.optionalAttributes.insert(Collation); |
| } |
| |
| /* xsl:analyze-string */ |
| { |
| ElementDescription<XSLTTokenLookup> &e = result[AnalyzeString]; |
| |
| e.requiredAttributes.insert(Select); |
| e.requiredAttributes.insert(Regex); |
| |
| e.optionalAttributes.insert(Flags); |
| } |
| |
| /* xsl:matching-substring */ |
| { |
| /* We insert a default constructed value. */ |
| result[MatchingSubstring]; |
| } |
| |
| /* xsl:non-matching-substring */ |
| { |
| /* We insert a default constructed value. */ |
| result[NonMatchingSubstring]; |
| } |
| |
| Q_ASSERT(result.count() == ReservedForElements); |
| |
| return result; |
| } |
| |
| QHash<QString, int> XSLTTokenizer::createValidationAlternatives() |
| { |
| QHash<QString, int> retval; |
| |
| retval.insert(QLatin1String("preserve"), 0); |
| retval.insert(QLatin1String("strip"), 1); |
| retval.insert(QLatin1String("strict"), 2); |
| retval.insert(QLatin1String("lax"), 3); |
| |
| return retval; |
| } |
| |
| bool XSLTTokenizer::whitespaceToSkip() const |
| { |
| return m_stripWhitespace.top() && isWhitespace(); |
| } |
| |
| void XSLTTokenizer::unexpectedContent(const ReportContext::ErrorCode code) const |
| { |
| QString message; |
| |
| ReportContext::ErrorCode effectiveCode = code; |
| |
| switch(tokenType()) |
| { |
| case QXmlStreamReader::StartElement: |
| { |
| if(isXSLT()) |
| { |
| switch(currentElementName()) |
| { |
| case Include: |
| effectiveCode = ReportContext::XTSE0170; |
| break; |
| case Import: |
| effectiveCode = ReportContext::XTSE0190; |
| break; |
| default: |
| ; |
| } |
| } |
| |
| message = QtXmlPatterns::tr("Element %1 is not allowed at this location.") |
| .arg(formatKeyword(name())); |
| break; |
| } |
| case QXmlStreamReader::Characters: |
| { |
| if(whitespaceToSkip()) |
| return; |
| |
| message = QtXmlPatterns::tr("Text nodes are not allowed at this location."); |
| break; |
| } |
| case QXmlStreamReader::Invalid: |
| { |
| /* It's an issue with well-formedness. */ |
| message = escape(errorString()); |
| break; |
| } |
| default: |
| Q_ASSERT(false); |
| } |
| |
| error(message, effectiveCode); |
| } |
| |
| void XSLTTokenizer::checkForParseError() const |
| { |
| if(hasError()) |
| { |
| error(QtXmlPatterns::tr("Parse error: %1").arg(escape(errorString())), ReportContext::XTSE0010); |
| } |
| } |
| |
| QString XSLTTokenizer::readElementText() |
| { |
| QString result; |
| |
| while(!atEnd()) |
| { |
| switch(readNext()) |
| { |
| case QXmlStreamReader::Characters: |
| { |
| result += text().toString(); |
| continue; |
| } |
| case QXmlStreamReader::Comment: |
| case QXmlStreamReader::ProcessingInstruction: |
| continue; |
| case QXmlStreamReader::EndElement: |
| return result; |
| default: |
| unexpectedContent(); |
| } |
| } |
| |
| checkForParseError(); |
| return result; |
| } |
| |
| int XSLTTokenizer::commenceScanOnly() |
| { |
| /* Do nothing, return a dummy value. */ |
| return 0; |
| } |
| |
| void XSLTTokenizer::resumeTokenizationFrom(const int position) |
| { |
| /* Do nothing. */ |
| Q_UNUSED(position); |
| } |
| |
| void XSLTTokenizer::handleXSLTVersion(TokenSource::Queue *const to, |
| QStack<Token> *const queueOnExit, |
| const bool isXSLTElement, |
| const QXmlStreamAttributes *atts, |
| const bool generateCode, |
| const bool setGlobalVersion) |
| { |
| const QString ns(isXSLTElement ? QString() : CommonNamespaces::XSLT); |
| const QXmlStreamAttributes effectiveAtts(atts ? *atts : attributes()); |
| |
| if(!effectiveAtts.hasAttribute(ns, QLatin1String("version"))) |
| return; |
| |
| const QString attribute(effectiveAtts.value(ns, QLatin1String("version")).toString()); |
| const AtomicValue::Ptr number(Decimal::fromLexical(attribute)); |
| |
| if(number->hasError()) |
| { |
| error(QtXmlPatterns::tr("The value of the XSL-T version attribute " |
| "must be a value of type %1, which %2 isn't.").arg(formatType(m_namePool, BuiltinTypes::xsDecimal), |
| formatData(attribute)), |
| ReportContext::XTSE0110); |
| } |
| else |
| { |
| |
| if(generateCode) |
| { |
| queueToken(Token(T_XSLT_VERSION, attribute), to); |
| queueToken(T_CURLY_LBRACE, to); |
| } |
| |
| const xsDecimal version = number->as<Numeric>()->toDecimal(); |
| if(version == 2.0) |
| m_processingMode.push(NormalProcessing); |
| else if(version == 1.0) |
| { |
| /* See section 3.6 Stylesheet Element discussing this. */ |
| warning(QtXmlPatterns::tr("Running an XSL-T 1.0 stylesheet with a 2.0 processor.")); |
| m_processingMode.push(BackwardsCompatible); |
| |
| if(setGlobalVersion) |
| { |
| m_parseInfo->staticContext->setCompatModeEnabled(true); |
| m_parseInfo->isBackwardsCompat.push(true); |
| } |
| } |
| else if(version > 2.0) |
| m_processingMode.push(ForwardCompatible); |
| else if(version < 2.0) |
| m_processingMode.push(BackwardsCompatible); |
| } |
| |
| if(generateCode) |
| queueOnExit->push(T_CURLY_RBRACE); |
| } |
| |
| void XSLTTokenizer::handleXMLBase(TokenSource::Queue *const to, |
| QStack<Token> *const queueOnExit, |
| const bool isInstruction, |
| const QXmlStreamAttributes *atts) |
| { |
| const QXmlStreamAttributes effectiveAtts(atts ? *atts : m_currentAttributes); |
| |
| if(effectiveAtts.hasAttribute(QLatin1String("xml:base"))) |
| { |
| const QStringRef val(effectiveAtts.value(QLatin1String("xml:base"))); |
| |
| if(!val.isEmpty()) |
| { |
| if(isInstruction) |
| { |
| queueToken(T_BASEURI, to); |
| queueToken(Token(T_STRING_LITERAL, val.toString()), to); |
| queueToken(T_CURLY_LBRACE, to); |
| queueOnExit->push(T_CURLY_RBRACE); |
| } |
| else |
| { |
| queueToken(T_DECLARE, to); |
| queueToken(T_BASEURI, to); |
| queueToken(T_INTERNAL, to); |
| queueToken(Token(T_STRING_LITERAL, val.toString()), to); |
| queueToken(T_SEMI_COLON, to); |
| } |
| } |
| } |
| } |
| |
| void XSLTTokenizer::handleStandardAttributes(const bool isXSLTElement) |
| { |
| /* We're not necessarily StartElement, that's why we have atts passed in. */ |
| Q_ASSERT(tokenType() == QXmlStreamReader::StartElement); |
| |
| if(m_hasHandledStandardAttributes) |
| return; |
| |
| m_hasHandledStandardAttributes = true; |
| |
| const QString ns(isXSLTElement ? QString() : CommonNamespaces::XSLT); |
| const int len = m_currentAttributes.count(); |
| |
| for(int i = 0; i < len; ++i) |
| { |
| const QXmlStreamAttribute &att = m_currentAttributes.at(i); |
| |
| if(att.qualifiedName() == QLatin1String("xml:space")) |
| { |
| /* We raise an error if the value is not recognized. |
| * |
| * Extensible Markup Language (XML) 1.0 (Fourth Edition), 2.10 |
| * White Space Handling: |
| * |
| * 'This specification does not give meaning to any value of |
| * xml:space other than "default" and "preserve". It is an error |
| * for other values to be specified; the XML processor may report |
| * the error or may recover by ignoring the attribute specification |
| * or by reporting the (erroneous) value to the application.' */ |
| m_stripWhitespace.push(readToggleAttribute(QLatin1String("xml:space"), |
| QLatin1String("default"), |
| QLatin1String("preserve"), |
| &m_currentAttributes)); |
| } |
| |
| if(att.namespaceUri() != ns) |
| continue; |
| |
| switch(toToken(att.name())) |
| { |
| case Type: |
| case Validation: |
| case UseAttributeSets: |
| 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(XPATHLTYPE *const sourceLocator) |
| { |
| Q_UNUSED(sourceLocator); |
| |
| if(m_tokenSource.isEmpty()) |
| { |
| switch(m_state.top()) |
| { |
| case OutsideDocumentElement: |
| outsideDocumentElement(); |
| break; |
| case InsideStylesheetModule: |
| insideStylesheetModule(); |
| break; |
| case InsideSequenceConstructor: |
| insideSequenceConstructor(&m_tokenSource); |
| break; |
| } |
| |
| if(m_tokenSource.isEmpty()) |
| { |
| *sourceLocator = currentSourceLocator(); |
| return Token(T_END_OF_FILE); |
| } |
| else |
| return m_tokenSource.head()->nextToken(sourceLocator); |
| } |
| else |
| { |
| do |
| { |
| const Token candidate(m_tokenSource.head()->nextToken(sourceLocator)); |
| if (candidate.type == T_END_OF_FILE) |
| m_tokenSource.dequeue(); |
| else |
| return candidate; |
| } |
| while(!m_tokenSource.isEmpty()); |
| |
| /* Now we will resume parsing inside the regular XSL-T(XML) file. */ |
| return nextToken(sourceLocator); |
| } |
| } |
| |
| bool XSLTTokenizer::isElement(const XSLTTokenLookup::NodeName &name) const |
| { |
| Q_ASSERT(isXSLT()); |
| Q_ASSERT(tokenType() == QXmlStreamReader::StartElement || |
| tokenType() == QXmlStreamReader::EndElement); |
| |
| return currentElementName() == name; |
| } |
| |
| inline bool XSLTTokenizer::isXSLT() const |
| { |
| Q_ASSERT_X(tokenType() == QXmlStreamReader::StartElement || |
| tokenType() == QXmlStreamReader::EndElement, |
| Q_FUNC_INFO, "The current token state must be StartElement or EndElement."); |
| /* Possible optimization: let MaintainingReader set an m_isXSLT which we |
| * read. */ |
| return namespaceUri() == CommonNamespaces::XSLT; |
| } |
| |
| void XSLTTokenizer::queueOnExit(QStack<Token> &source, |
| TokenSource::Queue *const destination) |
| { |
| while(!source.isEmpty()) |
| queueToken(source.pop(), destination); |
| } |
| |
| void XSLTTokenizer::outsideDocumentElement() |
| { |
| while(!atEnd()) |
| { |
| switch(readNext()) |
| { |
| case QXmlStreamReader::StartElement: |
| { |
| /* First, we synthesize one of the built-in templates, |
| * see section 6.6 Built-in Template Rules. |
| * |
| * Note that insideStylesheetModule() can be called multiple |
| * times so we can't do it there. */ |
| { |
| /* Start with the one for text nodes and attributes. |
| * declare template matches (text() | @*) mode #all |
| * { |
| * text{.} |
| * }; |
| */ |
| |
| /* declare template matches (text() | @*) */ |
| queueToken(T_DECLARE, &m_tokenSource); |
| queueToken(T_TEMPLATE, &m_tokenSource); |
| queueToken(T_MATCHES, &m_tokenSource); |
| queueToken(T_LPAREN, &m_tokenSource); |
| queueToken(T_TEXT, &m_tokenSource); |
| queueToken(T_LPAREN, &m_tokenSource); |
| queueToken(T_RPAREN, &m_tokenSource); |
| queueToken(T_BAR, &m_tokenSource); |
| queueToken(T_AT_SIGN, &m_tokenSource); |
| queueToken(T_STAR, &m_tokenSource); |
| queueToken(T_RPAREN, &m_tokenSource); |
| |
| /* mode #all */ |
| queueToken(T_MODE, &m_tokenSource); |
| queueToken(Token(T_NCNAME, QLatin1String("#all")), &m_tokenSource); |
| queueToken(T_CURLY_LBRACE, &m_tokenSource); |
| |
| /* text{.} { */ |
| queueToken(T_TEXT, &m_tokenSource); |
| queueToken(T_CURLY_LBRACE, &m_tokenSource); |
| queueToken(T_DOT, &m_tokenSource); |
| queueToken(T_CURLY_RBRACE, &m_tokenSource); |
| |
| /* }; */ |
| queueToken(T_CURLY_RBRACE, &m_tokenSource); |
| queueToken(T_SEMI_COLON, &m_tokenSource); |
| } |
| |
| if(isXSLT() && isStylesheetElement()) |
| { |
| handleStandardAttributes(true); |
| QStack<Token> onExitTokens; |
| handleXMLBase(&m_tokenSource, &onExitTokens, false); |
| handleXSLTVersion(&m_tokenSource, &onExitTokens, true, 0, false, true); |
| validateElement(); |
| queueNamespaceDeclarations(&m_tokenSource, 0, true); |
| |
| /* We're a regular stylesheet. */ |
| |
| pushState(InsideStylesheetModule); |
| insideStylesheetModule(); |
| } |
| else |
| { |
| /* We're a simplified stylesheet. */ |
| |
| if(!hasAttribute(CommonNamespaces::XSLT, QLatin1String("version"))) |
| { |
| error(QtXmlPatterns::tr("In a simplified stylesheet module, attribute %1 must be present.") |
| .arg(formatKeyword(QLatin1String("version"))), |
| ReportContext::XTSE0010); |
| } |
| |
| QStack<Token> onExitTokens; |
| |
| /* We synthesize this as exemplified in |
| * 3.7 Simplified Stylesheet Modules. */ |
| queueToken(T_DECLARE, &m_tokenSource); |
| queueToken(T_TEMPLATE, &m_tokenSource); |
| queueToken(T_MATCHES, &m_tokenSource); |
| queueToken(T_LPAREN, &m_tokenSource); |
| queueToken(T_SLASH, &m_tokenSource); |
| queueToken(T_RPAREN, &m_tokenSource); |
| queueToken(T_CURLY_LBRACE, &m_tokenSource); |
| pushState(InsideSequenceConstructor); |
| |
| handleXSLTVersion(&m_tokenSource, &onExitTokens, false, 0, true); |
| handleStandardAttributes(false); |
| |
| insideSequenceConstructor(&m_tokenSource, false); |
| |
| queueOnExit(onExitTokens, &m_tokenSource); |
| queueToken(T_CURLY_RBRACE, &m_tokenSource); |
| queueToken(T_CURLY_RBRACE, &m_tokenSource); |
| queueToken(T_SEMI_COLON, &m_tokenSource); |
| } |
| |
| queueToken(T_APPLY_TEMPLATE, &m_tokenSource); |
| queueToken(T_LPAREN, &m_tokenSource); |
| queueToken(T_RPAREN, &m_tokenSource); |
| |
| break; |
| } |
| default: |
| /* Do nothing. */; |
| } |
| } |
| checkForParseError(); |
| } |
| |
| void XSLTTokenizer::queueToken(const Token &token, |
| TokenSource::Queue *const to) |
| { |
| TokenSource::Queue *const effective = to ? to : &m_tokenSource; |
| |
| effective->enqueue(TokenSource::Ptr(new SingleTokenContainer(token, currentSourceLocator()))); |
| } |
| |
| void XSLTTokenizer::pushState(const State nextState) |
| { |
| m_state.push(nextState); |
| } |
| |
| void XSLTTokenizer::leaveState() |
| { |
| m_state.pop(); |
| } |
| |
| void XSLTTokenizer::insideTemplate() |
| { |
| const bool hasPriority = hasAttribute(QLatin1String("priority")); |
| const bool hasMatch = hasAttribute(QLatin1String("match")); |
| const bool hasName = hasAttribute(QLatin1String("name")); |
| const bool hasMode = hasAttribute(QLatin1String("mode")); |
| const bool hasAs = hasAttribute(QLatin1String("as")); |
| |
| if(!hasMatch && |
| (hasMode || |
| hasPriority)) |
| { |
| error(QtXmlPatterns::tr("If element %1 has no attribute %2, it cannot have attribute %3 or %4.") |
| .arg(formatKeyword(QLatin1String("template")), |
| formatKeyword(QLatin1String("match")), |
| formatKeyword(QLatin1String("mode")), |
| formatKeyword(QLatin1String("priority"))), |
| ReportContext::XTSE0500); |
| } |
| else if(!hasMatch && !hasName) |
| { |
| error(QtXmlPatterns::tr("Element %1 must have at least one of the attributes %2 or %3.") |
| .arg(formatKeyword(QLatin1String("template")), |
| formatKeyword(QLatin1String("name")), |
| formatKeyword(QLatin1String("match"))), |
| ReportContext::XTSE0500); |
| } |
| |
| queueToken(T_DECLARE, &m_tokenSource); |
| queueToken(T_TEMPLATE, &m_tokenSource); |
| |
| if(hasName) |
| { |
| queueToken(T_NAME, &m_tokenSource); |
| queueToken(Token(T_QNAME, readAttribute(QLatin1String("name"))), &m_tokenSource); |
| } |
| |
| if(hasMatch) |
| { |
| queueToken(T_MATCHES, &m_tokenSource); |
| queueExpression(readAttribute(QLatin1String("match")), &m_tokenSource); |
| } |
| |
| if(hasMode) |
| { |
| const QString modeString(readAttribute(QLatin1String("mode")).simplified()); |
| |
| if(modeString.isEmpty()) |
| { |
| error(QtXmlPatterns::tr("At least one mode must be specified in the %1-attribute on element %2.") |
| .arg(formatKeyword(QLatin1String("mode")), |
| formatKeyword(QLatin1String("template"))), |
| ReportContext::XTSE0500); |
| } |
| |
| queueToken(T_MODE, &m_tokenSource); |
| |
| const QStringList modeList(modeString.split(QLatin1Char(' '))); |
| |
| for(int i = 0; i < modeList.count(); ++i) |
| { |
| const QString &mode = modeList.at(i); |
| |
| queueToken(Token(mode.contains(QLatin1Char(':')) ? T_QNAME : T_NCNAME, mode), &m_tokenSource); |
| |
| if(i < modeList.count() - 1) |
| queueToken(T_COMMA, &m_tokenSource); |
| } |
| } |
| |
| if(hasPriority) |
| { |
| queueToken(T_PRIORITY, &m_tokenSource); |
| queueToken(Token(T_STRING_LITERAL, readAttribute(QLatin1String("priority"))), &m_tokenSource); |
| } |
| |
| QStack<Token> onExitTokens; |
| Q_ASSERT(tokenType() == QXmlStreamReader::StartElement); |
| |
| /* queueParams moves the reader so we need to freeze the attributes. */ |
| const QXmlStreamAttributes atts(m_currentAttributes); |
| handleStandardAttributes(true); |
| queueToken(T_LPAREN, &m_tokenSource); |
| queueParams(Template, &m_tokenSource); |
| queueToken(T_RPAREN, &m_tokenSource); |
| |
| if(hasAs) |
| { |
| queueToken(T_AS, &m_tokenSource); |
| queueSequenceType(atts.value(QLatin1String("as")).toString()); |
| } |
| |
| queueToken(T_CURLY_LBRACE, &m_tokenSource); |
| |
| handleXMLBase(&m_tokenSource, &onExitTokens, true, &atts); |
| handleXSLTVersion(&m_tokenSource, &onExitTokens, true, &atts); |
| pushState(InsideSequenceConstructor); |
| startStorageOfCurrent(&m_tokenSource); |
| insideSequenceConstructor(&m_tokenSource, onExitTokens, false); |
| queueOnExit(onExitTokens, &m_tokenSource); |
| } |
| |
| void XSLTTokenizer::queueExpression(const QString &expr, |
| TokenSource::Queue *const to, |
| const bool wrapWithParantheses) |
| { |
| TokenSource::Queue *const effectiveTo = to ? to : &m_tokenSource; |
| |
| if(wrapWithParantheses) |
| queueToken(T_LPAREN, effectiveTo); |
| |
| effectiveTo->enqueue(TokenSource::Ptr(new XQueryTokenizer(expr, queryURI()))); |
| |
| if(wrapWithParantheses) |
| queueToken(T_RPAREN, effectiveTo); |
| } |
| |
| void XSLTTokenizer::queueAVT(const QString &expr, |
| TokenSource::Queue *const to) |
| { |
| queueToken(T_AVT, to); |
| queueToken(T_LPAREN, to); |
| to->enqueue(TokenSource::Ptr(new XQueryTokenizer(expr, queryURI(), |
| XQueryTokenizer::QuotAttributeContent))); |
| queueToken(T_RPAREN, to); |
| } |
| |
| void XSLTTokenizer::queueSequenceType(const QString &expr) |
| { |
| m_tokenSource.enqueue(TokenSource::Ptr(new XQueryTokenizer(expr, queryURI(), |
| XQueryTokenizer::ItemType))); |
| } |
| |
| void XSLTTokenizer::commencingExpression(bool &hasWrittenExpression, |
| TokenSource::Queue *const to) |
| { |
| if(hasWrittenExpression) |
| queueToken(T_COMMA, to); |
| else |
| hasWrittenExpression = true; |
| } |
| |
| void XSLTTokenizer::queueEmptySequence(TokenSource::Queue *const to) |
| { |
| queueToken(T_LPAREN, to); |
| queueToken(T_RPAREN, to); |
| } |
| |
| void XSLTTokenizer::insideChoose(TokenSource::Queue *const to) |
| { |
| Q_ASSERT(tokenType() == QXmlStreamReader::StartElement); |
| bool hasHandledOtherwise = false; |
| bool hasEncounteredAtLeastOneWhen = false; |
| |
| while(!atEnd()) |
| { |
| switch(readNext()) |
| { |
| case QXmlStreamReader::StartElement: |
| { |
| if(isXSLT()) |
| { |
| QStack<Token> onExitTokens; |
| handleStandardAttributes(true); |
| validateElement(); |
| |
| switch(currentElementName()) |
| { |
| case When: |
| { |
| if(hasHandledOtherwise) |
| { |
| error(QtXmlPatterns::tr("Element %1 must come last.") |
| .arg(formatKeyword(QLatin1String("otherwise"))), |
| ReportContext::XTSE0010); |
| } |
| |
| queueToken(T_IF, to); |
| queueToken(T_LPAREN, to); |
| queueExpression(readAttribute(QLatin1String("test")), to); |
| queueToken(T_RPAREN, to); |
| queueToken(T_THEN, to); |
| queueToken(T_LPAREN, to); |
| pushState(InsideSequenceConstructor); |
| insideSequenceConstructor(to); |
| queueToken(T_RPAREN, to); |
| Q_ASSERT(tokenType() == QXmlStreamReader::EndElement); |
| queueToken(T_ELSE, to); |
| hasEncounteredAtLeastOneWhen = true; |
| queueOnExit(onExitTokens, to); |
| break; |
| } |
| case Otherwise: |
| { |
| if(!hasEncounteredAtLeastOneWhen) |
| { |
| error(QtXmlPatterns::tr("At least one %1-element must occur before %2.") |
| .arg(formatKeyword(QLatin1String("when")), |
| formatKeyword(QLatin1String("otherwise"))), |
| ReportContext::XTSE0010); |
| } |
| else if(hasHandledOtherwise) |
| { |
| error(QtXmlPatterns::tr("Only one %1-element can appear.") |
| .arg(formatKeyword(QLatin1String("otherwise"))), |
| ReportContext::XTSE0010); |
| } |
| |
| pushState(InsideSequenceConstructor); |
| queueToken(T_LPAREN, to); |
| insideSequenceConstructor(to, to); |
| queueToken(T_RPAREN, to); |
| hasHandledOtherwise = true; |
| queueOnExit(onExitTokens, to); |
| break; |
| } |
| default: |
| unexpectedContent(); |
| } |
| } |
| else |
| unexpectedContent(); |
| break; |
| } |
| case QXmlStreamReader::EndElement: |
| { |
| if(isXSLT()) |
| { |
| switch(currentElementName()) |
| { |
| case Choose: |
| { |
| if(!hasEncounteredAtLeastOneWhen) |
| { |
| error(QtXmlPatterns::tr("At least one %1-element must occur inside %2.") |
| .arg(formatKeyword(QLatin1String("when")), |
| formatKeyword(QLatin1String("choose"))), |
| ReportContext::XTSE0010); |
| } |
| |
| if(!hasHandledOtherwise) |
| queueEmptySequence(to); |
| return; |
| } |
| case Otherwise: |
| continue; |
| default: |
| unexpectedContent(); |
| } |
| } |
| else |
| unexpectedContent(); |
| break; |
| } |
| case QXmlStreamReader::Comment: |
| 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; |
| Q_FALLTHROUGH(); |
| } |
| default: |
| unexpectedContent(); |
| break; |
| } |
| } |
| checkForParseError(); |
| } |
| |
| bool XSLTTokenizer::queueSelectOrSequenceConstructor(const ReportContext::ErrorCode code, |
| const bool emptynessAllowed, |
| TokenSource::Queue *const to, |
| const QXmlStreamAttributes *const attsP, |
| const bool queueEmptyOnEmpty) |
| { |
| Q_ASSERT(tokenType() == QXmlStreamReader::StartElement || attsP); |
| const NodeName elementName(currentElementName()); |
| const QXmlStreamAttributes atts(attsP ? *attsP : m_currentAttributes); |
| |
| if(atts.hasAttribute(QLatin1String("select"))) |
| { |
| queueExpression(atts.value(QLatin1String("select")).toString(), to); |
| |
| /* First, verify that we don't have a body. */ |
| if(skipSubTree(true)) |
| { |
| error(QtXmlPatterns::tr("When attribute %1 is present on %2, a sequence " |
| "constructor cannot be used.").arg(formatKeyword(QLatin1String("select")), |
| formatKeyword(toString(elementName))), |
| code); |
| } |
| |
| return true; |
| } |
| else |
| { |
| pushState(InsideSequenceConstructor); |
| if(!insideSequenceConstructor(to, true, queueEmptyOnEmpty) && !emptynessAllowed) |
| { |
| error(QtXmlPatterns::tr("Element %1 must have either a %2-attribute " |
| "or a sequence constructor.").arg(formatKeyword(toString(elementName)), |
| formatKeyword(QLatin1String("select"))), |
| code); |
| |
| } |
| |
| return false; |
| } |
| } |
| |
| void XSLTTokenizer::queueSimpleContentConstructor(const ReportContext::ErrorCode code, |
| const bool emptynessAllowed, |
| TokenSource::Queue *const to, |
| const bool selectOnlyFirst) |
| { |
| queueToken(T_INTERNAL_NAME, to); |
| queueToken(Token(T_NCNAME, QLatin1String("generic-string-join")), to); |
| queueToken(T_LPAREN, to); |
| |
| /* We have to read the attribute before calling |
| * queueSelectOrSequenceConstructor(), since it advances the reader. */ |
| const bool hasSeparator = m_currentAttributes.hasAttribute(QLatin1String("separator")); |
| const QString separatorAVT(m_currentAttributes.value(QLatin1String("separator")).toString()); |
| |
| queueToken(T_LPAREN, to); |
| const bool viaSelectAttribute = queueSelectOrSequenceConstructor(code, emptynessAllowed, to); |
| queueToken(T_RPAREN, to); |
| |
| if(selectOnlyFirst) |
| { |
| queueToken(T_LBRACKET, to); |
| queueToken(Token(T_NUMBER, QChar::fromLatin1('1')), to); |
| queueToken(T_RBRACKET, to); |
| } |
| |
| queueToken(T_COMMA, to); |
| |
| if(hasSeparator) |
| queueAVT(separatorAVT, to); |
| else |
| { |
| /* The default value depends on whether the value is from @select, or from |
| * the sequence constructor. */ |
| queueToken(Token(T_STRING_LITERAL, viaSelectAttribute ? QString(QLatin1Char(' ')) |
| : QString()), |
| to); |
| } |
| |
| queueToken(T_RPAREN, to); |
| } |
| |
| void XSLTTokenizer::queueTextConstructor(QString &chars, |
| bool &hasWrittenExpression, |
| TokenSource::Queue *const to) |
| { |
| if(!chars.isEmpty()) |
| { |
| commencingExpression(hasWrittenExpression, to); |
| queueToken(T_TEXT, to); |
| queueToken(T_CURLY_LBRACE, to); |
| queueToken(Token(T_STRING_LITERAL, chars), to); |
| queueToken(T_CURLY_RBRACE, to); |
| chars.clear(); |
| } |
| } |
| |
| void XSLTTokenizer::queueVariableDeclaration(const VariableType variableType, |
| TokenSource::Queue *const to) |
| { |
| Q_ASSERT(tokenType() == QXmlStreamReader::StartElement); |
| |
| if(variableType == VariableInstruction) |
| { |
| queueToken(T_LET, to); |
| queueToken(T_INTERNAL, to); |
| } |
| else if(variableType == VariableDeclaration || variableType == GlobalParameter) |
| { |
| queueToken(T_DECLARE, to); |
| queueToken(T_VARIABLE, to); |
| queueToken(T_INTERNAL, to); |
| } |
| |
| queueToken(T_DOLLAR, to); |
| |
| queueExpression(readAttribute(QLatin1String("name")), to, false); |
| |
| const bool hasAs = m_currentAttributes.hasAttribute(QLatin1String("as")); |
| if(hasAs) |
| { |
| queueToken(T_AS, to); |
| queueSequenceType(m_currentAttributes.value(QLatin1String("as")).toString()); |
| } |
| |
| if(variableType == FunctionParameter) |
| { |
| skipBodyOfParam(ReportContext::XTSE0760); |
| return; |
| } |
| |
| /* We must do this here, because queueSelectOrSequenceConstructor() |
| * advances the reader. */ |
| const bool hasSelect = hasAttribute(QLatin1String("select")); |
| const bool isRequired = hasAttribute(QLatin1String("required")) ? attributeYesNo(QLatin1String("required")) : false; |
| |
| TokenSource::Queue storage; |
| queueSelectOrSequenceConstructor(ReportContext::XTSE0620, true, &storage, 0, false); |
| |
| /* XSL-T has some wicked rules, see |
| * 9.3 Values of Variables and Parameters. */ |
| |
| const bool hasQueuedContent = !storage.isEmpty(); |
| |
| /* The syntax for global parameters is: |
| * |
| * declare variable $var external := 'defaultValue'; |
| */ |
| if(variableType == GlobalParameter) |
| queueToken(T_EXTERNAL, to); |
| |
| if(isRequired) |
| { |
| if(hasQueuedContent) |
| { |
| error(QtXmlPatterns::tr("When a parameter is required, a default value " |
| "cannot be supplied through a %1-attribute or " |
| "a sequence constructor.").arg(formatKeyword(QLatin1String("select"))), |
| ReportContext::XTSE0010); |
| } |
| } |
| else |
| { |
| if(hasQueuedContent) |
| { |
| queueToken(T_ASSIGN, to); |
| |
| if(!hasSelect && !hasAs && !hasQueuedContent) |
| queueToken(Token(T_STRING_LITERAL, QString()), to); |
| else if(hasAs || hasSelect) |
| queueToken(T_LPAREN, to); |
| else |
| { |
| queueToken(T_DOCUMENT, to); |
| queueToken(T_INTERNAL, to); |
| queueToken(T_CURLY_LBRACE, to); |
| } |
| } |
| else |
| { |
| if(!hasAs) |
| { |
| queueToken(T_ASSIGN, to); |
| queueToken(Token(T_STRING_LITERAL, QString()), to); |
| } |
| else if(variableType == VariableDeclaration || variableType == VariableInstruction) |
| { |
| queueToken(T_ASSIGN, to); |
| queueEmptySequence(to); |
| } |
| } |
| |
| /* storage has tokens if hasSelect or hasQueuedContent is true. */ |
| if(hasSelect | hasQueuedContent) |
| *to += storage; |
| |
| if(hasQueuedContent) |
| { |
| if(!hasSelect && !hasAs && !hasQueuedContent) |
| queueToken(Token(T_STRING_LITERAL, QString()), to); |
| else if(hasAs || hasSelect) |
| queueToken(T_RPAREN, to); |
| else |
| queueToken(T_CURLY_RBRACE, to); |
| } |
| } |
| |
| if(variableType == VariableInstruction) |
| queueToken(T_RETURN, to); |
| else if(variableType == VariableDeclaration || variableType == GlobalParameter) |
| queueToken(T_SEMI_COLON, to); |
| } |
| |
| void XSLTTokenizer::startStorageOfCurrent(TokenSource::Queue *const to) |
| { |
| queueToken(T_CURRENT, to); |
| queueToken(T_CURLY_LBRACE, to); |
| } |
| |
| void XSLTTokenizer::endStorageOfCurrent(TokenSource::Queue *const to) |
| { |
| queueToken(T_CURLY_RBRACE, to); |
| } |
| |
| void XSLTTokenizer::queueNamespaceDeclarations(TokenSource::Queue *const to, |
| QStack<Token> *const queueOnExit, |
| const bool isDeclaration) |
| { |
| Q_ASSERT(tokenType() == QXmlStreamReader::StartElement); |
| Q_ASSERT_X(isDeclaration || queueOnExit, |
| Q_FUNC_INFO, |
| "If isDeclaration is false, queueOnExit must be passed."); |
| |
| const QXmlStreamNamespaceDeclarations nss(namespaceDeclarations()); |
| |
| for(int i = 0; i < nss.count(); ++i) |
| { |
| const QXmlStreamNamespaceDeclaration &at = nss.at(i); |
| queueToken(T_DECLARE, to); |
| queueToken(T_NAMESPACE, to); |
| queueToken(Token(T_NCNAME, at.prefix().toString()), to); |
| queueToken(T_G_EQ, to); |
| queueToken(Token(T_STRING_LITERAL, at.namespaceUri().toString()), to); |
| |
| if(isDeclaration) |
| { |
| queueToken(T_INTERNAL, to); |
| queueToken(T_SEMI_COLON, to); |
| } |
| else |
| { |
| queueToken(T_CURLY_LBRACE, to); |
| queueOnExit->push(T_CURLY_RBRACE); |
| } |
| } |
| } |
| |
| bool XSLTTokenizer::insideSequenceConstructor(TokenSource::Queue *const to, |
| const bool initialAdvance, |
| const bool queueEmptyOnEmpty) |
| { |
| QStack<Token> onExitTokens; |
| return insideSequenceConstructor(to, onExitTokens, initialAdvance, queueEmptyOnEmpty); |
| } |
| |
| bool XSLTTokenizer::insideSequenceConstructor(TokenSource::Queue *const to, |
| QStack<Token> &onExitTokens, |
| const bool initialAdvance, |
| const bool queueEmptyOnEmpty) |
| { |
| bool effectiveInitialAdvance = initialAdvance; |
| bool hasWrittenExpression = false; |
| |
| /* Buffer which all text nodes, that might be split up by comments, |
| * processing instructions and CDATA sections, are appended to. */ |
| QString characters; |
| |
| while(!atEnd()) |
| { |
| if(effectiveInitialAdvance) |
| readNext(); |
| else |
| effectiveInitialAdvance = true; |
| |
| switch(tokenType()) |
| { |
| case QXmlStreamReader::StartElement: |
| { |
| queueTextConstructor(characters, hasWrittenExpression, to); |
| handleXMLBase(to, &onExitTokens); |
| |
| pushState(InsideSequenceConstructor); |
| |
| commencingExpression(hasWrittenExpression, to); |
| |
| if(isXSLT()) |
| { |
| handleXSLTVersion(&m_tokenSource, &onExitTokens, true); |
| handleStandardAttributes(true); |
| validateElement(); |
| |
| queueNamespaceDeclarations(to, &onExitTokens); |
| |
| switch(currentElementName()) |
| { |
| case If: |
| { |
| queueToken(T_IF, to); |
| queueToken(T_LPAREN, to); |
| |
| queueExpression(readAttribute(QLatin1String("test")), to); |
| queueToken(T_RPAREN, to); |
| queueToken(T_THEN, to); |
| |
| queueToken(T_LPAREN, to); |
| pushState(InsideSequenceConstructor); |
| insideSequenceConstructor(to); |
| |
| break; |
| } |
| case Choose: |
| { |
| insideChoose(to); |
| break; |
| } |
| case ValueOf: |
| { |
| /* We generate a computed text node constructor. */ |
| queueToken(T_TEXT, to); |
| queueToken(T_CURLY_LBRACE, to); |
| |
| queueSimpleContentConstructor(ReportContext::XTSE0870, true, to, |
| !hasAttribute(QLatin1String("separator")) && m_processingMode.top() == BackwardsCompatible); |
| queueToken(T_CURLY_RBRACE, to); |
| break; |
| } |
| case Sequence: |
| { |
| queueExpression(readAttribute(QLatin1String("select")), to); |
| parseFallbacksOnly(); |
| break; |
| } |
| case Text: |
| { |
| queueToken(T_TEXT, to); |
| queueToken(T_CURLY_LBRACE, to); |
| |
| queueToken(Token(T_STRING_LITERAL, readElementText()), to); |
| queueToken(T_CURLY_RBRACE, to); |
| break; |
| } |
| case Variable: |
| { |
| queueVariableDeclaration(VariableInstruction, to); |
| |
| /* We wrap the children in parantheses since we may |
| * queue several expressions using the comma operator, |
| * and in that case the let-binding is only in-scope |
| * for the first expression. */ |
| queueToken(T_LPAREN, to); |
| |
| /* We don't want a comma outputted, we're expecting an |
| * expression now. */ |
| hasWrittenExpression = false; |
| |
| onExitTokens.push(T_RPAREN); |
| |
| break; |
| } |
| case CallTemplate: |
| { |
| queueToken(T_CALL_TEMPLATE, to); |
| queueToken(Token(T_QNAME, readAttribute(QLatin1String("name"))), to); |
| queueToken(T_LPAREN, to); |
| queueWithParams(CallTemplate, to); |
| queueToken(T_RPAREN, to); |
| break; |
| } |
| case ForEach: |
| { |
| queueExpression(readAttribute(QLatin1String("select")), to); |
| queueToken(T_MAP, to); |
| pushState(InsideSequenceConstructor); |
| |
| TokenSource::Queue sorts; |
| queueSorting(false, &sorts); |
| |
| |
| if(sorts.isEmpty()) |
| { |
| startStorageOfCurrent(to); |
| insideSequenceConstructor(to, false); |
| endStorageOfCurrent(to); |
| } |
| else |
| { |
| queueToken(T_SORT, to); |
| *to += sorts; |
| queueToken(T_RETURN, to); |
| startStorageOfCurrent(to); |
| insideSequenceConstructor(to, false); |
| endStorageOfCurrent(to); |
| queueToken(T_END_SORT, to); |
| } |
| |
| break; |
| } |
| case XSLTTokenLookup::Comment: |
| { |
| queueToken(T_COMMENT, to); |
| queueToken(T_INTERNAL, to); |
| queueToken(T_CURLY_LBRACE, to); |
| queueSelectOrSequenceConstructor(ReportContext::XTSE0940, true, to); |
| queueToken(T_CURLY_RBRACE, to); |
| break; |
| } |
| case CopyOf: |
| { |
| queueExpression(readAttribute(QLatin1String("select")), to); |
| // TODO |
| |
| if(readNext() == QXmlStreamReader::EndElement) |
| break; |
| else |
| { |
| error(QtXmlPatterns::tr("Element %1 cannot have children.").arg(formatKeyword(QLatin1String("copy-of"))), |
| ReportContext::XTSE0010); |
| } |
| break; |
| } |
| case AnalyzeString: |
| { |
| // TODO |
| skipSubTree(); |
| break; |
| } |
| case ResultDocument: |
| { |
| // TODO |
| pushState(InsideSequenceConstructor); |
| insideSequenceConstructor(to); |
| break; |
| } |
| case Copy: |
| { |
| /* We translate: |
| * <xsl:copy>expr</xsl:copy> |
| * into: |
| * |
| * let $body := expr |
| * return |
| * if(self::element()) then |
| * element internal {node-name()} {$body} |
| * else if(self::document-node()) then |
| * document internal {$body} |
| * else (: This includes comments, processing-instructions, |
| * attributes, and comments. :) |
| * . |
| * |
| * TODO node identity is the same as the old node. |
| * TODO namespace bindings are lost when elements are constructed |
| */ |
| |
| /* let $body := expr */ |
| queueToken(T_LET, to); |
| queueToken(T_INTERNAL, to); |
| queueToken(T_DOLLAR, to); |
| queueToken(Token(T_NCNAME, QString(QLatin1Char('b'))), to); // TODO we need an internal name |
| queueToken(T_ASSIGN, to); |
| queueToken(T_LPAREN, to); |
| pushState(InsideSequenceConstructor); |
| /* Don't queue an empty sequence, we want the dot. */ |
| insideSequenceConstructor(to); |
| queueToken(T_RPAREN, to); |
| queueToken(T_RETURN, to); |
| |
| /* if(self::element()) then */ |
| queueToken(T_IF, to); |
| queueToken(T_LPAREN, to); |
| queueToken(T_SELF, to); |
| queueToken(T_COLONCOLON, to); |
| queueToken(T_ELEMENT, to); |
| queueToken(T_LPAREN, to); |
| queueToken(T_RPAREN, to); |
| queueToken(T_RPAREN, to); |
| queueToken(T_THEN, to); |
| |
| /* element internal {node-name()} {$body} */ |
| queueToken(T_ELEMENT, to); |
| queueToken(T_INTERNAL, to); |
| queueToken(T_CURLY_LBRACE, to); |
| queueToken(Token(T_NCNAME, QLatin1String("node-name")), to); // TODO what if the default ns changes? |
| queueToken(T_LPAREN, to); |
| queueToken(T_DOT, to); |
| queueToken(T_RPAREN, to); |
| queueToken(T_CURLY_RBRACE, to); |
| queueToken(T_CURLY_LBRACE, to); |
| queueToken(T_DOLLAR, to); |
| queueToken(Token(T_NCNAME, QString(QLatin1Char('b'))), to); // TODO we need an internal name |
| queueToken(T_CURLY_RBRACE, to); |
| |
| /* else if(self::document-node()) then */ |
| queueToken(T_ELSE, to); |
| queueToken(T_IF, to); |
| queueToken(T_LPAREN, to); |
| queueToken(T_SELF, to); |
| queueToken(T_COLONCOLON, to); |
| queueToken(T_DOCUMENT_NODE, to); |
| queueToken(T_LPAREN, to); |
| queueToken(T_RPAREN, to); |
| queueToken(T_RPAREN, to); |
| queueToken(T_THEN, to); |
| |
| /* document internal {$body} */ |
| queueToken(T_DOCUMENT, to); |
| queueToken(T_INTERNAL, to); |
| queueToken(T_CURLY_LBRACE, to); |
| queueToken(T_DOLLAR, to); |
| queueToken(Token(T_NCNAME, QString(QLatin1Char('b'))), to); // TODO we need an internal name |
| queueToken(T_CURLY_RBRACE, to); |
| |
| /* else . */ |
| queueToken(T_ELSE, to); |
| queueToken(T_DOT, to); |
| |
| break; |
| } |
| case XSLTTokenLookup::ProcessingInstruction: |
| { |
| queueToken(T_PROCESSING_INSTRUCTION, to); |
| queueToken(T_CURLY_LBRACE, to); |
| queueAVT(readAttribute(QLatin1String("name")), to); |
| queueToken(T_CURLY_RBRACE, to); |
| queueToken(T_CURLY_LBRACE, to); |
| queueSelectOrSequenceConstructor(ReportContext::XTSE0880, true, to); |
| queueToken(T_CURLY_RBRACE, to); |
| break; |
| } |
| case Document: |
| { |
| handleValidationAttributes(false); |
| |
| // TODO base-URI |
| queueToken(T_DOCUMENT, to); |
| queueToken(T_INTERNAL, to); |
| queueToken(T_CURLY_LBRACE, to); |
| pushState(InsideSequenceConstructor); |
| insideSequenceConstructor(to); |
| queueToken(T_CURLY_RBRACE, to); |
| break; |
| } |
| case Element: |
| { |
| handleValidationAttributes(false); |
| |
| // TODO base-URI |
| queueToken(T_ELEMENT, to); |
| queueToken(T_INTERNAL, to); |
| |
| /* The name. */ |
| queueToken(T_CURLY_LBRACE, to); |
| // TODO only strings allowed, not qname values. |
| queueAVT(readAttribute(QLatin1String("name")), to); |
| queueToken(T_CURLY_RBRACE, to); |
| |
| /* The sequence constructor. */ |
| queueToken(T_CURLY_LBRACE, to); |
| pushState(InsideSequenceConstructor); |
| insideSequenceConstructor(to); |
| queueToken(T_CURLY_RBRACE, to); |
| break; |
| } |
| case Attribute: |
| { |
| handleValidationAttributes(false); |
| |
| // TODO base-URI |
| queueToken(T_ATTRIBUTE, to); |
| queueToken(T_INTERNAL, to); |
| |
| /* The name. */ |
| queueToken(T_CURLY_LBRACE, to); |
| // TODO only strings allowed, not qname values. |
| queueAVT(readAttribute(QLatin1String("name")), to); |
| queueToken(T_CURLY_RBRACE, to); |
| |
| /* The sequence constructor. */ |
| queueToken(T_CURLY_LBRACE, to); |
| queueSimpleContentConstructor(ReportContext::XTSE0840, |
| true, to); |
| queueToken(T_CURLY_RBRACE, to); |
| break; |
| } |
| case Namespace: |
| { |
| queueToken(T_NAMESPACE, to); |
| |
| /* The name. */ |
| queueToken(T_CURLY_LBRACE, to); |
| queueAVT(readAttribute(QLatin1String("name")), to); |
| queueToken(T_CURLY_RBRACE, to); |
| |
| /* The sequence constructor. */ |
| queueToken(T_CURLY_LBRACE, to); |
| queueSelectOrSequenceConstructor(ReportContext::XTSE0910, |
| false, to); |
| queueToken(T_CURLY_RBRACE, to); |
| break; |
| } |
| case PerformSort: |
| { |
| /* For: |
| * <xsl:perform-sort select="$in"> |
| * <xsl:sort select="@key"/> |
| * </xsl:perform-sort> |
| * |
| * we generate: |
| * |
| * $in map sort order by @key |
| * return . |
| * end_sort |
| */ |
| |
| /* In XQuery, the sort keys appear after the expression |
| * supplying the initial sequence, while in |
| * xsl:perform-sort, if a sequence constructor is used, |
| * they appear in the opposite order. Hence, we need to |
| * reorder it. */ |
| |
| /* We store the attributes of xsl:perform-sort, before |
| * queueSorting() advances the reader. */ |
| const QXmlStreamAttributes atts(m_currentAttributes); |
| |
| TokenSource::Queue sorts; |
| queueSorting(true, &sorts); |
| queueSelectOrSequenceConstructor(ReportContext::XTSE1040, |
| true, |
| to, |
| &atts); |
| /* queueSelectOrSequenceConstructor() positions us on EndElement. */ |
| effectiveInitialAdvance = false; |
| queueToken(T_MAP, to); |
| queueToken(T_SORT, to); |
| *to += sorts; |
| queueToken(T_RETURN, to); |
| queueToken(T_DOT, to); |
| queueToken(T_END_SORT, to); |
| |
| break; |
| } |
| case Message: |
| { |
| // TODO |
| queueEmptySequence(to); |
| skipSubTree(); |
| break; |
| } |
| case ApplyTemplates: |
| { |
| if(hasAttribute(QLatin1String("select"))) |
| queueExpression(readAttribute(QLatin1String("select")), to); |
| else |
| { |
| queueToken(T_CHILD, to); |
| queueToken(T_COLONCOLON, to); |
| queueToken(T_NODE, to); |
| queueToken(T_LPAREN, to); |
| queueToken(T_RPAREN, to); |
| } |
| |
| bool hasMode = hasAttribute(QLatin1String("mode")); |
| QString mode; |
| |
| if(hasMode) |
| mode = readAttribute(QLatin1String("mode")).trimmed(); |
| |
| queueToken(T_FOR_APPLY_TEMPLATE, to); |
| |
| TokenSource::Queue sorts; |
| queueSorting(false, &sorts, true); |
| |
| if(!sorts.isEmpty()) |
| { |
| queueToken(T_SORT, to); |
| *to += sorts; |
| queueToken(T_RETURN, to); |
| } |
| |
| queueToken(T_APPLY_TEMPLATE, to); |
| |
| if(hasMode) |
| { |
| queueToken(T_MODE, to); |
| queueToken(Token(mode.startsWith(QLatin1Char('#')) ? T_NCNAME : T_QNAME, mode), to); |
| } |
| |
| queueToken(T_LPAREN, to); |
| queueWithParams(ApplyTemplates, to, false); |
| queueToken(T_RPAREN, to); |
| |
| if(!sorts.isEmpty()) |
| queueToken(T_END_SORT, to); |
| |
| break; |
| } |
| default: |
| unexpectedContent(); |
| } |
| } |
| else |
| { |
| handleXSLTVersion(&m_tokenSource, &onExitTokens, true); |
| handleStandardAttributes(false); |
| handleValidationAttributes(false); |
| |
| /* We're generating an element constructor. */ |
| queueNamespaceDeclarations(to, &onExitTokens); // TODO same in the isXSLT() branch |
| queueToken(T_ELEMENT, to); |
| queueToken(T_INTERNAL, to); |
| queueToken(Token(T_QNAME, qualifiedName().toString()), to); |
| queueToken(T_CURLY_LBRACE, to); |
| const int len = m_currentAttributes.count(); |
| |
| for(int i = 0; i < len; ++i) |
| { |
| const QXmlStreamAttribute &at = m_currentAttributes.at(i); |
| |
| /* We don't want to generate constructors for XSL-T attributes. */ |
| if(at.namespaceUri() == CommonNamespaces::XSLT) |
| continue; |
| |
| queueToken(T_ATTRIBUTE, to); |
| queueToken(T_INTERNAL, to); |
| |
| queueToken(Token(at.prefix().isEmpty() ? T_NCNAME : T_QNAME, at.qualifiedName().toString()), to); |
| queueToken(T_CURLY_LBRACE, to); |
| queueAVT(at.value().toString(), to); |
| queueToken(T_CURLY_RBRACE, to); |
| queueToken(T_COMMA, to); |
| } |
| |
| pushState(InsideSequenceConstructor); |
| insideSequenceConstructor(to); |
| Q_ASSERT(tokenType() == QXmlStreamReader::EndElement || hasError()); |
| } |
| |
| continue; |
| } |
| case QXmlStreamReader::EndElement: |
| { |
| queueTextConstructor(characters, hasWrittenExpression, to); |
| leaveState(); |
| |
| if(!hasWrittenExpression && queueEmptyOnEmpty) |
| queueEmptySequence(to); |
| |
| queueOnExit(onExitTokens, to); |
| |
| if(isXSLT()) |
| { |
| Q_ASSERT(!isElement(Sequence)); |
| |
| switch(currentElementName()) |
| { |
| /* Fallthrough all these. */ |
| case When: |
| case Choose: |
| case ForEach: |
| case Otherwise: |
| case PerformSort: |
| case Message: |
| case ResultDocument: |
| case Copy: |
| case CallTemplate: |
| case Text: |
| case ValueOf: |
| { |
| hasWrittenExpression = true; |
| break; |
| } |
| case If: |
| { |
| queueToken(T_RPAREN, to); |
| queueToken(T_ELSE, to); |
| queueEmptySequence(to); |
| break; |
| } |
| case Function: |
| { |
| queueToken(T_CURLY_RBRACE, to); |
| queueToken(T_SEMI_COLON, to); |
| break; |
| } |
| case Template: |
| { |
| endStorageOfCurrent(&m_tokenSource); |
| /* TODO, fallthrough to Function. */ |
| queueToken(T_CURLY_RBRACE, to); |
| queueToken(T_SEMI_COLON, to); |
| break; |
| } |
| default: |
| ; |
| } |
| } |
| else |
| { |
| /* We're closing a direct element constructor. */ |
| hasWrittenExpression = true; |
| queueToken(T_CURLY_RBRACE, to); |
| } |
| |
| return hasWrittenExpression; |
| } |
| case QXmlStreamReader::ProcessingInstruction: |
| case QXmlStreamReader::Comment: |
| /* We do nothing, we just ignore them. */ |
| continue; |
| case QXmlStreamReader::Characters: |
| { |
| if(whitespaceToSkip()) |
| continue; |
| else |
| { |
| characters += text().toString(); |
| continue; |
| } |
| } |
| default: |
| ; |
| } |
| } |
| |
| leaveState(); |
| return hasWrittenExpression; |
| } |
| |
| bool XSLTTokenizer::isStylesheetElement() const |
| { |
| Q_ASSERT(isXSLT()); |
| Q_ASSERT(tokenType() == QXmlStreamReader::StartElement || |
| tokenType() == QXmlStreamReader::EndElement); |
| |
| const NodeName name = currentElementName(); |
| return name == Stylesheet || name == Transform; |
| } |
| |
| void XSLTTokenizer::skipBodyOfParam(const ReportContext::ErrorCode code) |
| { |
| Q_ASSERT(isXSLT()); |
| Q_ASSERT(tokenType() == QXmlStreamReader::StartElement); |
| const NodeName name(currentElementName()); |
| |
| if(skipSubTree()) |
| { |
| error(QtXmlPatterns::tr("Element %1 cannot have a sequence constructor.") |
| .arg(formatKeyword(toString(name))), |
| code); |
| } |
| } |
| |
| void XSLTTokenizer::queueWithParams(const XSLTTokenLookup::NodeName parentName, |
| TokenSource::Queue *const to, |
| const bool initialAdvance) |
| { |
| Q_ASSERT(parentName == ApplyTemplates || parentName == CallTemplate); |
| |
| bool effectiveInitialAdvance = initialAdvance; |
| bool hasQueuedParam = false; |
| |
| while(!atEnd()) |
| { |
| if(effectiveInitialAdvance) |
| readNext(); |
| else |
| effectiveInitialAdvance = true; |
| |
| switch(tokenType()) |
| { |
| case QXmlStreamReader::StartElement: |
| { |
| if(hasQueuedParam) |
| queueToken(T_COMMA, to); |
| |
| if(isXSLT() && isElement(WithParam)) |
| { |
| if(hasAttribute(QLatin1String("tunnel")) && attributeYesNo(QLatin1String("tunnel"))) |
| queueToken(T_TUNNEL, to); |
| |
| queueVariableDeclaration(WithParamVariable, to); |
| hasQueuedParam = true; |
| continue; |
| } |
| else |
| unexpectedContent(); |
| Q_FALLTHROUGH(); |
| } |
| case QXmlStreamReader::EndElement: |
| { |
| if(isElement(parentName)) |
| return; |
| else |
| continue; |
| } |
| case QXmlStreamReader::ProcessingInstruction: |
| case QXmlStreamReader::Comment: |
| continue; |
| case QXmlStreamReader::Characters: |
| if(whitespaceToSkip()) |
| continue; |
| else |
| return; |
| default: |
| unexpectedContent(); |
| } |
| } |
| unexpectedContent(); |
| } |
| |
| void XSLTTokenizer::queueParams(const XSLTTokenLookup::NodeName parentName, |
| TokenSource::Queue *const to) |
| { |
| bool hasQueuedParam = false; |
| |
| Q_ASSERT(tokenType() == QXmlStreamReader::StartElement); |
| |
| while(!atEnd()) |
| { |
| switch(readNext()) |
| { |
| case QXmlStreamReader::StartElement: |
| { |
| if(isXSLT() && isElement(Param)) |
| { |
| if(hasQueuedParam) |
| queueToken(T_COMMA, to); |
| |
| validateElement(); |
| |
| if(parentName == Function && m_currentAttributes.hasAttribute(QLatin1String("select"))) |
| { |
| error(QtXmlPatterns::tr("The attribute %1 cannot appear on %2, when it is a child of %3.") |
| .arg(formatKeyword(QLatin1String("select")), |
| formatKeyword(QLatin1String("param")), |
| formatKeyword(QLatin1String("function"))), |
| ReportContext::XTSE0760); |
| } |
| |
| if(parentName == Function && m_currentAttributes.hasAttribute(QLatin1String("required"))) |
| { |
| error(QtXmlPatterns::tr("The attribute %1 cannot appear on %2, when it is a child of %3.") |
| .arg(formatKeyword(QLatin1String("required")), |
| formatKeyword(QLatin1String("param")), |
| formatKeyword(QLatin1String("function"))), |
| ReportContext::XTSE0010); |
| } |
| |
| const bool hasTunnel = m_currentAttributes.hasAttribute(QLatin1String("tunnel")); |
| const bool isTunnel = hasTunnel ? attributeYesNo(QLatin1String("tunnel")) : false; |
| |
| if(isTunnel) |
| { |
| if(parentName == Function) |
| { |
| /* See W3C public report 5650: http://www.w3.org/Bugs/Public/show_bug.cgi?id=5650 */ |
| error(QtXmlPatterns::tr("A parameter in a function cannot be declared to be a tunnel."), |
| ReportContext::XTSE0010); |
| } |
| else |
| queueToken(T_TUNNEL, to); |
| } |
| |
| hasQueuedParam = true; |
| queueVariableDeclaration(parentName == Function ? FunctionParameter : TemplateParameter, to); |
| continue; |
| } |
| else |
| return; |
| } |
| case QXmlStreamReader::Characters: |
| { |
| if(whitespaceToSkip()) |
| continue; |
| Q_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: |
| case QXmlStreamReader::Comment: |
| continue; |
| case QXmlStreamReader::Characters: |
| if(whitespaceToSkip()) |
| continue; |
| Q_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: |
| 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: |
| case ForEach: |
| case ApplyTemplates: |
| return; |
| default: |
| ; |
| } |
| } |
| continue; |
| } |
| case QXmlStreamReader::StartElement: |
| { |
| if(isXSLT() && isElement(Sort)) |
| { |
| if(hasQueuedOneSort) |
| queueToken(T_COMMA, to); |
| |
| /* sorts are by default stable. */ |
| if(hasAttribute(QLatin1String("stable"))) |
| { |
| if(hasQueuedOneSort) |
| { |
| error(QtXmlPatterns::tr("The attribute %1 can only appear on " |
| "the first %2 element.").arg(formatKeyword(QLatin1String("stable")), |
| formatKeyword(QLatin1String("sort"))), |
| ReportContext::XTSE0020); |
| } |
| |
| if(attributeYesNo(QLatin1String("stable"))) |
| queueToken(T_STABLE, to); |
| } |
| |
| if(!hasQueuedOneSort) |
| { |
| queueToken(T_ORDER, to); |
| queueToken(T_BY, to); |
| } |
| |
| /* We store a copy such that we can use them after |
| * queueSelectOrSequenceConstructor() advances the reader. */ |
| const QXmlStreamAttributes atts(m_currentAttributes); |
| |
| const int before = to->count(); |
| |
| // TODO This doesn't work as is. @data-type can be an AVT. |
| if(atts.hasAttribute(QLatin1String("data-type"))) |
| { |
| if(readToggleAttribute(QLatin1String("data-type"), |
| QLatin1String("text"), |
| QLatin1String("number"), |
| &atts)) |
| queueToken(Token(T_NCNAME, QLatin1String("string")), to); |
| else |
| queueToken(Token(T_NCNAME, QLatin1String("number")), to); |
| } |
| /* We queue these parantheses for the sake of the function |
| * call for attribute data-type. In the case we don't have |
| * such an attribute, the parantheses are just redundant. */ |
| queueToken(T_LPAREN, to); |
| queueSelectOrSequenceConstructor(ReportContext::XTSE1015, |
| true, |
| to, |
| 0, |
| false); |
| /* If neither a select attribute or a sequence constructor is supplied, |
| * we're supposed to use the context item. */ |
| queueToken(T_RPAREN, to); |
| if(before == to->count()) |
| queueToken(T_DOT, to); |
| |
| // TODO case-order |
| // TODO lang |
| |
| // TODO This doesn't work as is. @order can be an AVT, and so can case-order and lang. |
| if(atts.hasAttribute(QLatin1String("order")) && readToggleAttribute(QLatin1String("order"), |
| QLatin1String("descending"), |
| QLatin1String("ascending"), |
| &atts)) |
| { |
| queueToken(T_DESCENDING, to); |
| } |
| else |
| { |
| /* This is the default. */ |
| queueToken(T_ASCENDING, to); |
| } |
| |
| if(atts.hasAttribute(QLatin1String("collation"))) |
| { |
| queueToken(T_INTERNAL, to); |
| queueToken(T_COLLATION, to); |
| queueAVT(atts.value(QLatin1String("collation")).toString(), to); |
| } |
| |
| hasQueuedOneSort = true; |
| continue; |
| } |
| else |
| break; |
| } |
| case QXmlStreamReader::Characters: |
| { |
| if(speciallyTreatWhitespace && isWhitespace()) |
| continue; |
| |
| if (whitespaceToSkip()) |
| continue; |
| |
| /* We have an instruction which is a text node, we're done. */ |
| break; |
| } |
| case QXmlStreamReader::ProcessingInstruction: |
| case QXmlStreamReader::Comment: |
| continue; |
| default: |
| unexpectedContent(); |
| |
| }; |
| if(oneSortRequired && !hasQueuedOneSort) |
| { |
| error(QtXmlPatterns::tr("At least one %1 element must appear as child of %2.") |
| .arg(formatKeyword(QLatin1String("sort")), formatKeyword(toString(elementName))), |
| ReportContext::XTSE0010); |
| } |
| else |
| return; |
| } |
| checkForParseError(); |
| } |
| |
| void XSLTTokenizer::insideFunction() |
| { |
| queueToken(T_DECLARE, &m_tokenSource); |
| queueToken(T_FUNCTION, &m_tokenSource); |
| queueToken(T_INTERNAL, &m_tokenSource); |
| queueToken(Token(T_QNAME, readAttribute(QLatin1String("name"))), &m_tokenSource); |
| queueToken(T_LPAREN, &m_tokenSource); |
| const QString expectedType(hasAttribute(QLatin1String("as")) ? readAttribute(QLatin1String("as")): QString()); |
| |
| if(hasAttribute(QLatin1String("override"))) |
| { |
| /* We currently have no external functions, so we don't pass it on currently. */ |
| attributeYesNo(QLatin1String("override")); |
| } |
| |
| queueParams(Function, &m_tokenSource); |
| |
| queueToken(T_RPAREN, &m_tokenSource); |
| |
| if(!expectedType.isNull()) |
| { |
| queueToken(T_AS, &m_tokenSource); |
| queueSequenceType(expectedType); |
| } |
| |
| QStack<Token> onExitTokens; |
| handleXMLBase(&m_tokenSource, &onExitTokens, true, &m_currentAttributes); |
| handleXSLTVersion(&m_tokenSource, &onExitTokens, true); |
| queueToken(T_CURLY_LBRACE, &m_tokenSource); |
| |
| pushState(InsideSequenceConstructor); |
| insideSequenceConstructor(&m_tokenSource, onExitTokens, false); |
| /* We don't queue CURLY_RBRACE, because it's done in |
| * insideSequenceConstructor(). */ |
| } |
| |
| XPATHLTYPE XSLTTokenizer::currentSourceLocator() const |
| { |
| XPATHLTYPE retval; |
| retval.first_line = lineNumber(); |
| retval.first_column = columnNumber(); |
| return retval; |
| } |
| |
| QT_END_NAMESPACE |