File: System\Xml\Xsl\Xslt\XsltInput.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.
 
//#define XSLT2
 
using System.Collections.Generic;
using System.Diagnostics;
using StringConcat = System.Xml.Xsl.Runtime.StringConcat;
 
namespace System.Xml.Xsl.Xslt
{
    //         a) Forward only, one pass.
    //         b) You should call MoveToFirstChildren on nonempty element node. (or may be skip)
 
    internal sealed class XsltInput : IErrorHelper
    {
#if DEBUG
        private const int InitRecordsSize = 1;
#else
        private const int InitRecordsSize = 1 + 21;
#endif
 
        private readonly XmlReader _reader;
        private readonly IXmlLineInfo? _readerLineInfo;
        private readonly bool _topLevelReader;
        private readonly CompilerScopeManager<VarPar> _scopeManager;
        private readonly KeywordsTable _atoms;
        private readonly Compiler _compiler;
        private readonly bool _reatomize;
 
        // Cached properties. MoveTo* functions set them.
        private XmlNodeType _nodeType;
        private Record[] _records = new Record[InitRecordsSize];
        private int _currentRecord;
        private bool _isEmptyElement;
        private int _lastTextNode;
        private int _numAttributes;
        private ContextInfo? _ctxInfo;
        private bool _attributesRead;
 
        public XsltInput(XmlReader reader, Compiler compiler, KeywordsTable atoms)
        {
            Debug.Assert(reader != null);
            Debug.Assert(atoms != null);
            EnsureExpandEntities(reader);
            IXmlLineInfo? xmlLineInfo = reader as IXmlLineInfo;
 
            _atoms = atoms;
            _reader = reader;
            _reatomize = reader.NameTable != atoms.NameTable;
            _readerLineInfo = (xmlLineInfo != null && xmlLineInfo.HasLineInfo()) ? xmlLineInfo : null;
            _topLevelReader = reader.ReadState == ReadState.Initial;
            _scopeManager = new CompilerScopeManager<VarPar>(atoms);
            _compiler = compiler;
            _nodeType = XmlNodeType.Document;
        }
 
        // Cached properties
        public XmlNodeType NodeType { get { return _nodeType == XmlNodeType.Element && 0 < _currentRecord ? XmlNodeType.Attribute : _nodeType; } }
        public string LocalName { get { return _records[_currentRecord].localName; } }
        public string NamespaceUri { get { return _records[_currentRecord].nsUri; } }
        public string Prefix { get { return _records[_currentRecord].prefix; } }
        public string Value { get { return _records[_currentRecord].value; } }
        public string? BaseUri { get { return _records[_currentRecord].baseUri; } }
        public string QualifiedName { get { return _records[_currentRecord].QualifiedName; } }
        public bool IsEmptyElement { get { return _isEmptyElement; } }
 
        public string? Uri { get { return _records[_currentRecord].baseUri; } }
        public Location Start { get { return _records[_currentRecord].start; } }
        public Location End { get { return _records[_currentRecord].end; } }
 
        private static void EnsureExpandEntities(XmlReader reader)
        {
            XmlTextReader? tr = reader as XmlTextReader;
            if (tr != null && tr.EntityHandling != EntityHandling.ExpandEntities)
            {
                Debug.Assert(tr.Settings == null, "XmlReader created with XmlReader.Create should always expand entities.");
                tr.EntityHandling = EntityHandling.ExpandEntities;
            }
        }
 
        private void ExtendRecordBuffer(int position)
        {
            if (_records.Length <= position)
            {
                int newSize = _records.Length * 2;
                if (newSize <= position)
                {
                    newSize = position + 1;
                }
                Record[] tmp = new Record[newSize];
                Array.Copy(_records, tmp, _records.Length);
                _records = tmp;
            }
        }
 
        public bool FindStylesheetElement()
        {
            if (!_topLevelReader)
            {
                if (_reader.ReadState != ReadState.Interactive)
                {
                    return false;
                }
            }
 
            // The stylesheet may be an embedded stylesheet. If this is the case the reader will be in Interactive state and should be
            // positioned on xsl:stylesheet element (or any preceding whitespace) but there also can be namespaces defined on one
            // of the ancestor nodes. These namespace definitions have to be copied to the xsl:stylesheet element scope. Otherwise it
            // will not be possible to resolve them later and loading the stylesheet will end up with throwing an exception.
            IDictionary<string, string>? namespacesInScope = null;
            if (_reader.ReadState == ReadState.Interactive)
            {
                // This may be an embedded stylesheet - store namespaces in scope
                IXmlNamespaceResolver? nsResolver = _reader as IXmlNamespaceResolver;
                if (nsResolver != null)
                {
                    namespacesInScope = nsResolver.GetNamespacesInScope(XmlNamespaceScope.ExcludeXml);
                }
            }
 
            while (MoveToNextSibling() && _nodeType == XmlNodeType.Whitespace) ;
 
            // An Element node was reached. Potentially this is xsl:stylesheet instruction.
            if (_nodeType == XmlNodeType.Element)
            {
                // If namespacesInScope is not null then the stylesheet being read is an embedded stylesheet that can have namespaces
                // defined outside of xsl:stylesheet instruction. In this case the namespace definitions collected above have to be added
                // to the element scope.
                if (namespacesInScope != null)
                {
                    foreach (KeyValuePair<string, string> prefixNamespacePair in namespacesInScope)
                    {
                        // The namespace could be redefined on the element we just read. If this is the case scopeManager already has
                        // namespace definition for this prefix and the old definition must not be added to the scope.
                        if (_scopeManager.LookupNamespace(prefixNamespacePair.Key) == null)
                        {
                            string nsAtomizedValue = _atoms.NameTable.Add(prefixNamespacePair.Value);
                            _scopeManager.AddNsDeclaration(prefixNamespacePair.Key, nsAtomizedValue);
                            _ctxInfo!.AddNamespace(prefixNamespacePair.Key, nsAtomizedValue);
                        }
                    }
                }
 
                // return true to indicate that we reached XmlNodeType.Element node - potentially xsl:stylesheet element.
                return true;
            }
 
            // return false to indicate that we did not reach XmlNodeType.Element node so it is not a valid stylesheet.
            return false;
        }
 
