e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
1039 lines
32 KiB
C#
1039 lines
32 KiB
C#
//------------------------------------------------------------
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//------------------------------------------------------------
|
|
namespace System.ServiceModel.Dispatcher
|
|
{
|
|
using System.Runtime;
|
|
using System.Xml;
|
|
using System.Xml.XPath;
|
|
using System.Xml.Xsl;
|
|
|
|
class XPathParser
|
|
{
|
|
IFunctionLibrary[] functionLibraries;
|
|
XPathLexer lexer;
|
|
XmlNamespaceManager namespaces;
|
|
XPathToken readToken;
|
|
XsltContext context;
|
|
|
|
internal XPathParser(string xpath, XmlNamespaceManager namespaces, IFunctionLibrary[] functionLibraries)
|
|
{
|
|
Fx.Assert(null != xpath, "");
|
|
this.functionLibraries = functionLibraries;
|
|
this.namespaces = namespaces;
|
|
this.lexer = new XPathLexer(xpath);
|
|
this.context = namespaces as XsltContext;
|
|
}
|
|
|
|
XPathExpr EnsureReturnsNodeSet(XPathExpr expr)
|
|
{
|
|
if (expr.ReturnType != ValueDataType.Sequence)
|
|
{
|
|
this.ThrowError(QueryCompileError.InvalidFunction);
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
XPathToken NextToken()
|
|
{
|
|
if (null != this.readToken)
|
|
{
|
|
XPathToken nextToken = this.readToken;
|
|
|
|
this.readToken = null;
|
|
return nextToken;
|
|
}
|
|
|
|
while (this.lexer.MoveNext())
|
|
{
|
|
if (XPathTokenID.Whitespace != this.lexer.Token.TokenID)
|
|
{
|
|
return this.lexer.Token;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
XPathToken NextToken(XPathTokenID id)
|
|
{
|
|
XPathToken token = this.NextToken();
|
|
|
|
if (null != token)
|
|
{
|
|
if (id == token.TokenID)
|
|
{
|
|
return token;
|
|
}
|
|
|
|
this.readToken = token;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
XPathToken NextToken(XPathTokenID id, QueryCompileError error)
|
|
{
|
|
XPathToken token = this.NextToken(id);
|
|
|
|
if (null == token)
|
|
{
|
|
this.ThrowError(error);
|
|
}
|
|
|
|
return token;
|
|
}
|
|
|
|
XPathToken NextTokenClass(XPathTokenID tokenClass)
|
|
{
|
|
XPathToken token = this.NextToken();
|
|
|
|
if (null != token)
|
|
{
|
|
if (0 != (token.TokenID & tokenClass))
|
|
{
|
|
return token;
|
|
}
|
|
|
|
this.readToken = token;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
NodeQName QualifyName(string prefix, string name)
|
|
{
|
|
if (null != this.namespaces && null != prefix && prefix.Length > 0)
|
|
{
|
|
prefix = this.namespaces.NameTable.Add(prefix);
|
|
|
|
string ns = this.namespaces.LookupNamespace(prefix);
|
|
|
|
if (null == ns)
|
|
{
|
|
this.ThrowError(QueryCompileError.NoNamespaceForPrefix);
|
|
}
|
|
|
|
return new NodeQName(name, ns);
|
|
}
|
|
|
|
return new NodeQName(name);
|
|
}
|
|
|
|
internal XPathExpr Parse()
|
|
{
|
|
XPathExpr expr = this.ParseExpression();
|
|
|
|
if (null == expr)
|
|
{
|
|
this.ThrowError(QueryCompileError.InvalidExpression);
|
|
}
|
|
|
|
// If we stopped before the entire xpath was lexed, we hit something we could not tokenize
|
|
XPathToken lastToken = this.NextToken();
|
|
|
|
if (null != lastToken)
|
|
{
|
|
this.ThrowError(QueryCompileError.UnexpectedToken);
|
|
}
|
|
|
|
return expr;
|
|
}
|
|
|
|
XPathExprList ParseAbsolutePath()
|
|
{
|
|
XPathExprList path = null;
|
|
XPathToken token = this.NextToken();
|
|
|
|
if (null != token)
|
|
{
|
|
switch (token.TokenID)
|
|
{
|
|
default:
|
|
this.PushToken(token);
|
|
break;
|
|
|
|
case XPathTokenID.Slash:
|
|
path = new XPathExprList();
|
|
path.Add(new XPathStepExpr(new NodeSelectCriteria(QueryAxisType.Child, NodeQName.Empty, QueryNodeType.Root)));
|
|
break;
|
|
|
|
case XPathTokenID.DblSlash:
|
|
// '//' is special. If found at the start of an absolute path, it implies that the descendant-or-self axis
|
|
// is applied to the ROOT
|
|
path = new XPathExprList();
|
|
path.Add(new XPathStepExpr(new NodeSelectCriteria(QueryAxisType.Child, NodeQName.Empty, QueryNodeType.Root)));
|
|
path.Add(new XPathStepExpr(new NodeSelectCriteria(QueryAxisType.DescendantOrSelf, NodeQName.Empty, QueryNodeType.All)));
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (null != path)
|
|
{
|
|
this.ParseRelativePath(path);
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
XPathExpr ParseAdditiveExpression()
|
|
{
|
|
XPathExpr leftExpr = this.ParseMultiplicativeExpression();
|
|
|
|
if (null != leftExpr)
|
|
{
|
|
MathOperator op;
|
|
|
|
do
|
|
{
|
|
op = MathOperator.None;
|
|
|
|
XPathToken token = this.NextToken();
|
|
|
|
if (null != token)
|
|
{
|
|
switch (token.TokenID)
|
|
{
|
|
default:
|
|
this.PushToken(token);
|
|
break;
|
|
|
|
case XPathTokenID.Plus:
|
|
op = MathOperator.Plus;
|
|
break;
|
|
|
|
case XPathTokenID.Minus:
|
|
op = MathOperator.Minus;
|
|
break;
|
|
}
|
|
if (MathOperator.None != op)
|
|
{
|
|
XPathExpr rightExpr = this.ParseMultiplicativeExpression();
|
|
|
|
if (null == rightExpr)
|
|
{
|
|
this.ThrowError(QueryCompileError.InvalidExpression);
|
|
}
|
|
|
|
leftExpr = new XPathMathExpr(op, leftExpr, rightExpr);
|
|
}
|
|
}
|
|
} while (MathOperator.None != op);
|
|
}
|
|
|
|
return leftExpr;
|
|
}
|
|
|
|
XPathExpr ParseAndExpression()
|
|
{
|
|
XPathExpr eqExpr = this.ParseEqualityExpression();
|
|
|
|
if (null != eqExpr && null != this.NextToken(XPathTokenID.And))
|
|
{
|
|
XPathExpr andExpr = new XPathExpr(XPathExprType.And, ValueDataType.Boolean);
|
|
|
|
andExpr.AddBooleanExpression(XPathExprType.And, eqExpr);
|
|
do
|
|
{
|
|
eqExpr = this.ParseEqualityExpression();
|
|
if (eqExpr == null)
|
|
this.ThrowError(QueryCompileError.InvalidExpression);
|
|
andExpr.AddBooleanExpression(XPathExprType.And, eqExpr);
|
|
} while (null != this.NextToken(XPathTokenID.And));
|
|
|
|
return andExpr;
|
|
}
|
|
|
|
return eqExpr;
|
|
}
|
|
|
|
QueryAxisType ParseAxisSpecifier()
|
|
{
|
|
if (null != this.NextToken(XPathTokenID.AtSign))
|
|
{
|
|
return QueryAxisType.Attribute;
|
|
}
|
|
|
|
QueryAxisType axisType = QueryAxisType.None;
|
|
XPathToken token;
|
|
|
|
if (null != (token = this.NextTokenClass(XPathTokenID.Axis)))
|
|
{
|
|
switch (token.TokenID)
|
|
{
|
|
default:
|
|
this.ThrowError(QueryCompileError.UnsupportedAxis);
|
|
break;
|
|
|
|
case XPathTokenID.Attribute:
|
|
axisType = QueryAxisType.Attribute;
|
|
break;
|
|
|
|
case XPathTokenID.Child:
|
|
axisType = QueryAxisType.Child;
|
|
break;
|
|
|
|
case XPathTokenID.Descendant:
|
|
axisType = QueryAxisType.Descendant;
|
|
break;
|
|
|
|
case XPathTokenID.DescendantOrSelf:
|
|
axisType = QueryAxisType.DescendantOrSelf;
|
|
break;
|
|
|
|
case XPathTokenID.Self:
|
|
axisType = QueryAxisType.Self;
|
|
break;
|
|
}
|
|
|
|
// axis specifiers must be followed by a '::'
|
|
this.NextToken(XPathTokenID.DblColon, QueryCompileError.InvalidAxisSpecifier);
|
|
}
|
|
|
|
return axisType;
|
|
}
|
|
|
|
XPathExpr ParseEqualityExpression()
|
|
{
|
|
XPathExpr leftExpr = this.ParseRelationalExpression();
|
|
|
|
if (null != leftExpr)
|
|
{
|
|
RelationOperator op;
|
|
|
|
do
|
|
{
|
|
op = RelationOperator.None;
|
|
|
|
XPathToken token = this.NextToken();
|
|
|
|
if (null != token)
|
|
{
|
|
switch (token.TokenID)
|
|
{
|
|
default:
|
|
this.PushToken(token);
|
|
break;
|
|
|
|
case XPathTokenID.Eq:
|
|
op = RelationOperator.Eq;
|
|
break;
|
|
|
|
case XPathTokenID.Neq:
|
|
op = RelationOperator.Ne;
|
|
break;
|
|
}
|
|
if (RelationOperator.None != op)
|
|
{
|
|
XPathExpr rightExpr = this.ParseRelationalExpression();
|
|
|
|
if (null == rightExpr)
|
|
{
|
|
this.ThrowError(QueryCompileError.InvalidExpression);
|
|
}
|
|
|
|
leftExpr = new XPathRelationExpr(op, leftExpr, rightExpr);
|
|
}
|
|
}
|
|
} while (RelationOperator.None != op);
|
|
}
|
|
|
|
return leftExpr;
|
|
}
|
|
|
|
XPathExpr ParseExpression()
|
|
{
|
|
return this.ParseOrExpression();
|
|
}
|
|
|
|
XPathExpr ParseFilterExpression()
|
|
{
|
|
XPathExpr primaryExpr = this.ParsePrimaryExpression();
|
|
|
|
if (null == primaryExpr)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
XPathExpr filterExpr = new XPathExpr(XPathExprType.Filter, primaryExpr.ReturnType);
|
|
filterExpr.Add(primaryExpr);
|
|
|
|
XPathExpr predicate = this.ParsePredicateExpression();
|
|
|
|
if (null != predicate)
|
|
{
|
|
EnsureReturnsNodeSet(primaryExpr);
|
|
|
|
//XPathExpr filterExpr = new XPathExpr(XPathExprType.Filter, ValueDataType.Sequence);
|
|
|
|
//filterExpr.Add(primaryExpr);
|
|
filterExpr.Add(predicate);
|
|
|
|
// Read in any additional predicates
|
|
while (null != (predicate = this.ParsePredicateExpression()))
|
|
{
|
|
filterExpr.Add(predicate);
|
|
}
|
|
|
|
return filterExpr;
|
|
}
|
|
|
|
return primaryExpr;
|
|
}
|
|
|
|
XPathExpr ParseFunctionExpression()
|
|
{
|
|
XPathToken functionToken = this.NextToken(XPathTokenID.Function);
|
|
|
|
if (null == functionToken)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
NodeQName functionName = this.QualifyName(functionToken.Prefix, functionToken.Name);
|
|
this.NextToken(XPathTokenID.LParen, QueryCompileError.InvalidFunction);
|
|
|
|
XPathExprList args = new XPathExprList();
|
|
|
|
// Read in arguments
|
|
XPathExpr arg;
|
|
|
|
while (null != (arg = this.ParseExpression()))
|
|
{
|
|
args.Add(arg);
|
|
if (null == this.NextToken(XPathTokenID.Comma))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Bind to the function
|
|
// Try each library until we can bind the function
|
|
XPathExpr functionImpl = null;
|
|
if (null != this.functionLibraries)
|
|
{
|
|
QueryFunction fun = null;
|
|
for (int i = 0; i < this.functionLibraries.Length; ++i)
|
|
{
|
|
if (null != (fun = this.functionLibraries[i].Bind(functionName.Name, functionName.Namespace, args)))
|
|
{
|
|
functionImpl = new XPathFunctionExpr(fun, args);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Try to bind using the XsltContext
|
|
if (null == functionImpl && this.context != null)
|
|
{
|
|
XPathResultType[] argTypes = new XPathResultType[args.Count];
|
|
for (int i = 0; i < args.Count; ++i)
|
|
{
|
|
argTypes[i] = XPathXsltFunctionExpr.ConvertTypeToXslt(args[i].ReturnType);
|
|
}
|
|
string prefix = this.context.LookupPrefix(functionName.Namespace);
|
|
IXsltContextFunction xsltFun = this.context.ResolveFunction(prefix, functionName.Name, argTypes);
|
|
if (xsltFun != null)
|
|
{
|
|
functionImpl = new XPathXsltFunctionExpr(this.context, xsltFun, args);
|
|
}
|
|
}
|
|
|
|
if (null == functionImpl)
|
|
{
|
|
this.ThrowError(QueryCompileError.UnsupportedFunction);
|
|
}
|
|
|
|
this.NextToken(XPathTokenID.RParen, QueryCompileError.InvalidFunction);
|
|
return functionImpl;
|
|
}
|
|
|
|
internal XPathExpr ParseLocationPath()
|
|
{
|
|
XPathExprList path = this.ParseAbsolutePath();
|
|
|
|
if (null == path)
|
|
{
|
|
path = this.ParseRelativePath();
|
|
}
|
|
|
|
if (null != path)
|
|
{
|
|
return new XPathExpr(XPathExprType.LocationPath, ValueDataType.Sequence, path);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
XPathExpr ParseLiteralExpression()
|
|
{
|
|
XPathToken literal;
|
|
|
|
if (null != (literal = this.NextToken(XPathTokenID.Literal)))
|
|
{
|
|
return new XPathStringExpr(literal.Name);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
XPathExpr ParseMultiplicativeExpression()
|
|
{
|
|
XPathExpr leftExpr = this.ParseUnaryExpression();
|
|
|
|
if (null != leftExpr)
|
|
{
|
|
MathOperator op;
|
|
|
|
do
|
|
{
|
|
op = MathOperator.None;
|
|
|
|
XPathToken token = this.NextToken();
|
|
|
|
if (null != token)
|
|
{
|
|
switch (token.TokenID)
|
|
{
|
|
default:
|
|
this.PushToken(token);
|
|
break;
|
|
|
|
case XPathTokenID.Multiply:
|
|
op = MathOperator.Multiply;
|
|
break;
|
|
|
|
case XPathTokenID.Div:
|
|
op = MathOperator.Div;
|
|
break;
|
|
|
|
case XPathTokenID.Mod:
|
|
op = MathOperator.Mod;
|
|
break;
|
|
}
|
|
if (MathOperator.None != op)
|
|
{
|
|
XPathExpr rightExpr = this.ParseUnaryExpression();
|
|
|
|
if (null == rightExpr)
|
|
{
|
|
this.ThrowError(QueryCompileError.InvalidExpression);
|
|
}
|
|
|
|
leftExpr = new XPathMathExpr(op, leftExpr, rightExpr);
|
|
}
|
|
}
|
|
} while (MathOperator.None != op);
|
|
}
|
|
|
|
return leftExpr;
|
|
}
|
|
|
|
NodeSelectCriteria ParseNodeTest(QueryAxisType axisType)
|
|
{
|
|
Fx.Assert(QueryAxisType.None != axisType, "");
|
|
|
|
QueryAxis axis = QueryDataModel.GetAxis(axisType);
|
|
XPathToken token;
|
|
NodeQName qname = NodeQName.Empty;
|
|
|
|
if (null != (token = this.NextTokenClass(XPathTokenID.NameTest)))
|
|
{
|
|
switch (token.TokenID)
|
|
{
|
|
default:
|
|
this.ThrowError(QueryCompileError.UnexpectedToken);
|
|
break;
|
|
|
|
case XPathTokenID.Wildcard:
|
|
qname = new NodeQName(QueryDataModel.Wildcard, QueryDataModel.Wildcard);
|
|
break;
|
|
|
|
case XPathTokenID.NameTest:
|
|
qname = this.QualifyName(token.Prefix, token.Name);
|
|
break;
|
|
|
|
case XPathTokenID.NameWildcard:
|
|
qname = this.QualifyName(token.Prefix, QueryDataModel.Wildcard);
|
|
break;
|
|
}
|
|
}
|
|
|
|
QueryNodeType nodeType = QueryNodeType.Any;
|
|
|
|
if (qname.IsEmpty)
|
|
{
|
|
// Check for nodeTests
|
|
if (null == (token = this.NextTokenClass(XPathTokenID.NodeType)))
|
|
{
|
|
// Not a NodeTest either.
|
|
return null;
|
|
}
|
|
|
|
switch (token.TokenID)
|
|
{
|
|
default:
|
|
this.ThrowError(QueryCompileError.UnsupportedNodeTest);
|
|
break;
|
|
|
|
case XPathTokenID.Comment:
|
|
nodeType = QueryNodeType.Comment;
|
|
break;
|
|
|
|
case XPathTokenID.Text:
|
|
nodeType = QueryNodeType.Text;
|
|
break;
|
|
|
|
case XPathTokenID.Processing:
|
|
nodeType = QueryNodeType.Processing;
|
|
break;
|
|
|
|
case XPathTokenID.Node:
|
|
nodeType = QueryNodeType.All;
|
|
break;
|
|
}
|
|
|
|
// Make sure the nodes being selected CAN actually be selected from this axis
|
|
if (0 == (axis.ValidNodeTypes & nodeType))
|
|
{
|
|
this.ThrowError(QueryCompileError.InvalidNodeType);
|
|
}
|
|
|
|
// Eat ()
|
|
this.NextToken(XPathTokenID.LParen, QueryCompileError.InvalidNodeTest);
|
|
this.NextToken(XPathTokenID.RParen, QueryCompileError.InvalidNodeTest);
|
|
}
|
|
else
|
|
{
|
|
nodeType = axis.PrincipalNodeType;
|
|
}
|
|
|
|
return new NodeSelectCriteria(axisType, qname, nodeType);
|
|
}
|
|
|
|
XPathExpr ParseNumberExpression()
|
|
{
|
|
XPathToken number;
|
|
|
|
if (null != (number = this.NextTokenClass(XPathTokenID.Number)))
|
|
{
|
|
return new XPathNumberExpr(number.Number);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
XPathExpr ParseOrExpression()
|
|
{
|
|
XPathExpr andExpr = this.ParseAndExpression();
|
|
|
|
if (null != andExpr && null != this.NextToken(XPathTokenID.Or))
|
|
{
|
|
XPathExpr orExpr = new XPathExpr(XPathExprType.Or, ValueDataType.Boolean);
|
|
|
|
orExpr.AddBooleanExpression(XPathExprType.Or, andExpr);
|
|
do
|
|
{
|
|
andExpr = this.ParseAndExpression();
|
|
if (andExpr == null)
|
|
this.ThrowError(QueryCompileError.InvalidExpression);
|
|
orExpr.AddBooleanExpression(XPathExprType.Or, andExpr);
|
|
} while (null != this.NextToken(XPathTokenID.Or));
|
|
|
|
return orExpr;
|
|
}
|
|
|
|
return andExpr;
|
|
}
|
|
|
|
XPathExpr ParsePathExpression()
|
|
{
|
|
XPathExpr pathExpr = this.ParseLocationPath();
|
|
|
|
if (null != pathExpr)
|
|
{
|
|
return pathExpr;
|
|
}
|
|
|
|
// Perhaps we have a filter expression
|
|
XPathExpr filterExpr = this.ParseFilterExpression();
|
|
if (null != filterExpr)
|
|
{
|
|
if (null != this.NextToken(XPathTokenID.Slash))
|
|
{
|
|
EnsureReturnsNodeSet(filterExpr);
|
|
|
|
// Is this a complex filter expression.. i.e. followed by further selections..
|
|
XPathExprList relPath = this.ParseRelativePath();
|
|
if (null == relPath)
|
|
{
|
|
this.ThrowError(QueryCompileError.InvalidLocationPath);
|
|
}
|
|
|
|
XPathExpr relPathExpr = new XPathExpr(XPathExprType.RelativePath, ValueDataType.Sequence, relPath);
|
|
|
|
pathExpr = new XPathExpr(XPathExprType.Path, ValueDataType.Sequence);
|
|
pathExpr.Add(filterExpr);
|
|
pathExpr.Add(relPathExpr);
|
|
}
|
|
else if (null != this.NextToken(XPathTokenID.DblSlash))
|
|
{
|
|
EnsureReturnsNodeSet(filterExpr);
|
|
|
|
XPathExprList relPath = this.ParseRelativePath();
|
|
if (null == relPath)
|
|
{
|
|
this.ThrowError(QueryCompileError.InvalidLocationPath);
|
|
}
|
|
|
|
XPathExpr relPathExpr = new XPathExpr(XPathExprType.RelativePath, ValueDataType.Sequence, relPath);
|
|
pathExpr = new XPathExpr(XPathExprType.Path, ValueDataType.Sequence);
|
|
pathExpr.Add(filterExpr);
|
|
pathExpr.Add(new XPathStepExpr(new NodeSelectCriteria(QueryAxisType.DescendantOrSelf, NodeQName.Empty, QueryNodeType.All)));
|
|
pathExpr.Add(relPathExpr);
|
|
}
|
|
else
|
|
{
|
|
pathExpr = filterExpr;
|
|
}
|
|
}
|
|
|
|
return pathExpr;
|
|
}
|
|
|
|
XPathExprList ParsePredicates()
|
|
{
|
|
XPathExprList predicates = null;
|
|
XPathExpr predicate = this.ParsePredicateExpression();
|
|
|
|
if (null != predicate)
|
|
{
|
|
predicates = new XPathExprList();
|
|
predicates.Add(predicate);
|
|
while (null != (predicate = this.ParsePredicateExpression()))
|
|
{
|
|
predicates.Add(predicate);
|
|
}
|
|
}
|
|
|
|
return predicates;
|
|
}
|
|
|
|
XPathExpr ParsePredicateExpression()
|
|
{
|
|
XPathExpr predicate = null;
|
|
|
|
if (null != this.NextToken(XPathTokenID.LBracket))
|
|
{
|
|
predicate = this.ParseExpression();
|
|
if (null == predicate)
|
|
{
|
|
this.ThrowError(QueryCompileError.InvalidPredicate);
|
|
}
|
|
|
|
this.NextToken(XPathTokenID.RBracket, QueryCompileError.InvalidPredicate);
|
|
}
|
|
|
|
return predicate;
|
|
}
|
|
|
|
XPathExpr ParsePrimaryExpression()
|
|
{
|
|
XPathExpr expr = this.ParseVariableExpression();
|
|
|
|
if (null == expr)
|
|
{
|
|
if (null != this.NextToken(XPathTokenID.LParen))
|
|
{
|
|
expr = this.ParseExpression();
|
|
if (null == expr || null == this.NextToken(XPathTokenID.RParen))
|
|
{
|
|
this.ThrowError(QueryCompileError.InvalidExpression);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (null == expr)
|
|
{
|
|
expr = this.ParseLiteralExpression();
|
|
}
|
|
|
|
if (null == expr)
|
|
{
|
|
expr = this.ParseNumberExpression();
|
|
}
|
|
|
|
if (null == expr)
|
|
{
|
|
expr = this.ParseFunctionExpression();
|
|
}
|
|
|
|
return expr;
|
|
}
|
|
|
|
XPathExprList ParseRelativePath()
|
|
{
|
|
XPathExprList path = new XPathExprList();
|
|
|
|
if (this.ParseRelativePath(path))
|
|
{
|
|
return path;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
bool ParseRelativePath(XPathExprList path)
|
|
{
|
|
Fx.Assert(null != path, "");
|
|
|
|
XPathStepExpr step = this.ParseStep();
|
|
|
|
if (null == step)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
path.Add(step);
|
|
while (true)
|
|
{
|
|
if (null != this.NextToken(XPathTokenID.Slash))
|
|
{
|
|
step = this.ParseStep();
|
|
}
|
|
else if (null != this.NextToken(XPathTokenID.DblSlash))
|
|
{
|
|
step = new XPathStepExpr(new NodeSelectCriteria(QueryAxisType.DescendantOrSelf, NodeQName.Empty, QueryNodeType.All));
|
|
path.Add(step);
|
|
step = this.ParseStep();
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (null == step)
|
|
{
|
|
this.ThrowError(QueryCompileError.InvalidLocationPath);
|
|
}
|
|
|
|
path.Add(step);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
XPathExpr ParseRelationalExpression()
|
|
{
|
|
XPathExpr leftExpr = this.ParseAdditiveExpression();
|
|
|
|
if (null != leftExpr)
|
|
{
|
|
RelationOperator op;
|
|
|
|
do
|
|
{
|
|
op = RelationOperator.None;
|
|
|
|
XPathToken token = this.NextToken();
|
|
|
|
if (null != token)
|
|
{
|
|
switch (token.TokenID)
|
|
{
|
|
default:
|
|
this.PushToken(token);
|
|
break;
|
|
|
|
case XPathTokenID.Lt:
|
|
op = RelationOperator.Lt;
|
|
break;
|
|
|
|
case XPathTokenID.Lte:
|
|
op = RelationOperator.Le;
|
|
break;
|
|
|
|
case XPathTokenID.Gt:
|
|
op = RelationOperator.Gt;
|
|
break;
|
|
|
|
case XPathTokenID.Gte:
|
|
op = RelationOperator.Ge;
|
|
break;
|
|
}
|
|
if (RelationOperator.None != op)
|
|
{
|
|
XPathExpr rightExpr = this.ParseAdditiveExpression();
|
|
|
|
if (null == rightExpr)
|
|
{
|
|
this.ThrowError(QueryCompileError.InvalidExpression);
|
|
}
|
|
|
|
leftExpr = new XPathRelationExpr(op, leftExpr, rightExpr);
|
|
}
|
|
}
|
|
} while (RelationOperator.None != op);
|
|
}
|
|
|
|
return leftExpr;
|
|
}
|
|
|
|
XPathStepExpr ParseStep()
|
|
{
|
|
QueryAxisType axis = this.ParseAxisSpecifier();
|
|
NodeSelectCriteria selectDesc = null;
|
|
bool abbreviatedStep = false;
|
|
|
|
if (QueryAxisType.None != axis)
|
|
{
|
|
// Valid axis specifier - must be followed by a nodeTest
|
|
selectDesc = this.ParseNodeTest(axis);
|
|
}
|
|
else
|
|
{
|
|
// No axis specifier. This could be an abbreviated step - shortcuts for 'self' or 'parent'
|
|
if (null != this.NextToken(XPathTokenID.Period))
|
|
{
|
|
selectDesc = new NodeSelectCriteria(QueryAxisType.Self, NodeQName.Empty, QueryNodeType.All);
|
|
abbreviatedStep = true;
|
|
}
|
|
else if (null != this.NextToken(XPathTokenID.DblPeriod))
|
|
{
|
|
// A shortcut for parent
|
|
selectDesc = new NodeSelectCriteria(QueryAxisType.Parent, NodeQName.Empty, QueryNodeType.Ancestor);
|
|
abbreviatedStep = true;
|
|
}
|
|
else
|
|
{
|
|
// No axis specifier provided. Assume child
|
|
if (null == (selectDesc = this.ParseNodeTest(QueryAxisType.Child)))
|
|
{
|
|
// No nodeTest either.. clearly not a Step
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (null == selectDesc)
|
|
{
|
|
this.ThrowError(QueryCompileError.InvalidLocationStep);
|
|
}
|
|
|
|
XPathExprList predicates = null;
|
|
|
|
if (!abbreviatedStep)
|
|
{
|
|
// Abbreviated steps are not permitted predicates
|
|
predicates = this.ParsePredicates();
|
|
}
|
|
|
|
return new XPathStepExpr(selectDesc, predicates);
|
|
}
|
|
|
|
XPathExpr ParseUnaryExpression()
|
|
{
|
|
bool negate = false, anyNegate = false;
|
|
for (; null != this.NextToken(XPathTokenID.Minus); anyNegate = true, negate = !negate);
|
|
XPathExpr expr = ParseUnionExpression();
|
|
if (expr != null)
|
|
{
|
|
// If there were any negations at all, the type gets converted to a number
|
|
if (anyNegate && expr.ReturnType != ValueDataType.Double)
|
|
{
|
|
expr.ReturnType = ValueDataType.Double;
|
|
expr.TypecastRequired = true;
|
|
}
|
|
expr.Negate = negate;
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
internal XPathExpr ParseUnionExpression()
|
|
{
|
|
XPathExpr leftExpr = this.ParsePathExpression();
|
|
|
|
if (null != leftExpr)
|
|
{
|
|
if (null != this.NextToken(XPathTokenID.Pipe))
|
|
{
|
|
EnsureReturnsNodeSet(leftExpr);
|
|
|
|
XPathExpr rightExpr = this.ParseUnionExpression();
|
|
if (rightExpr == null)
|
|
{
|
|
ThrowError(QueryCompileError.CouldNotParseExpression);
|
|
}
|
|
EnsureReturnsNodeSet(rightExpr);
|
|
|
|
return new XPathConjunctExpr(XPathExprType.Union, ValueDataType.Sequence, leftExpr, rightExpr);
|
|
}
|
|
}
|
|
|
|
return leftExpr;
|
|
}
|
|
|
|
internal XPathExpr ParseVariableExpression()
|
|
{
|
|
XPathExpr expr = null;
|
|
if (this.context != null)
|
|
{
|
|
XPathToken varTok = this.NextToken(XPathTokenID.Variable);
|
|
if (varTok != null)
|
|
{
|
|
NodeQName varName = this.QualifyName(varTok.Prefix, varTok.Name);
|
|
string prefix = this.context.LookupPrefix(varName.Namespace);
|
|
|
|
IXsltContextVariable var = this.context.ResolveVariable(prefix, varName.Name);
|
|
if (var != null)
|
|
{
|
|
expr = new XPathXsltVariableExpr(this.context, var);
|
|
}
|
|
}
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
void PushToken(XPathToken token)
|
|
{
|
|
Fx.Assert(null == this.readToken, "");
|
|
this.readToken = token;
|
|
}
|
|
|
|
internal void ThrowError(QueryCompileError error)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new QueryCompileException(error, this.lexer.ConsumedSubstring()));
|
|
}
|
|
|
|
internal struct QName
|
|
{
|
|
string prefix;
|
|
|
|
string name;
|
|
|
|
internal QName(string prefix, string name)
|
|
{
|
|
Fx.Assert(null != prefix, "");
|
|
this.prefix = prefix;
|
|
this.name = name;
|
|
}
|
|
|
|
internal string Prefix
|
|
{
|
|
get
|
|
{
|
|
return this.prefix;
|
|
}
|
|
}
|
|
|
|
internal string Name
|
|
{
|
|
get
|
|
{
|
|
return this.name;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|