blob: 38e0374dbe395b7be317fea44c86045ff5337483 [file] [log] [blame]
/****************************************************************************
**
** 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(YYLTYPE *const location)
{
if(m_hasDelivered)
return Tokenizer::Token(T_END_OF_FILE);
else
{
*location = m_location;
m_hasDelivered = true;
return m_token;
}
}
XSLTTokenizer::XSLTTokenizer(QIODevice *const queryDevice,
const QUrl &location,
const ReportContext::Ptr &context,
const NamePool::Ptr &np) : Tokenizer(location)
, MaintainingReader<XSLTTokenLookup>(createElementDescriptions(), createStandardAttributes(), context, queryDevice)
, m_location(location)
, m_namePool(np)
/* We initialize after all name constants. */
, m_validationAlternatives(createValidationAlternatives())
, m_parseInfo(0)
{
Q_ASSERT(m_namePool);
pushState(OutsideDocumentElement);
}
bool XSLTTokenizer::isAnyAttributeAllowed() const
{
return m_processingMode.top() == ForwardCompatible;
}
void XSLTTokenizer::setParserContext(const ParserContext::Ptr &parseInfo)
{
m_parseInfo = parseInfo;
}
void XSLTTokenizer::validateElement() const
{
MaintainingReader<XSLTTokenLookup>::validateElement(currentElementName());
}
QSet<XSLTTokenizer::NodeName> XSLTTokenizer::createStandardAttributes()
{
QSet<NodeName> retval;
enum
{
ReservedForAttributes = 6
};
retval.reserve(6);
retval.insert(DefaultCollation);
retval.insert(ExcludeResultPrefixes);
retval.insert(ExtensionElementPrefixes);
retval.insert(UseWhen);
retval.insert(Version);
retval.insert(XpathDefaultNamespace);
Q_ASSERT(retval.count() == ReservedForAttributes);
return retval;
}
ElementDescription<XSLTTokenLookup>::Hash XSLTTokenizer::createElementDescriptions()
{
ElementDescription<XSLTTokenLookup>::Hash result;
enum
{
ReservedForElements = 40
};
result.reserve(ReservedForElements);
/* xsl:apply-templates */
{
ElementDescription<XSLTTokenLookup> &e = result[ApplyTemplates];
e.optionalAttributes.insert(Select);
e.optionalAttributes.insert(Mode);
}
/* xsl:template */
{
ElementDescription<XSLTTokenLookup> &e = result[Template];
e.optionalAttributes.insert(Match);
e.optionalAttributes.insert(Name);
e.optionalAttributes.insert(Mode);
e.optionalAttributes.insert(Priority);
e.optionalAttributes.insert(As);
}
/* xsl:text, xsl:choose and xsl:otherwise */
{
ElementDescription<XSLTTokenLookup> &e = result[Text];
result.insert(Choose, e);
result.insert(Otherwise, e);
}
/* xsl:stylesheet */
{
ElementDescription<XSLTTokenLookup> &e = result[Stylesheet];
e.requiredAttributes.insert(Version);
e.optionalAttributes.insert(Id);
e.optionalAttributes.insert(ExtensionElementPrefixes);
e.optionalAttributes.insert(ExcludeResultPrefixes);
e.optionalAttributes.insert(XpathDefaultNamespace);
e.optionalAttributes.insert(DefaultValidation);
e.optionalAttributes.insert(DefaultCollation);
e.optionalAttributes.insert(InputTypeAnnotations);
}
/* xsl:transform */
{
result[Transform] = result[Stylesheet];
}
/* xsl:value-of */
{
ElementDescription<XSLTTokenLookup> &e = result[ValueOf];
e.optionalAttributes.insert(Separator);
e.optionalAttributes.insert(Select);
}
/* xsl:variable */
{
ElementDescription<XSLTTokenLookup> &e = result[Variable];
e.requiredAttributes.insert(Name);
e.optionalAttributes.insert(Select);
e.optionalAttributes.insert(As);
}
/* xsl:when & xsl:if */
{
ElementDescription<XSLTTokenLookup> &e = result[When];
e.requiredAttributes.insert(Test);
result.insert(If, e);
}
/* xsl:sequence, xsl:for-each */
{
ElementDescription<XSLTTokenLookup> &e = result[Sequence];
e.requiredAttributes.insert(Select);
result.insert(ForEach, e);
}
/* xsl:comment */
{
ElementDescription<XSLTTokenLookup> &e = result[XSLTTokenLookup::Comment];
e.optionalAttributes.insert(Select);
}
/* xsl:processing-instruction */
{
ElementDescription<XSLTTokenLookup> &e = result[XSLTTokenLookup::ProcessingInstruction];
e.requiredAttributes.insert(Name);
e.optionalAttributes.insert(Select);
}
/* xsl:document */
{
ElementDescription<XSLTTokenLookup> &e = result[Document];
e.optionalAttributes.insert(Validation);
e.optionalAttributes.insert(Type);
}
/* xsl:element */
{
ElementDescription<XSLTTokenLookup> &e = result[Element];
e.requiredAttributes.insert(Name);
e.optionalAttributes.insert(Namespace);
e.optionalAttributes.insert(InheritNamespaces);
e.optionalAttributes.insert(UseAttributeSets);
e.optionalAttributes.insert(Validation);
e.optionalAttributes.insert(Type);
}
/* xsl:attribute */
{
ElementDescription<XSLTTokenLookup> &e = result[Attribute];
e.requiredAttributes.insert(Name);
e.optionalAttributes.insert(Namespace);
e.optionalAttributes.insert(Select);
e.optionalAttributes.insert(Separator);
e.optionalAttributes.insert(Validation);
e.optionalAttributes.insert(Type);
}
/* xsl:function */
{
ElementDescription<XSLTTokenLookup> &e = result[Function];
e.requiredAttributes.insert(Name);
e.optionalAttributes.insert(As);
e.optionalAttributes.insert(Override);
}
/* xsl:param */
{
ElementDescription<XSLTTokenLookup> &e = result[Param];
e.requiredAttributes.insert(Name);
e.optionalAttributes.insert(Select);
e.optionalAttributes.insert(As);
e.optionalAttributes.insert(Required);
e.optionalAttributes.insert(Tunnel);
}
/* xsl:namespace */
{
ElementDescription<XSLTTokenLookup> &e = result[Namespace];
e.requiredAttributes.insert(Name);
e.optionalAttributes.insert(Select);
}
/* xsl:call-template */
{
ElementDescription<XSLTTokenLookup> &e = result[CallTemplate];
e.requiredAttributes.insert(Name);
}
/* xsl:perform-sort */
{
ElementDescription<XSLTTokenLookup> &e = result[PerformSort];
e.requiredAttributes.insert(Select);
}
/* xsl:sort */
{
ElementDescription<XSLTTokenLookup> &e = result[Sort];
e.optionalAttributes.reserve(7);
e.optionalAttributes.insert(Select);
e.optionalAttributes.insert(Lang);
e.optionalAttributes.insert(Order);
e.optionalAttributes.insert(Collation);
e.optionalAttributes.insert(Stable);
e.optionalAttributes.insert(CaseOrder);
e.optionalAttributes.insert(DataType);
}
/* xsl:import-schema */
{
ElementDescription<XSLTTokenLookup> &e = result[ImportSchema];
e.optionalAttributes.reserve(2);
e.optionalAttributes.insert(Namespace);
e.optionalAttributes.insert(SchemaLocation);
}
/* xsl:message */
{
ElementDescription<XSLTTokenLookup> &e = result[Message];
e.optionalAttributes.reserve(2);
e.optionalAttributes.insert(Select);
e.optionalAttributes.insert(Terminate);
}
/* xsl:copy-of */
{
ElementDescription<XSLTTokenLookup> &e = result[CopyOf];
e.requiredAttributes.insert(Select);
e.optionalAttributes.reserve(2);
e.optionalAttributes.insert(CopyNamespaces);
e.optionalAttributes.insert(Type);
e.optionalAttributes.insert(Validation);
}
/* xsl:copy */
{
ElementDescription<XSLTTokenLookup> &e = result[Copy];
e.optionalAttributes.reserve(5);
e.optionalAttributes.insert(CopyNamespaces);
e.optionalAttributes.insert(InheritNamespaces);
e.optionalAttributes.insert(UseAttributeSets);
e.optionalAttributes.insert(Type);
e.optionalAttributes.insert(Validation);
}
/* xsl:output */
{
ElementDescription<XSLTTokenLookup> &e = result[Output];
e.optionalAttributes.reserve(17);
e.optionalAttributes.insert(Name);
e.optionalAttributes.insert(Method);
e.optionalAttributes.insert(ByteOrderMark);
e.optionalAttributes.insert(CdataSectionElements);
e.optionalAttributes.insert(DoctypePublic);
e.optionalAttributes.insert(DoctypeSystem);
e.optionalAttributes.insert(Encoding);
e.optionalAttributes.insert(EscapeUriAttributes);
e.optionalAttributes.insert(IncludeContentType);
e.optionalAttributes.insert(Indent);
e.optionalAttributes.insert(MediaType);
e.optionalAttributes.insert(NormalizationForm);
e.optionalAttributes.insert(OmitXmlDeclaration);
e.optionalAttributes.insert(Standalone);
e.optionalAttributes.insert(UndeclarePrefixes);
e.optionalAttributes.insert(UseCharacterMaps);
e.optionalAttributes.insert(Version);
}
/* xsl:attribute-set */
{
ElementDescription<XSLTTokenLookup> &e = result[AttributeSet];
e.requiredAttributes.insert(Name);
e.optionalAttributes.insert(UseAttributeSets);
}
/* xsl:include and xsl:import. */
{
ElementDescription<XSLTTokenLookup> &e = result[Include];
e.requiredAttributes.insert(Href);
result[Import] = e;
}
/* xsl:with-param */
{
ElementDescription<XSLTTokenLookup> &e = result[WithParam];
e.requiredAttributes.insert(Name);
e.optionalAttributes.insert(Select);
e.optionalAttributes.insert(As);
e.optionalAttributes.insert(Tunnel);
}
/* xsl:strip-space */
{
ElementDescription<XSLTTokenLookup> &e = result[StripSpace];
e.requiredAttributes.insert(Elements);
result.insert(PreserveSpace, e);
}
/* xsl:result-document */
{
ElementDescription<XSLTTokenLookup> &e = result[ResultDocument];
e.optionalAttributes.insert(ByteOrderMark);
e.optionalAttributes.insert(CdataSectionElements);
e.optionalAttributes.insert(DoctypePublic);
e.optionalAttributes.insert(DoctypeSystem);
e.optionalAttributes.insert(Encoding);
e.optionalAttributes.insert(EscapeUriAttributes);
e.optionalAttributes.insert(Format);
e.optionalAttributes.insert(Href);
e.optionalAttributes.insert(IncludeContentType);
e.optionalAttributes.insert(Indent);
e.optionalAttributes.insert(MediaType);
e.optionalAttributes.insert(Method);
e.optionalAttributes.insert(NormalizationForm);
e.optionalAttributes.insert(OmitXmlDeclaration);
e.optionalAttributes.insert(OutputVersion);
e.optionalAttributes.insert(Standalone);
e.optionalAttributes.insert(Type);
e.optionalAttributes.insert(UndeclarePrefixes);
e.optionalAttributes.insert(UseCharacterMaps);
e.optionalAttributes.insert(Validation);
}
/* xsl:key */
{
ElementDescription<XSLTTokenLookup> &e = result[Key];
e.requiredAttributes.insert(Name);
e.requiredAttributes.insert(Match);
e.optionalAttributes.insert(Use);
e.optionalAttributes.insert(Collation);
}
/* xsl:analyze-string */
{
ElementDescription<XSLTTokenLookup> &e = result[AnalyzeString];
e.requiredAttributes.insert(Select);
e.requiredAttributes.insert(Regex);
e.optionalAttributes.insert(Flags);
}
/* xsl:matching-substring */
{
/* We insert a default constructed value. */
result[MatchingSubstring];
}
/* xsl:non-matching-substring */
{
/* We insert a default constructed value. */
result[NonMatchingSubstring];
}
Q_ASSERT(result.count() == ReservedForElements);
return result;
}
QHash<QString, int> XSLTTokenizer::createValidationAlternatives()
{
QHash<QString, int> retval;
retval.insert(QLatin1String("preserve"), 0);
retval.insert(QLatin1String("strip"), 1);
retval.insert(QLatin1String("strict"), 2);
retval.insert(QLatin1String("lax"), 3);
return retval;
}
bool XSLTTokenizer::whitespaceToSkip() const
{
return m_stripWhitespace.top() && isWhitespace();
}
void XSLTTokenizer::unexpectedContent(const ReportContext::ErrorCode code) const
{
QString message;
ReportContext::ErrorCode effectiveCode = code;
switch(tokenType())
{
case QXmlStreamReader::StartElement:
{
if(isXSLT())
{
switch(currentElementName())
{
case Include:
effectiveCode = ReportContext::XTSE0170;
break;
case Import:
effectiveCode = ReportContext::XTSE0190;
break;
default:
;
}
}
message = QtXmlPatterns::tr("Element %1 is not allowed at this location.")
.arg(formatKeyword(name()));
break;
}
case QXmlStreamReader::Characters:
{
if(whitespaceToSkip())
return;
message = QtXmlPatterns::tr("Text nodes are not allowed at this location.");
break;
}
case QXmlStreamReader::Invalid:
{
/* It's an issue with well-formedness. */
message = escape(errorString());
break;
}
default:
Q_ASSERT(false);
}
error(message, effectiveCode);
}
void XSLTTokenizer::checkForParseError() const
{
if(hasError())
{
error(QtXmlPatterns::tr("Parse error: %1").arg(escape(errorString())), ReportContext::XTSE0010);
}
}
QString XSLTTokenizer::readElementText()
{
QString result;
while(!atEnd())
{
switch(readNext())
{
case QXmlStreamReader::Characters:
{
result += text().toString();
continue;
}
case QXmlStreamReader::Comment:
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(YYLTYPE *const sourceLocator)
{
Q_UNUSED(sourceLocator);
if(m_tokenSource.isEmpty())
{
switch(m_state.top())
{
case OutsideDocumentElement:
outsideDocumentElement();
break;
case InsideStylesheetModule:
insideStylesheetModule();
break;
case InsideSequenceConstructor:
insideSequenceConstructor(&m_tokenSource);
break;
}
if(m_tokenSource.isEmpty())
{
*sourceLocator = currentSourceLocator();
return Token(T_END_OF_FILE);
}
else
return m_tokenSource.head()->nextToken(sourceLocator);
}
else
{
do
{
const Token candidate(m_tokenSource.head()->nextToken(sourceLocator));
if (candidate.type == T_END_OF_FILE)
m_tokenSource.dequeue();
else
return candidate;
}
while(!m_tokenSource.isEmpty());
/* Now we will resume parsing inside the regular XSL-T(XML) file. */
return nextToken(sourceLocator);
}
}
bool XSLTTokenizer::isElement(const XSLTTokenLookup::NodeName &name) const
{
Q_ASSERT(isXSLT());
Q_ASSERT(tokenType() == QXmlStreamReader::StartElement ||
tokenType() == QXmlStreamReader::EndElement);
return currentElementName() == name;
}
inline bool XSLTTokenizer::isXSLT() const
{
Q_ASSERT_X(tokenType() == QXmlStreamReader::StartElement ||
tokenType() == QXmlStreamReader::EndElement,
Q_FUNC_INFO, "The current token state must be StartElement or EndElement.");
/* Possible optimization: let MaintainingReader set an m_isXSLT which we
* read. */
return namespaceUri() == CommonNamespaces::XSLT;
}
void XSLTTokenizer::queueOnExit(QStack<Token> &source,
TokenSource::Queue *const destination)
{
while(!source.isEmpty())
queueToken(source.pop(), destination);
}
void XSLTTokenizer::outsideDocumentElement()
{
while(!atEnd())
{
switch(readNext())
{
case QXmlStreamReader::StartElement:
{
/* First, we synthesize one of the built-in templates,
* see section 6.6 Built-in Template Rules.
*
* Note that insideStylesheetModule() can be called multiple
* times so we can't do it there. */
{
/* Start with the one for text nodes and attributes.
* declare template matches (text() | @*) mode #all
* {
* text{.}
* };
*/
/* declare template matches (text() | @*) */
queueToken(T_DECLARE, &m_tokenSource);
queueToken(T_TEMPLATE, &m_tokenSource);
queueToken(T_MATCHES, &m_tokenSource);
queueToken(T_LPAREN, &m_tokenSource);
queueToken(T_TEXT, &m_tokenSource);
queueToken(T_LPAREN, &m_tokenSource);
queueToken(T_RPAREN, &m_tokenSource);
queueToken(T_BAR, &m_tokenSource);
queueToken(T_AT_SIGN, &m_tokenSource);
queueToken(T_STAR, &m_tokenSource);
queueToken(T_RPAREN, &m_tokenSource);
/* mode #all */
queueToken(T_MODE, &m_tokenSource);
queueToken(Token(T_NCNAME, QLatin1String("#all")), &m_tokenSource);
queueToken(T_CURLY_LBRACE, &m_tokenSource);
/* text{.} { */
queueToken(T_TEXT, &m_tokenSource);
queueToken(T_CURLY_LBRACE, &m_tokenSource);
queueToken(T_DOT, &m_tokenSource);
queueToken(T_CURLY_RBRACE, &m_tokenSource);
/* }; */
queueToken(T_CURLY_RBRACE, &m_tokenSource);
queueToken(T_SEMI_COLON, &m_tokenSource);
}
if(isXSLT() && isStylesheetElement())
{
handleStandardAttributes(true);
QStack<Token> onExitTokens;
handleXMLBase(&m_tokenSource, &onExitTokens, false);
handleXSLTVersion(&m_tokenSource, &onExitTokens, true, 0, false, true);
validateElement();
queueNamespaceDeclarations(&m_tokenSource, 0, true);
/* We're a regular stylesheet. */
pushState(InsideStylesheetModule);
insideStylesheetModule();
}
else
{
/* We're a simplified stylesheet. */
if(!hasAttribute(CommonNamespaces::XSLT, QLatin1String("version")))
{
error(QtXmlPatterns::tr("In a simplified stylesheet module, attribute %1 must be present.")
.arg(formatKeyword(QLatin1String("version"))),
ReportContext::XTSE0010);
}
QStack<Token> onExitTokens;
/* We synthesize this as exemplified in
* 3.7 Simplified Stylesheet Modules. */
queueToken(T_DECLARE, &m_tokenSource);
queueToken(T_TEMPLATE, &m_tokenSource);
queueToken(T_MATCHES, &m_tokenSource);
queueToken(T_LPAREN, &m_tokenSource);
queueToken(T_SLASH, &m_tokenSource);
queueToken(T_RPAREN, &m_tokenSource);
queueToken(T_CURLY_LBRACE, &m_tokenSource);
pushState(InsideSequenceConstructor);
handleXSLTVersion(&m_tokenSource, &onExitTokens, false, 0, true);
handleStandardAttributes(false);
insideSequenceConstructor(&m_tokenSource, false);
queueOnExit(onExitTokens, &m_tokenSource);
queueToken(T_CURLY_RBRACE, &m_tokenSource);
queueToken(T_CURLY_RBRACE, &m_tokenSource);
queueToken(T_SEMI_COLON, &m_tokenSource);
}
queueToken(T_APPLY_TEMPLATE, &m_tokenSource);
queueToken(T_LPAREN, &m_tokenSource);
queueToken(T_RPAREN, &m_tokenSource);
break;
}
default:
/* Do nothing. */;
}
}
checkForParseError();
}
void XSLTTokenizer::queueToken(const Token &token,
TokenSource::Queue *const to)
{
TokenSource::Queue *const effective = to ? to : &m_tokenSource;
effective->enqueue(TokenSource::Ptr(new SingleTokenContainer(token, currentSourceLocator())));
}
void XSLTTokenizer::pushState(const State nextState)
{
m_state.push(nextState);
}
void XSLTTokenizer::leaveState()
{
m_state.pop();
}
void XSLTTokenizer::insideTemplate()
{
const bool hasPriority = hasAttribute(QLatin1String("priority"));
const bool hasMatch = hasAttribute(QLatin1String("match"));
const bool hasName = hasAttribute(QLatin1String("name"));
const bool hasMode = hasAttribute(QLatin1String("mode"));
const bool hasAs = hasAttribute(QLatin1String("as"));
if(!hasMatch &&
(hasMode ||
hasPriority))
{
error(QtXmlPatterns::tr("If element %1 has no attribute %2, it cannot have attribute %3 or %4.")
.arg(formatKeyword(QLatin1String("template")),
formatKeyword(QLatin1String("match")),
formatKeyword(QLatin1String("mode")),
formatKeyword(QLatin1String("priority"))),
ReportContext::XTSE0500);
}
else if(!hasMatch && !hasName)
{
error(QtXmlPatterns::tr("Element %1 must have at least one of the attributes %2 or %3.")
.arg(formatKeyword(QLatin1String("template")),
formatKeyword(QLatin1String("name")),
formatKeyword(QLatin1String("match"))),
ReportContext::XTSE0500);
}
queueToken(T_DECLARE, &m_tokenSource);
queueToken(T_TEMPLATE, &m_tokenSource);
if(hasName)
{
queueToken(T_NAME, &m_tokenSource);
queueToken(Token(T_QNAME, readAttribute(QLatin1String("name"))), &m_tokenSource);
}
if(hasMatch)
{
queueToken(T_MATCHES, &m_tokenSource);
queueExpression(readAttribute(QLatin1String("match")), &m_tokenSource);
}
if(hasMode)
{
const QString modeString(readAttribute(QLatin1String("mode")).simplified());
if(modeString.isEmpty())
{
error(QtXmlPatterns::tr("At least one mode must be specified in the %1-attribute on element %2.")
.arg(formatKeyword(QLatin1String("mode")),
formatKeyword(QLatin1String("template"))),
ReportContext::XTSE0500);
}
queueToken(T_MODE, &m_tokenSource);
const QStringList modeList(modeString.split(QLatin1Char(' ')));
for(int i = 0; i < modeList.count(); ++i)
{
const QString &mode = modeList.at(i);
queueToken(Token(mode.contains(QLatin1Char(':')) ? T_QNAME : T_NCNAME, mode), &m_tokenSource);
if(i < modeList.count() - 1)
queueToken(T_COMMA, &m_tokenSource);
}
}
if(hasPriority)
{
queueToken(T_PRIORITY, &m_tokenSource);
queueToken(Token(T_STRING_LITERAL, readAttribute(QLatin1String("priority"))), &m_tokenSource);
}
QStack<Token> onExitTokens;
Q_ASSERT(tokenType() == QXmlStreamReader::StartElement);
/* queueParams moves the reader so we need to freeze the attributes. */
const QXmlStreamAttributes atts(m_currentAttributes);
handleStandardAttributes(true);
queueToken(T_LPAREN, &m_tokenSource);
queueParams(Template, &m_tokenSource);
queueToken(T_RPAREN, &m_tokenSource);
if(hasAs)
{
queueToken(T_AS, &m_tokenSource);
queueSequenceType(atts.value(QLatin1String("as")).toString());
}
queueToken(T_CURLY_LBRACE, &m_tokenSource);
handleXMLBase(&m_tokenSource, &onExitTokens, true, &atts);
handleXSLTVersion(&m_tokenSource, &onExitTokens, true, &atts);
pushState(InsideSequenceConstructor);
startStorageOfCurrent(&m_tokenSource);
insideSequenceConstructor(&m_tokenSource, onExitTokens, false);
queueOnExit(onExitTokens, &m_tokenSource);
}
void XSLTTokenizer::queueExpression(const QString &expr,
TokenSource::Queue *const to,
const bool wrapWithParantheses)
{
TokenSource::Queue *const effectiveTo = to ? to : &m_tokenSource;
if(wrapWithParantheses)
queueToken(T_LPAREN, effectiveTo);
effectiveTo->enqueue(TokenSource::Ptr(new XQueryTokenizer(expr, queryURI())));
if(wrapWithParantheses)
queueToken(T_RPAREN, effectiveTo);
}
void XSLTTokenizer::queueAVT(const QString &expr,
TokenSource::Queue *const to)
{
queueToken(T_AVT, to);
queueToken(T_LPAREN, to);
to->enqueue(TokenSource::Ptr(new XQueryTokenizer(expr, queryURI(),
XQueryTokenizer::QuotAttributeContent)));
queueToken(T_RPAREN, to);
}
void XSLTTokenizer::queueSequenceType(const QString &expr)
{
m_tokenSource.enqueue(TokenSource::Ptr(new XQueryTokenizer(expr, queryURI(),
XQueryTokenizer::ItemType)));
}
void XSLTTokenizer::commencingExpression(bool &hasWrittenExpression,
TokenSource::Queue *const to)
{
if(hasWrittenExpression)
queueToken(T_COMMA, to);
else
hasWrittenExpression = true;
}
void XSLTTokenizer::queueEmptySequence(TokenSource::Queue *const to)
{
queueToken(T_LPAREN, to);
queueToken(T_RPAREN, to);
}
void XSLTTokenizer::insideChoose(TokenSource::Queue *const to)
{
Q_ASSERT(tokenType() == QXmlStreamReader::StartElement);
bool hasHandledOtherwise = false;
bool hasEncounteredAtLeastOneWhen = false;
while(!atEnd())
{
switch(readNext())
{
case QXmlStreamReader::StartElement:
{
if(isXSLT())
{
QStack<Token> onExitTokens;
handleStandardAttributes(true);
validateElement();
switch(currentElementName())
{
case When:
{
if(hasHandledOtherwise)
{
error(QtXmlPatterns::tr("Element %1 must come last.")
.arg(formatKeyword(QLatin1String("otherwise"))),
ReportContext::XTSE0010);
}
queueToken(T_IF, to);
queueToken(T_LPAREN, to);
queueExpression(readAttribute(QLatin1String("test")), to);
queueToken(T_RPAREN, to);
queueToken(T_THEN, to);
queueToken(T_LPAREN, to);
pushState(InsideSequenceConstructor);
insideSequenceConstructor(to);
queueToken(T_RPAREN, to);
Q_ASSERT(tokenType() == QXmlStreamReader::EndElement);
queueToken(T_ELSE, to);
hasEncounteredAtLeastOneWhen = true;
queueOnExit(onExitTokens, to);
break;
}
case Otherwise:
{
if(!hasEncounteredAtLeastOneWhen)
{
error(QtXmlPatterns::tr("At least one %1-element must occur before %2.")
.arg(formatKeyword(QLatin1String("when")),
formatKeyword(QLatin1String("otherwise"))),
ReportContext::XTSE0010);
}
else if(hasHandledOtherwise)
{
error(QtXmlPatterns::tr("Only one %1-element can appear.")
.arg(formatKeyword(QLatin1String("otherwise"))),
ReportContext::XTSE0010);
}
pushState(InsideSequenceConstructor);
queueToken(T_LPAREN, to);
insideSequenceConstructor(to, to);
queueToken(T_RPAREN, to);
hasHandledOtherwise = true;
queueOnExit(onExitTokens, to);
break;
}
default:
unexpectedContent();
}
}
else
unexpectedContent();
break;
}
case QXmlStreamReader::EndElement:
{
if(isXSLT())
{
switch(currentElementName())
{
case Choose:
{
if(!hasEncounteredAtLeastOneWhen)
{
error(QtXmlPatterns::tr("At least one %1-element must occur inside %2.")
.arg(formatKeyword(QLatin1String("when")),
formatKeyword(QLatin1String("choose"))),
ReportContext::XTSE0010);
}
if(!hasHandledOtherwise)
queueEmptySequence(to);
return;
}
case Otherwise:
continue;
default:
unexpectedContent();
}
}
else
unexpectedContent();
break;
}
case QXmlStreamReader::Comment:
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(). */
}
YYLTYPE XSLTTokenizer::currentSourceLocator() const
{
YYLTYPE retval;
retval.first_line = lineNumber();
retval.first_column = columnNumber();
return retval;
}
QT_END_NAMESPACE