        public void Finish()
        {
            _scopeManager.CheckEmpty();
 
            if (_topLevelReader)
            {
                while (_reader.ReadState == ReadState.Interactive)
                {
                    _reader.Skip();
                }
            }
        }
 
        private void FillupRecord(ref Record rec)
        {
            rec.localName = _reader.LocalName;
            rec.nsUri = _reader.NamespaceURI;
            rec.prefix = _reader.Prefix;
            rec.value = _reader.Value;
            rec.baseUri = _reader.BaseURI;
 
            if (_reatomize)
            {
                rec.localName = _atoms.NameTable.Add(rec.localName);
                rec.nsUri = _atoms.NameTable.Add(rec.nsUri);
                rec.prefix = _atoms.NameTable.Add(rec.prefix);
            }
 
            if (_readerLineInfo != null)
            {
                rec.start = new Location(_readerLineInfo.LineNumber, _readerLineInfo.LinePosition - PositionAdjustment(_reader.NodeType));
            }
        }
 
        private void SetRecordEnd(ref Record rec)
        {
            if (_readerLineInfo != null)
            {
                rec.end = new Location(_readerLineInfo.LineNumber, _readerLineInfo.LinePosition - PositionAdjustment(_reader.NodeType));
                if (_reader.BaseURI != rec.baseUri || rec.end.LessOrEqual(rec.start))
                {
                    rec.end = new Location(rec.start.Line, int.MaxValue);
                }
            }
        }
 
        private void FillupTextRecord(ref Record rec)
        {
            Debug.Assert(
                _reader.NodeType == XmlNodeType.Whitespace || _reader.NodeType == XmlNodeType.SignificantWhitespace ||
                _reader.NodeType == XmlNodeType.Text || _reader.NodeType == XmlNodeType.CDATA
            );
            rec.localName = string.Empty;
            rec.nsUri = string.Empty;
            rec.prefix = string.Empty;
            rec.value = _reader.Value;
            rec.baseUri = _reader.BaseURI;
 
            if (_readerLineInfo != null)
            {
                bool isCDATA = (_reader.NodeType == XmlNodeType.CDATA);
                int line = _readerLineInfo.LineNumber;
                int pos = _readerLineInfo.LinePosition;
                rec.start = new Location(line, pos - (isCDATA ? 9 : 0));
                char prevChar = ' ';
                foreach (char ch in rec.value)
                {
                    switch (ch)
                    {
                        case '\n':
                            if (prevChar != '\r')
                            {
                                goto case '\r';
                            }
                            break;
                        case '\r':
                            line++;
                            pos = 1;
                            break;
                        default:
                            pos++;
                            break;
                    }
                    prevChar = ch;
                }
                rec.end = new Location(line, pos + (isCDATA ? 3 : 0));
            }
        }
 
        private void FillupCharacterEntityRecord(ref Record rec)
        {
            Debug.Assert(_reader.NodeType == XmlNodeType.EntityReference);
            string local = _reader.LocalName;
            Debug.Assert(local[0] == '#' || local == "lt" || local == "gt" || local == "quot" || local == "apos");
            rec.localName = string.Empty;
            rec.nsUri = string.Empty;
            rec.prefix = string.Empty;
            rec.baseUri = _reader.BaseURI;
 
            if (_readerLineInfo != null)
            {
                rec.start = new Location(_readerLineInfo.LineNumber, _readerLineInfo.LinePosition - 1);
            }
            _reader.ResolveEntity();
            _reader.Read();
            Debug.Assert(_reader.NodeType == XmlNodeType.Text || _reader.NodeType == XmlNodeType.Whitespace || _reader.NodeType == XmlNodeType.SignificantWhitespace);
            rec.value = _reader.Value;
            _reader.Read();
            Debug.Assert(_reader.NodeType == XmlNodeType.EndEntity);
            if (_readerLineInfo != null)
            {
                rec.end = new Location(_readerLineInfo.LineNumber, _readerLineInfo.LinePosition + 1);
            }
        }
 
        private StringConcat _strConcat;
 
