File: System\Xml\Xsl\Xslt\XPathPatternParser.cs
Web Access
Project: src\src\libraries\System.Private.Xml\src\System.Private.Xml.csproj (System.Private.Xml)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Generic;
using System.Diagnostics;
using System.Xml;
using System.Xml.Xsl.Qil;
using System.Xml.Xsl.XPath;
using XPathNodeType = System.Xml.XPath.XPathNodeType;
using XPathParser = System.Xml.Xsl.XPath.XPathParser<System.Xml.Xsl.Qil.QilNode>;
 
namespace System.Xml.Xsl.Xslt
{
    internal sealed class XPathPatternParser
    {
        public interface IPatternBuilder : IXPathBuilder<QilNode>
        {
            IXPathBuilder<QilNode> GetPredicateBuilder(QilNode context);
        }
 
        private XPathScanner? _scanner;
        private IPatternBuilder? _ptrnBuilder;
        private readonly XPathParser _predicateParser = new XPathParser();
 
        public QilNode Parse(XPathScanner scanner, IPatternBuilder ptrnBuilder)
        {
            Debug.Assert(_scanner == null && _ptrnBuilder == null);
            Debug.Assert(scanner != null && ptrnBuilder != null);
            QilNode? result = null;
            ptrnBuilder.StartBuild();
            try
            {
                _scanner = scanner;
                _ptrnBuilder = ptrnBuilder;
                result = this.ParsePattern();
                _scanner.CheckToken(LexKind.Eof);
            }
            finally
            {
                result = ptrnBuilder.EndBuild(result);
#if DEBUG
                _ptrnBuilder = null;
                _scanner = null;
#endif
            }
            return result!;
        }
 
        /*
        *   Pattern ::= LocationPathPattern ('|' LocationPathPattern)*
        */
        private QilNode ParsePattern()
        {
            QilNode opnd = ParseLocationPathPattern();
 
            while (_scanner!.Kind == LexKind.Union)
            {
                _scanner.NextLex();
                opnd = _ptrnBuilder!.Operator(XPathOperator.Union, opnd, ParseLocationPathPattern());
            }
            return opnd;
        }
 
        /*
        *   LocationPathPattern ::= '/' RelativePathPattern? | '//'? RelativePathPattern | IdKeyPattern (('/' | '//') RelativePathPattern)?
        */
        private QilNode ParseLocationPathPattern()
        {
            QilNode opnd;
 
            switch (_scanner!.Kind)
            {
                case LexKind.Slash:
                    _scanner.NextLex();
                    opnd = _ptrnBuilder!.Axis(XPathAxis.Root, XPathNodeType.All, null, null);
 
                    if (XPathParser.IsStep(_scanner.Kind))
                    {
                        opnd = _ptrnBuilder.JoinStep(opnd, ParseRelativePathPattern());
                    }
                    return opnd;
                case LexKind.SlashSlash:
                    _scanner.NextLex();
                    return _ptrnBuilder!.JoinStep(
                        _ptrnBuilder.Axis(XPathAxis.Root, XPathNodeType.All, null, null),
                        _ptrnBuilder.JoinStep(
                            _ptrnBuilder.Axis(XPathAxis.DescendantOrSelf, XPathNodeType.All, null, null),
                            ParseRelativePathPattern()
                        )
                    );
                case LexKind.Name:
                    if (_scanner.CanBeFunction && _scanner.Prefix.Length == 0 && (_scanner.Name == "id" || _scanner.Name == "key"))
                    {
                        opnd = ParseIdKeyPattern();
                        switch (_scanner.Kind)
                        {
                            case LexKind.Slash:
                                _scanner.NextLex();
                                opnd = _ptrnBuilder!.JoinStep(opnd, ParseRelativePathPattern());
                                break;
                            case LexKind.SlashSlash:
                                _scanner.NextLex();
                                opnd = _ptrnBuilder!.JoinStep(opnd,
                                    _ptrnBuilder.JoinStep(
                                        _ptrnBuilder.Axis(XPathAxis.DescendantOrSelf, XPathNodeType.All, null, null),
                                        ParseRelativePathPattern()
                                    )
                                );
                                break;
                        }
                        return opnd;
                    }
                    break;
            }
            opnd = ParseRelativePathPattern();
            return opnd;
        }
 
