//------------------------------------------------------------ // 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; } } } } }