        // returns false if attribute is actualy namespace
        private bool ReadAttribute(ref Record rec)
        {
            Debug.Assert(_reader.NodeType == XmlNodeType.Attribute, "reader.NodeType == XmlNodeType.Attribute");
            FillupRecord(ref rec);
            if (Ref.Equal(rec.prefix, _atoms.Xmlns))
            {                                      // xmlns:foo="NS_FOO"
                string atomizedValue = _atoms.NameTable.Add(_reader.Value);
                if (!Ref.Equal(rec.localName, _atoms.Xml))
                {
                    _scopeManager.AddNsDeclaration(rec.localName, atomizedValue);
                    _ctxInfo!.AddNamespace(rec.localName, atomizedValue);
                }
                return false;
            }
            else if (rec.prefix.Length == 0 && Ref.Equal(rec.localName, _atoms.Xmlns))
            {  // xmlns="NS_FOO"
                string atomizedValue = _atoms.NameTable.Add(_reader.Value);
                _scopeManager.AddNsDeclaration(string.Empty, atomizedValue);
                _ctxInfo!.AddNamespace(string.Empty, atomizedValue);
                return false;
            }
            /* Read Attribute Value */
            {
                if (!_reader.ReadAttributeValue())
                {
                    // XmlTextReader never returns false from first call to ReadAttributeValue()
                    rec.value = string.Empty;
                    SetRecordEnd(ref rec);
                    return true;
                }
                if (_readerLineInfo != null)
                {
                    int correction = (_reader.NodeType == XmlNodeType.EntityReference) ? -2 : -1;
                    rec.valueStart = new Location(_readerLineInfo.LineNumber, _readerLineInfo.LinePosition + correction);
                    if (_reader.BaseURI != rec.baseUri || rec.valueStart.LessOrEqual(rec.start))
                    {
                        int nameLength = ((rec.prefix.Length != 0) ? rec.prefix.Length + 1 : 0) + rec.localName.Length;
                        rec.end = new Location(rec.start.Line, rec.start.Pos + nameLength + 1);
                    }
                }
                string lastText = string.Empty;
                _strConcat.Clear();
                do
                {
                    switch (_reader.NodeType)
                    {
                        case XmlNodeType.EntityReference:
                            _reader.ResolveEntity();
                            break;
                        case XmlNodeType.EndEntity:
                            break;
                        default:
                            Debug.Assert(_reader.NodeType == XmlNodeType.Text, "Unexpected node type inside attribute value");
                            lastText = _reader.Value;
                            _strConcat.Concat(lastText);
                            break;
                    }
                } while (_reader.ReadAttributeValue());
                rec.value = _strConcat.GetResult();
                if (_readerLineInfo != null)
                {
                    Debug.Assert(_reader.NodeType != XmlNodeType.EntityReference);
                    int correction = ((_reader.NodeType == XmlNodeType.EndEntity) ? 1 : lastText.Length) + 1;
                    rec.end = new Location(_readerLineInfo.LineNumber, _readerLineInfo.LinePosition + correction);
                    if (_reader.BaseURI != rec.baseUri || rec.end.LessOrEqual(rec.valueStart))
                    {
                        rec.end = new Location(rec.start.Line, int.MaxValue);
                    }
                }
            }
            return true;
        }
 
        // --------------------
 
        public bool MoveToFirstChild()
        {
            Debug.Assert(_nodeType == XmlNodeType.Element, "To call MoveToFirstChild() XsltInut should be positioned on an Element.");
            if (IsEmptyElement)
            {
                return false;
            }
            return ReadNextSibling();
        }
 
        public bool MoveToNextSibling()
        {
            Debug.Assert(_nodeType != XmlNodeType.Element || IsEmptyElement, "On non-empty elements we should call MoveToFirstChild()");
            if (_nodeType == XmlNodeType.Element || _nodeType == XmlNodeType.EndElement)
            {
                _scopeManager.ExitScope();
            }
            return ReadNextSibling();
        }
 
        public void SkipNode()
        {
            if (_nodeType == XmlNodeType.Element && MoveToFirstChild())
            {
                do
                {
                    SkipNode();
                } while (MoveToNextSibling());
            }
        }
 
        private int ReadTextNodes()
        {
            bool textPreserveWS = _reader.XmlSpace == XmlSpace.Preserve;
            bool textIsWhite = true;
            int curTextNode = 0;
            do
            {
                switch (_reader.NodeType)
                {
                    case XmlNodeType.Text:
                    // XLinq reports WS nodes as Text so we need to analyze them here
                    case XmlNodeType.CDATA:
                        if (textIsWhite && !XmlCharType.IsOnlyWhitespace(_reader.Value))
                        {
                            textIsWhite = false;
                        }
                        goto case XmlNodeType.SignificantWhitespace;
                    case XmlNodeType.Whitespace:
                    case XmlNodeType.SignificantWhitespace:
                        ExtendRecordBuffer(curTextNode);
                        FillupTextRecord(ref _records[curTextNode]);
                        _reader.Read();
                        curTextNode++;
                        break;
                    case XmlNodeType.EntityReference:
                        string local = _reader.LocalName;
                        if (local.Length > 0 && (
                            local[0] == '#' ||
                            local == "lt" || local == "gt" || local == "quot" || local == "apos"
                        ))
                        {
                            // Special treatment for character and built-in entities
                            ExtendRecordBuffer(curTextNode);
                            FillupCharacterEntityRecord(ref _records[curTextNode]);
                            if (textIsWhite && !XmlCharType.IsOnlyWhitespace(_records[curTextNode].value))
                            {
                                textIsWhite = false;
                            }
                            curTextNode++;
                        }
                        else
                        {
                            _reader.ResolveEntity();
                            _reader.Read();
                        }
                        break;
                    case XmlNodeType.EndEntity:
                        _reader.Read();
                        break;
                    default:
                        _nodeType = (
                            !textIsWhite ? XmlNodeType.Text :
                            textPreserveWS ? XmlNodeType.SignificantWhitespace :
                            /*default:    */ XmlNodeType.Whitespace
                        );
                        return curTextNode;
                }
            } while (true);
        }
 