        /*
        *   IdKeyPattern ::= 'id' '(' Literal ')' | 'key' '(' Literal ',' Literal ')'
        */
        private QilNode ParseIdKeyPattern()
        {
            Debug.Assert(_scanner!.CanBeFunction);
            Debug.Assert(_scanner.Prefix.Length == 0);
            Debug.Assert(_scanner.Name == "id" || _scanner.Name == "key");
            List<QilNode> args = new List<QilNode>(2);
 
            if (_scanner.Name == "id")
            {
                _scanner.NextLex();
                _scanner.PassToken(LexKind.LParens);
                _scanner.CheckToken(LexKind.String);
                args.Add(_ptrnBuilder!.String(_scanner.StringValue));
                _scanner.NextLex();
                _scanner.PassToken(LexKind.RParens);
                return _ptrnBuilder.Function("", "id", args);
            }
            else
            {
                _scanner.NextLex();
                _scanner.PassToken(LexKind.LParens);
                _scanner.CheckToken(LexKind.String);
                args.Add(_ptrnBuilder!.String(_scanner.StringValue));
                _scanner.NextLex();
                _scanner.PassToken(LexKind.Comma);
                _scanner.CheckToken(LexKind.String);
                args.Add(_ptrnBuilder.String(_scanner.StringValue));
                _scanner.NextLex();
                _scanner.PassToken(LexKind.RParens);
                return _ptrnBuilder.Function("", "key", args);
            }
        }
 
        /*
        *   RelativePathPattern ::= StepPattern (('/' | '//') StepPattern)*
        */
        //Max depth to avoid StackOverflow
        private const int MaxParseRelativePathDepth = 1024;
        private int _parseRelativePath;
        private QilNode ParseRelativePathPattern()
        {
            if (++_parseRelativePath > MaxParseRelativePathDepth)
            {
                if (LocalAppContextSwitches.LimitXPathComplexity)
                {
                    throw _scanner!.CreateException(SR.Xslt_InputTooComplex);
                }
            }
            QilNode opnd = ParseStepPattern();
            if (_scanner!.Kind == LexKind.Slash)
            {
                _scanner.NextLex();
                opnd = _ptrnBuilder!.JoinStep(opnd, ParseRelativePathPattern());
            }
            else if (_scanner.Kind == LexKind.SlashSlash)
            {
                _scanner.NextLex();
                opnd = _ptrnBuilder!.JoinStep(opnd,
                    _ptrnBuilder.JoinStep(
                        _ptrnBuilder.Axis(XPathAxis.DescendantOrSelf, XPathNodeType.All, null, null),
                        ParseRelativePathPattern()
                    )
                );
            }
            --_parseRelativePath;
            return opnd;
        }
 
        /*
        *   StepPattern ::= ChildOrAttributeAxisSpecifier NodeTest Predicate*
        *   ChildOrAttributeAxisSpecifier ::= @ ? | ('child' | 'attribute') '::'
        */
        private QilNode ParseStepPattern()
        {
            QilNode opnd;
            XPathAxis axis;
 
            switch (_scanner!.Kind)
            {
                case LexKind.Dot:
                case LexKind.DotDot:
                    throw _scanner.CreateException(SR.XPath_InvalidAxisInPattern);
                case LexKind.At:
                    axis = XPathAxis.Attribute;
                    _scanner.NextLex();
                    break;
                case LexKind.Axis:
                    axis = _scanner.Axis;
                    if (axis != XPathAxis.Child && axis != XPathAxis.Attribute)
                    {
                        throw _scanner.CreateException(SR.XPath_InvalidAxisInPattern);
                    }
                    _scanner.NextLex();  // Skip '::'
                    _scanner.NextLex();
                    break;
                case LexKind.Name:
                case LexKind.Star:
                    // NodeTest must start with Name or '*'
                    axis = XPathAxis.Child;
                    break;
                default:
                    throw _scanner.CreateException(SR.XPath_UnexpectedToken, _scanner.RawValue);
            }
 
            XPathNodeType nodeType;
            string? nodePrefix, nodeName;
            XPathParser.InternalParseNodeTest(_scanner, axis, out nodeType, out nodePrefix, out nodeName);
            opnd = _ptrnBuilder!.Axis(axis, nodeType, nodePrefix, nodeName);
 
            XPathPatternBuilder? xpathPatternBuilder = _ptrnBuilder as XPathPatternBuilder;
            if (xpathPatternBuilder != null)
            {
                //for XPathPatternBuilder, get all predicates and then build them
                List<QilNode> predicates = new List<QilNode>();
                while (_scanner.Kind == LexKind.LBracket)
                {
                    predicates.Add(ParsePredicate(opnd));
                }
                if (predicates.Count > 0)
                    opnd = xpathPatternBuilder.BuildPredicates(opnd, predicates);
            }
            else
            {
                while (_scanner.Kind == LexKind.LBracket)
                {
                    opnd = _ptrnBuilder.Predicate(opnd, ParsePredicate(opnd), /*reverseStep:*/false);
                }
            }
            return opnd;
        }
 
        /*
        *   Predicate ::= '[' Expr ']'
        */
        private QilNode ParsePredicate(QilNode context)
        {
            Debug.Assert(_scanner!.Kind == LexKind.LBracket);
            _scanner.NextLex();
            QilNode result = _predicateParser.Parse(_scanner, _ptrnBuilder!.GetPredicateBuilder(context), LexKind.RBracket);
            Debug.Assert(_scanner.Kind == LexKind.RBracket);
            _scanner.NextLex();
            return result;
        }
    }
}