        private bool ReadNextSibling()
        {
            if (_currentRecord < _lastTextNode)
            {
                Debug.Assert(_nodeType == XmlNodeType.Text || _nodeType == XmlNodeType.Whitespace || _nodeType == XmlNodeType.SignificantWhitespace);
                _currentRecord++;
                if (_currentRecord == _lastTextNode)
                {
                    _lastTextNode = 0;  // we are done with text nodes. Reset this counter
                }
                return true;
            }
            _currentRecord = 0;
            while (!_reader.EOF)
            {
                switch (_reader.NodeType)
                {
                    case XmlNodeType.Text:
                    case XmlNodeType.CDATA:
                    case XmlNodeType.Whitespace:
                    case XmlNodeType.SignificantWhitespace:
                    case XmlNodeType.EntityReference:
                        int numTextNodes = ReadTextNodes();
                        if (numTextNodes == 0)
                        {
                            // Most likely this was Entity that starts from non-text node
                            continue;
                        }
                        _lastTextNode = numTextNodes - 1;
                        return true;
                    case XmlNodeType.Element:
                        _scopeManager.EnterScope();
                        _numAttributes = ReadElement();
                        return true;
                    case XmlNodeType.EndElement:
                        _nodeType = XmlNodeType.EndElement;
                        _isEmptyElement = false;
                        FillupRecord(ref _records[0]);
                        _reader.Read();
                        SetRecordEnd(ref _records[0]);
                        return false;
                    default:
                        _reader.Read();
                        break;
                }
            }
            return false;
        }
 
        private int ReadElement()
        {
            Debug.Assert(_reader.NodeType == XmlNodeType.Element);
 
            _attributesRead = false;
            FillupRecord(ref _records[0]);
            _nodeType = XmlNodeType.Element;
            _isEmptyElement = _reader.IsEmptyElement;
            _ctxInfo = new ContextInfo(this);
 
            int record = 1;
            if (_reader.MoveToFirstAttribute())
            {
                do
                {
                    ExtendRecordBuffer(record);
                    if (ReadAttribute(ref _records[record]))
                    {
                        record++;
                    }
                } while (_reader.MoveToNextAttribute());
                _reader.MoveToElement();
            }
            _reader.Read();
            SetRecordEnd(ref _records[0]);
            _ctxInfo.lineInfo = BuildLineInfo();
            _attributes = null;
            return record - 1;
        }
 
        public void MoveToElement()
        {
            Debug.Assert(_nodeType == XmlNodeType.Element, "For MoveToElement() we should be positioned on Element or Attribute");
            _currentRecord = 0;
        }
 
        private bool MoveToAttributeBase(int attNum)
        {
            Debug.Assert(_nodeType == XmlNodeType.Element, "For MoveToLiteralAttribute() we should be positioned on Element or Attribute");
            if (0 < attNum && attNum <= _numAttributes)
            {
                _currentRecord = attNum;
                return true;
            }
            else
            {
                _currentRecord = 0;
                return false;
            }
        }
 
        public bool MoveToLiteralAttribute(int attNum)
        {
            Debug.Assert(_nodeType == XmlNodeType.Element, "For MoveToLiteralAttribute() we should be positioned on Element or Attribute");
            if (0 < attNum && attNum <= _numAttributes)
            {
                _currentRecord = attNum;
                return true;
            }
            else
            {
                _currentRecord = 0;
                return false;
            }
        }
 
        public bool MoveToXsltAttribute(int attNum, string attName)
        {
            Debug.Assert(_attributes != null && _attributes[attNum].name == attName, "Attribute numbering error.");
            _currentRecord = _xsltAttributeNumber[attNum];
            return _currentRecord != 0;
        }
 
        public bool IsRequiredAttribute(int attNum)
        {
            return (_attributes![attNum].flags & (_compiler.Version == 2 ? XsltLoader.V2Req : XsltLoader.V1Req)) != 0;
        }
 
        public bool AttributeExists(int attNum, string attName)
        {
            Debug.Assert(_attributes != null && _attributes[attNum].name == attName, "Attribute numbering error.");
            return _xsltAttributeNumber[attNum] != 0;
        }
 
        public struct DelayedQName
        {
            private readonly string _prefix;
            private readonly string _localName;
            public DelayedQName(ref Record rec)
            {
                _prefix = rec.prefix;
                _localName = rec.localName;
            }
            public static implicit operator string(DelayedQName qn)
            {
                return qn._prefix.Length == 0 ? qn._localName : (qn._prefix + ':' + qn._localName);
            }
        }
 
        public DelayedQName ElementName
        {
            get
            {
                Debug.Assert(_nodeType == XmlNodeType.Element || _nodeType == XmlNodeType.EndElement, "Input is positioned on element or attribute");
                return new DelayedQName(ref _records[0]);
            }
        }
 
        // -------------------- Keywords testing --------------------
 
        public bool IsNs(string ns) { return Ref.Equal(ns, NamespaceUri); }
        public bool IsKeyword(string kwd) { return Ref.Equal(kwd, LocalName); }
        public bool IsXsltNamespace() { return IsNs(_atoms.UriXsl); }
        public bool IsNullNamespace() { return IsNs(string.Empty); }
        public bool IsXsltKeyword(string kwd) { return IsKeyword(kwd) && IsXsltNamespace(); }
 
        // -------------------- Scope Management --------------------
        // See private class InputScopeManager bellow.
        // InputScopeManager handles some flags and values with respect of scope level where they as defined.
        // To parse XSLT style sheet we need the folloing values:
        //  BackwardCompatibility -- this flag is set when compiler.version==2 && xsl:version<2.
        //  ForwardCompatibility  -- this flag is set when compiler.version==2 && xsl:version>1 or compiler.version==1 && xsl:version!=1
        //  CanHaveApplyImports  -- we allow xsl:apply-templates instruction to apear in any template with match!=null, but not inside xsl:for-each
        //                          so it can't be inside global variable and has initial value = false
        //  ExtensionNamespace   -- is defined by extension-element-prefixes attribute on LRE or xsl:stylesheet
 
        public bool CanHaveApplyImports
        {
            get { return _scopeManager.CanHaveApplyImports; }
            set { _scopeManager.CanHaveApplyImports = value; }
        }
 
        public bool IsExtensionNamespace(string uri)
        {
            Debug.Assert(_nodeType != XmlNodeType.Element || _attributesRead, "Should first read attributes");
            return _scopeManager.IsExNamespace(uri);
        }
 
        public bool ForwardCompatibility
        {
            get
            {
                Debug.Assert(_nodeType != XmlNodeType.Element || _attributesRead, "Should first read attributes");
                return _scopeManager.ForwardCompatibility;
            }
        }
 
        public bool BackwardCompatibility
        {
            get
            {
                Debug.Assert(_nodeType != XmlNodeType.Element || _attributesRead, "Should first read attributes");
                return _scopeManager.BackwardCompatibility;
            }
        }
 
        public XslVersion XslVersion
        {
            get { return _scopeManager.ForwardCompatibility ? XslVersion.ForwardsCompatible : XslVersion.Current; }
        }
 
        private void SetVersion(int attVersion)
        {
            MoveToLiteralAttribute(attVersion);
            Debug.Assert(IsKeyword(_atoms.Version));
            double version = XPathConvert.StringToDouble(Value);
            if (double.IsNaN(version))
            {
                ReportError(/*[XT0110]*/SR.Xslt_InvalidAttrValue, _atoms.Version, Value);
#if XSLT2
                version = 2.0;
#else
                version = 1.0;
#endif
            }
            SetVersion(version);
        }
        private void SetVersion(double version)
        {
            if (_compiler.Version == 0)
            {
#if XSLT2
                compiler.Version = version < 2.0 ? 1 : 2;
#else
                _compiler.Version = 1;
#endif
            }
 
            if (_compiler.Version == 1)
            {
                _scopeManager.BackwardCompatibility = false;
                _scopeManager.ForwardCompatibility = (version != 1.0);
            }
            else
            {
                _scopeManager.BackwardCompatibility = version < 2;
                _scopeManager.ForwardCompatibility = 2 < version;
            }
        }
 
        // --------------- GetAttributes(...) -------------------------
        // All Xslt Instructions allows fixed set of attributes in null-ns, no in XSLT-ns and any in other ns.
        // In ForwardCompatibility mode we should ignore any of this problems.
        // We not use these functions for parseing LiteralResultElement and xsl:stylesheet
 
        public struct XsltAttribute
        {
            public string name;
            public int flags;
            public XsltAttribute(string name, int flags)
            {
                this.name = name;
                this.flags = flags;
            }
        }
 
        private XsltAttribute[]? _attributes;
        // Mapping of attribute names as they ordered in 'attributes' array
        // to there's numbers in actual stylesheet as they ordered in 'records' array
        private readonly int[] _xsltAttributeNumber = new int[21];
 
        public ContextInfo GetAttributes()
        {
            return GetAttributes(Array.Empty<XsltAttribute>());
        }
 
        public ContextInfo GetAttributes(XsltAttribute[] attributes)
        {
            Debug.Assert(NodeType == XmlNodeType.Element);
            Debug.Assert(attributes.Length <= _xsltAttributeNumber.Length);
            _attributes = attributes;
            // temp hack to fix value? = new AttValue(records[values[?]].value);
            _records[0].value = null!;
 
            // Standard Attributes:
            int attExtension = 0;
            int attExclude = 0;
            int attNamespace = 0;
            int attCollation = 0;
            int attUseWhen = 0;
 
            bool isXslOutput = IsXsltNamespace() && IsKeyword(_atoms.Output);
            bool SS = IsXsltNamespace() && (IsKeyword(_atoms.Stylesheet) || IsKeyword(_atoms.Transform));
            bool V2 = _compiler.Version == 2;
 
            for (int i = 0; i < attributes.Length; i++)
            {
                _xsltAttributeNumber[i] = 0;
            }
 
            _compiler.EnterForwardsCompatible();
            if (SS || V2 && !isXslOutput)
            {
                for (int i = 1; MoveToAttributeBase(i); i++)
                {
                    if (IsNullNamespace() && IsKeyword(_atoms.Version))
                    {
                        SetVersion(i);
                        break;
                    }
                }
            }
            if (_compiler.Version == 0)
            {
                Debug.Assert(SS, "First we parse xsl:stylesheet element");
#if XSLT2
                SetVersion(2.0);
#else
                SetVersion(1.0);
#endif
            }
            V2 = _compiler.Version == 2;
            int OptOrReq = V2 ? XsltLoader.V2Opt | XsltLoader.V2Req : XsltLoader.V1Opt | XsltLoader.V1Req;
 
            for (int attNum = 1; MoveToAttributeBase(attNum); attNum++)
            {
                if (IsNullNamespace())
                {
                    string localName = LocalName;
                    int kwd;
                    for (kwd = 0; kwd < attributes.Length; kwd++)
                    {
                        if (Ref.Equal(localName, attributes[kwd].name) && (attributes[kwd].flags & OptOrReq) != 0)
                        {
                            _xsltAttributeNumber[kwd] = attNum;
                            break;
                        }
                    }
 
                    if (kwd == attributes.Length)
                    {
                        if (Ref.Equal(localName, _atoms.ExcludeResultPrefixes) && (SS || V2)) { attExclude = attNum; }
                        else
                        if (Ref.Equal(localName, _atoms.ExtensionElementPrefixes) && (SS || V2)) { attExtension = attNum; }
                        else
                        if (Ref.Equal(localName, _atoms.XPathDefaultNamespace) && (V2)) { attNamespace = attNum; }
                        else
                        if (Ref.Equal(localName, _atoms.DefaultCollation) && (V2)) { attCollation = attNum; }
                        else
                        if (Ref.Equal(localName, _atoms.UseWhen) && (V2)) { attUseWhen = attNum; }
                        else
                        {
                            ReportError(/*[XT0090]*/SR.Xslt_InvalidAttribute, QualifiedName, _records[0].QualifiedName);
                        }
                    }
                }
                else if (IsXsltNamespace())
                {
                    ReportError(/*[XT0090]*/SR.Xslt_InvalidAttribute, QualifiedName, _records[0].QualifiedName);
                }
                else
                {
                    // Ignore the attribute.
                    // An element from the XSLT namespace may have any attribute not from the XSLT namespace,
                    // provided that the expanded-name of the attribute has a non-null namespace URI.
                    // For example, it may be 'xml:space'.
                }
            }
 
            _attributesRead = true;
 
            // Ignore invalid attributes if forwards-compatible behavior is enabled. Note that invalid
            // attributes may encounter before ForwardCompatibility flag is set to true. For example,
            // <xsl:stylesheet unknown="foo" version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"/>
            _compiler.ExitForwardsCompatible(ForwardCompatibility);
 
            InsertExNamespaces(attExtension, _ctxInfo!, /*extensions:*/ true);
            InsertExNamespaces(attExclude, _ctxInfo!, /*extensions:*/ false);
            SetXPathDefaultNamespace(attNamespace);
            SetDefaultCollation(attCollation);
            if (attUseWhen != 0)
            {
                ReportNYI(_atoms.UseWhen);
            }
 
            MoveToElement();
            // Report missing mandatory attributes
            for (int i = 0; i < attributes.Length; i++)
            {
                if (_xsltAttributeNumber[i] == 0)
                {
                    int flags = attributes[i].flags;
                    if (
                        _compiler.Version == 2 && (flags & XsltLoader.V2Req) != 0 ||
                        _compiler.Version == 1 && (flags & XsltLoader.V1Req) != 0 && (!ForwardCompatibility || (flags & XsltLoader.V2Req) != 0)
                    )
                    {
                        ReportError(/*[XT_001]*/SR.Xslt_MissingAttribute, attributes[i].name);
                    }
                }
            }
 
            return _ctxInfo!;
        }
 
        public ContextInfo GetLiteralAttributes(bool asStylesheet)
        {
            Debug.Assert(NodeType == XmlNodeType.Element);
 
            // Standard Attributes:
            int attVersion = 0;
            int attExtension = 0;
            int attExclude = 0;
            int attNamespace = 0;
            int attCollation = 0;
            int attUseWhen = 0;
 
            for (int i = 1; MoveToLiteralAttribute(i); i++)
            {
                if (IsXsltNamespace())
                {
                    string localName = LocalName;
                    if (Ref.Equal(localName, _atoms.Version)) { attVersion = i; }
                    else
                    if (Ref.Equal(localName, _atoms.ExtensionElementPrefixes)) { attExtension = i; }
                    else
                    if (Ref.Equal(localName, _atoms.ExcludeResultPrefixes)) { attExclude = i; }
                    else
                    if (Ref.Equal(localName, _atoms.XPathDefaultNamespace)) { attNamespace = i; }
                    else
                    if (Ref.Equal(localName, _atoms.DefaultCollation)) { attCollation = i; }
                    else
                    if (Ref.Equal(localName, _atoms.UseWhen)) { attUseWhen = i; }
                }
            }
 
            _attributesRead = true;
            this.MoveToElement();
 
            if (attVersion != 0)
            {
                // Enable forwards-compatible behavior if version attribute is not "1.0"
                SetVersion(attVersion);
            }
            else
            {
                if (asStylesheet)
                {
                    ReportError(Ref.Equal(NamespaceUri, _atoms.UriWdXsl) && Ref.Equal(LocalName, _atoms.Stylesheet) ?
                        /*[XT_025]*/SR.Xslt_WdXslNamespace : /*[XT0150]*/SR.Xslt_WrongStylesheetElement
                    );
#if XSLT2
                    SetVersion(2.0);
#else
                    SetVersion(1.0);
#endif
                }
            }
 
            // Parse xsl:extension-element-prefixes attribute (now that forwards-compatible mode is known)
            InsertExNamespaces(attExtension, _ctxInfo!, /*extensions:*/true);
 
            if (!IsExtensionNamespace(_records[0].nsUri))
            {
                // Parse other attributes (now that it's known this is a literal result element)
                if (_compiler.Version == 2)
                {
                    SetXPathDefaultNamespace(attNamespace);
                    SetDefaultCollation(attCollation);
                    if (attUseWhen != 0)
                    {
                        ReportNYI(_atoms.UseWhen);
                    }
                }
 
                InsertExNamespaces(attExclude, _ctxInfo!, /*extensions:*/false);
            }
 
            return _ctxInfo!;
        }
 
        // Get just the 'version' attribute of an unknown XSLT instruction. All other attributes
        // are ignored since we do not want to report an error on each of them.
        public void GetVersionAttribute()
        {
            Debug.Assert(NodeType == XmlNodeType.Element && IsXsltNamespace());
            bool V2 = _compiler.Version == 2;
 
            if (V2)
            {
                for (int i = 1; MoveToAttributeBase(i); i++)
                {
                    if (IsNullNamespace() && IsKeyword(_atoms.Version))
                    {
                        SetVersion(i);
                        break;
                    }
                }
            }
            _attributesRead = true;
        }
 
        private void InsertExNamespaces(int attExPrefixes, ContextInfo ctxInfo, bool extensions)
        {
            // List of Extension namespaces are maintaned by XsltInput's ScopeManager and is used by IsExtensionNamespace() in XsltLoader.LoadLiteralResultElement()
            // Both Extension and Exclusion namespaces will not be coppied by LiteralResultElement. Logic of copping namespaces are in QilGenerator.CompileLiteralElement().
            // At this time we will have different scope manager and need preserve all required information from load time to compile time.
            // Each XslNode contains list of NsDecls (nsList) which stores prefix+namespaces pairs for each namespace decls as well as exclusion namespaces.
            // In addition it also contains Exclusion namespace. They are represented as (null+namespace). Special case is Exlusion "#all" represented as (null+null).
            //and Exclusion namespace
            if (MoveToLiteralAttribute(attExPrefixes))
            {
                Debug.Assert(extensions ? IsKeyword(_atoms.ExtensionElementPrefixes) : IsKeyword(_atoms.ExcludeResultPrefixes));
                string value = Value;
                if (value.Length != 0)
                {
                    if (!extensions && _compiler.Version != 1 && value == "#all")
                    {
                        ctxInfo.nsList = new NsDecl(ctxInfo.nsList, /*prefix:*/null, /*nsUri:*/null);    // null, null means Exlusion #all
                    }
                    else
                    {
                        _compiler.EnterForwardsCompatible();
                        string[] list = XmlConvert.SplitString(value);
                        for (int idx = 0; idx < list.Length; idx++)
                        {
                            if (list[idx] == "#default")
                            {
                                list[idx] = this.LookupXmlNamespace(string.Empty)!;
                                if (list[idx].Length == 0 && _compiler.Version != 1 && !BackwardCompatibility)
                                {
                                    ReportError(/*[XTSE0809]*/SR.Xslt_ExcludeDefault);
                                }
                            }
                            else
                            {
                                list[idx] = this.LookupXmlNamespace(list[idx])!;
                            }
                        }
                        if (!_compiler.ExitForwardsCompatible(this.ForwardCompatibility))
                        {
                            // There were errors in the list, ignore the whole list
                            return;
                        }
 
                        for (int idx = 0; idx < list.Length; idx++)
                        {
                            if (list[idx] != null)
                            {
                                ctxInfo.nsList = new NsDecl(ctxInfo.nsList, /*prefix:*/null, list[idx]); // null means that this Exlusion NS
                                if (extensions)
                                {
                                    _scopeManager.AddExNamespace(list[idx]);                         // At Load time we need to know Extencion namespaces to ignore such literal elements.
                                }
                            }
                        }
                    }
                }
            }
        }
 
        private void SetXPathDefaultNamespace(int attNamespace)
        {
            if (MoveToLiteralAttribute(attNamespace))
            {
                Debug.Assert(IsKeyword(_atoms.XPathDefaultNamespace));
                if (Value.Length != 0)
                {
                    ReportNYI(_atoms.XPathDefaultNamespace);
                }
            }
        }
 
        private void SetDefaultCollation(int attCollation)
        {
            if (MoveToLiteralAttribute(attCollation))
            {
                Debug.Assert(IsKeyword(_atoms.DefaultCollation));
                string[] list = XmlConvert.SplitString(Value);
                int col;
                for (col = 0; col < list.Length; col++)
                {
                    if (System.Xml.Xsl.Runtime.XmlCollation.Create(list[col], /*throw:*/false) != null)
                    {
                        break;
                    }
                }
                if (col == list.Length)
                {
                    ReportErrorFC(/*[XTSE0125]*/SR.Xslt_CollationSyntax);
                }
                else
                {
                    if (list[col] != XmlReservedNs.NsCollCodePoint)
                    {
                        ReportNYI(_atoms.DefaultCollation);
                    }
                }
            }
        }
 
        // ----------------------- ISourceLineInfo -----------------------
 
        private static int PositionAdjustment(XmlNodeType nt) =>
            nt switch
            {
                XmlNodeType.Element => 1,               // "<"
                XmlNodeType.CDATA => 9,                 // "<![CDATA["
                XmlNodeType.ProcessingInstruction => 2, // "<?"
                XmlNodeType.Comment => 4,               // "<!--"
                XmlNodeType.EndElement => 2,            // "</"
                XmlNodeType.EntityReference => 1,       // "&"
                _ => 0,
            };
 
        public ISourceLineInfo BuildLineInfo()
        {
            return new SourceLineInfo(Uri, Start, End);
        }
 
        public ISourceLineInfo BuildNameLineInfo()
        {
            if (_readerLineInfo == null)
            {
                return BuildLineInfo();
            }
 
            // LocalName is checked against null since it is used to calculate QualifiedName used in turn to
            // calculate end position.
            // LocalName (and other cached properties) can be null only if nothing has been read from the reader.
            // This happens for instance when a reader which has already been closed or a reader positioned
            // on the very last node of the document is passed to the ctor.
            if (LocalName == null)
            {
                // Fill up the current record to set all the properties used below.
                FillupRecord(ref _records[_currentRecord]);
            }
 
            Location start = Start;
            int line = start.Line;
            int pos = start.Pos + PositionAdjustment(NodeType);
            return new SourceLineInfo(Uri, new Location(line, pos), new Location(line, pos + QualifiedName.Length));
        }
 
        public ISourceLineInfo BuildReaderLineInfo()
        {
            Location loc;
 
            if (_readerLineInfo != null)
                loc = new Location(_readerLineInfo.LineNumber, _readerLineInfo.LinePosition);
            else
                loc = new Location(0, 0);
 
            return new SourceLineInfo(_reader.BaseURI!, loc, loc);
        }
 
        // Resolve prefix, return null and report an error if not found
        public string? LookupXmlNamespace(string prefix)
        {
            Debug.Assert(prefix != null);
            string? nsUri = _scopeManager.LookupNamespace(prefix);
            if (nsUri != null)
            {
                Debug.Assert(Ref.Equal(_atoms.NameTable.Get(nsUri), nsUri), "Namespaces must be atomized");
                return nsUri;
            }
            if (prefix.Length == 0)
            {
                return string.Empty;
            }
            ReportError(/*[XT0280]*/SR.Xslt_InvalidPrefix, prefix);
            return null;
        }
 
        // ---------------------- Error Handling ----------------------
 
        public void ReportError(string res, params string?[]? args)
        {
            _compiler.ReportError(BuildNameLineInfo(), res, args);
        }
 
        public void ReportWarning(string res, params string?[]? args)
        {
            _compiler.ReportWarning(BuildNameLineInfo(), res, args);
        }
 
        public void ReportErrorFC(string res, params string[] args)
        {
            if (!ForwardCompatibility)
            {
                _compiler.ReportError(BuildNameLineInfo(), res, args);
            }
        }
 
        private void ReportNYI(string arg)
        {
            ReportErrorFC(SR.Xslt_NotYetImplemented, arg);
        }
 
        // -------------------------------- ContextInfo ------------------------------------
 
        internal sealed class ContextInfo
        {
            public NsDecl? nsList;
            public ISourceLineInfo? lineInfo;       // Line info for whole start tag
            public ISourceLineInfo? elemNameLi;     // Line info for element name
            public ISourceLineInfo? endTagLi;       // Line info for end tag or '/>'
            private readonly int _elemNameLength;
 
            // Create ContextInfo based on existing line info (used during AST rewriting)
            internal ContextInfo(ISourceLineInfo? lineinfo)
            {
                this.elemNameLi = lineinfo;
                this.endTagLi = lineinfo;
                this.lineInfo = lineinfo;
            }
 
            public ContextInfo(XsltInput input)
            {
                _elemNameLength = input.QualifiedName.Length;
            }
 
            public void AddNamespace(string prefix, string nsUri)
            {
                nsList = new NsDecl(nsList, prefix, nsUri);
            }
 
            public void SaveExtendedLineInfo(XsltInput input)
            {
                if (lineInfo!.Start.Line == 0)
                {
                    elemNameLi = endTagLi = null;
                    return;
                }
 
                elemNameLi = new SourceLineInfo(
                    lineInfo.Uri,
                    lineInfo.Start.Line, lineInfo.Start.Pos + 1,  // "<"
                    lineInfo.Start.Line, lineInfo.Start.Pos + 1 + _elemNameLength
                );
 
                if (!input.IsEmptyElement)
                {
                    Debug.Assert(input.NodeType == XmlNodeType.EndElement);
                    endTagLi = input.BuildLineInfo();
                }
                else
                {
                    Debug.Assert(input.NodeType == XmlNodeType.Element || input.NodeType == XmlNodeType.Attribute);
                    endTagLi = new EmptyElementEndTag(lineInfo);
                }
            }
 
            // We need this wrapper class because elementTagLi is not yet calculated
            internal sealed class EmptyElementEndTag : ISourceLineInfo
            {
                private readonly ISourceLineInfo _elementTagLi;
 
                public EmptyElementEndTag(ISourceLineInfo elementTagLi)
                {
                    _elementTagLi = elementTagLi;
                }
 
                public string? Uri { get { return _elementTagLi.Uri; } }
                public bool IsNoSource { get { return _elementTagLi.IsNoSource; } }
                public Location Start { get { return new Location(_elementTagLi.End.Line, _elementTagLi.End.Pos - 2); } }
                public Location End { get { return _elementTagLi.End; } }
            }
        }
        internal struct Record
        {
            public string localName;
            public string nsUri;
            public string prefix;
            public string value;
            public string? baseUri;
            public Location start;
            public Location valueStart;
            public Location end;
            public string QualifiedName { get { return prefix.Length == 0 ? localName : $"{prefix}:{localName}"; } }
        }
    }
}