File: System\Xml\Xsl\Runtime\XmlQueryRuntime.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;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reflection;
using System.Xml.Schema;
using System.Xml.XPath;
using System.Xml.Xsl.IlGen;
using System.Xml.Xsl.Xslt;
using MS.Internal.Xml.XPath;
 
namespace System.Xml.Xsl.Runtime
{
    /// <summary>
    /// XmlQueryRuntime is passed as the first parameter to all generated query methods.
    ///
    /// XmlQueryRuntime contains runtime support for generated ILGen queries:
    ///   1. Stack of output writers (stack handles nested document construction)
    ///   2. Manages list of all xml types that are used within the query
    ///   3. Manages list of all atomized names that are used within the query
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public sealed class XmlQueryRuntime
    {
        // Early-Bound Library Objects
        private readonly XmlQueryContext _ctxt;
        private XsltLibrary? _xsltLib;
        private readonly EarlyBoundInfo[]? _earlyInfo;
        private readonly object[]? _earlyObjects;
 
        // Global variables and parameters
        private readonly string[]? _globalNames;
        private readonly object[]? _globalValues;
 
        // Names, prefix mappings, and name filters
        private readonly XmlNameTable _nameTableQuery;
        private readonly string[]? _atomizedNames;             // Names after atomization
        private readonly XmlNavigatorFilter[]? _filters;       // Name filters (contain atomized names)
        private readonly StringPair[][]? _prefixMappingsList;  // Lists of prefix mappings (used to resolve computed names)
 
        // Xml types
        private readonly XmlQueryType[]? _types;
 
        // Collations
        private readonly XmlCollation[]? _collations;
 
        // Document ordering
        private readonly DocumentOrderComparer _docOrderCmp;
 
        // Indexes
        private ArrayList[]? _indexes;
 
        // Output construction
        private XmlQueryOutput _output;
        private readonly Stack<XmlQueryOutput> _stkOutput;
 
 
        //-----------------------------------------------
        // Constructors
        //-----------------------------------------------
 
        /// <summary>
        /// This constructor is internal so that external users cannot construct it (and therefore we do not have to test it separately).
        /// </summary>
        internal XmlQueryRuntime(XmlQueryStaticData data, object defaultDataSource, XmlResolver dataSources, XsltArgumentList? argList, XmlSequenceWriter seqWrt)
        {
            Debug.Assert(data != null);
            string[]? names = data.Names;
            Int32Pair[]? filters = data.Filters;
            WhitespaceRuleLookup? wsRules;
            int i;
 
            // Early-Bound Library Objects
            wsRules = (data.WhitespaceRules != null && data.WhitespaceRules.Count != 0) ? new WhitespaceRuleLookup(data.WhitespaceRules) : null;
            _ctxt = new XmlQueryContext(this, defaultDataSource, dataSources, argList, wsRules);
            _xsltLib = null;
            _earlyInfo = data.EarlyBound;
            _earlyObjects = (_earlyInfo != null) ? new object[_earlyInfo.Length] : null;
 
            // Global variables and parameters
            _globalNames = data.GlobalNames;
            _globalValues = (_globalNames != null) ? new object[_globalNames.Length] : null;
 
            // Names
            _nameTableQuery = _ctxt.QueryNameTable;
            _atomizedNames = null;
 
            if (names != null)
            {
                // Atomize all names in "nameTableQuery".  Use names from the default data source's
                // name table when possible.
                XmlNameTable? nameTableDefault = _ctxt.DefaultNameTable;
                _atomizedNames = new string[names.Length];
 
                if (nameTableDefault != _nameTableQuery && nameTableDefault != null)
                {
                    // Ensure that atomized names from the default data source are added to the
                    // name table used in this query
                    for (i = 0; i < names.Length; i++)
                    {
                        string? name = nameTableDefault.Get(names[i]);
                        _atomizedNames[i] = _nameTableQuery.Add(name ?? names[i]);
                    }
                }
                else
                {
                    // Enter names into nametable used in this query
                    for (i = 0; i < names.Length; i++)
                        _atomizedNames[i] = _nameTableQuery.Add(names[i]);
                }
            }
 
            // Name filters
            _filters = null;
            if (filters != null)
            {
                // Construct name filters.  Each pair of integers in the filters[] array specifies the
                // (localName, namespaceUri) of the NameFilter to be created.
                _filters = new XmlNavigatorFilter[filters.Length];
 
                for (i = 0; i < filters.Length; i++)
                    _filters[i] = XmlNavNameFilter.Create(_atomizedNames![filters[i].Left], _atomizedNames[filters[i].Right]);
            }
 
            // Prefix maping lists
            _prefixMappingsList = data.PrefixMappingsList;
 
            // Xml types
            _types = data.Types;
 
            // Xml collations
            _collations = data.Collations;
 
            // Document ordering
            _docOrderCmp = new DocumentOrderComparer();
 
            // Indexes
            _indexes = null;
 
            // Output construction
            _stkOutput = new Stack<XmlQueryOutput>(16);
            _output = new XmlQueryOutput(this, seqWrt);
        }
 
 
        //-----------------------------------------------
        // Debugger Utility Methods
        //-----------------------------------------------
 
        /// <summary>
        /// Return array containing the names of all the global variables and parameters used in this query, in this format:
        ///     {namespace}prefix:local-name
        /// </summary>
        public string[]? DebugGetGlobalNames()
        {
            return _globalNames;
        }
 
        /// <summary>
        /// Get the value of a global value having the specified name.  Always return the global value as a list of XPathItem.
        /// Return null if there is no global value having the specified name.
        /// </summary>
        public IList? DebugGetGlobalValue(string name)
        {
            for (int idx = 0; idx < _globalNames!.Length; idx++)
            {
                if (_globalNames[idx] == name)
                {
                    Debug.Assert(IsGlobalComputed(idx), "Cannot get the value of a global value until it has been computed.");
                    Debug.Assert(_globalValues![idx] is IList<XPathItem>, "Only debugger should call this method, and all global values should have type item* in debugging scenarios.");
                    return (IList)_globalValues[idx];
                }
            }
            return null;
        }
 
        /// <summary>
        /// Set the value of a global value having the specified name.  If there is no such value, this method is a no-op.
        /// </summary>
        public void DebugSetGlobalValue(string name, object value)
        {
            for (int idx = 0; idx < _globalNames!.Length; idx++)
            {
                if (_globalNames[idx] == name)
                {
                    Debug.Assert(IsGlobalComputed(idx), "Cannot get the value of a global value until it has been computed.");
                    Debug.Assert(_globalValues![idx] is IList<XPathItem>, "Only debugger should call this method, and all global values should have type item* in debugging scenarios.");
 
                    // Always convert "value" to a list of XPathItem using the item* converter
                    _globalValues[idx] = (IList<XPathItem>)XmlAnyListConverter.ItemList.ChangeType(value, typeof(XPathItem[]), null);
                    break;
                }
            }
        }
 
        /// <summary>
        /// Convert sequence to it's appropriate XSLT type and return to caller.
        /// </summary>
        public object? DebugGetXsltValue(IList? seq)
        {
            if (seq != null && seq.Count == 1)
            {
                XPathItem? item = seq[0] as XPathItem;
                if (item != null && !item.IsNode)
                {
                    return item.TypedValue;
                }
                else if (item is RtfNavigator)
                {
                    return ((RtfNavigator)item).ToNavigator();
                }
            }
 
            return seq;
        }
 
 
        //-----------------------------------------------
        // Early-Bound Library Objects
        //-----------------------------------------------
 
        internal const BindingFlags EarlyBoundFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static;
        internal const BindingFlags LateBoundFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static;
 
        /// <summary>
        /// Return the object that manages external user context information such as data sources, parameters, extension objects, etc.
        /// </summary>
        public XmlQueryContext ExternalContext
        {
            get { return _ctxt; }
        }
 
        /// <summary>
        /// Return the object that manages the state needed to implement various Xslt functions.
        /// </summary>
        public XsltLibrary XsltFunctions => _xsltLib ??= new XsltLibrary(this);
 
        /// <summary>
        /// Get the early-bound extension object identified by "index".  If it does not yet exist, create an instance using the
        /// corresponding ConstructorInfo.
        /// </summary>
        public object GetEarlyBoundObject(int index)
        {
            object obj;
            Debug.Assert(_earlyObjects != null && index < _earlyObjects.Length, "Early bound object does not exist");
 
            obj = _earlyObjects[index];
            if (obj == null)
            {
                // Early-bound object does not yet exist, so create it now
                obj = _earlyInfo![index].CreateObject();
                _earlyObjects[index] = obj;
            }
 
            return obj;
        }
 
        /// <summary>
        /// Return true if the early bound object identified by "namespaceUri" contains a method that matches "name".
        /// </summary>
        [RequiresUnreferencedCode(Scripts.ExtensionFunctionCannotBeStaticallyAnalyzed)]
        public bool EarlyBoundFunctionExists(string name, string namespaceUri)
        {
            if (_earlyInfo == null)
                return false;
 
            for (int idx = 0; idx < _earlyInfo.Length; idx++)
            {
                if (namespaceUri == _earlyInfo[idx].NamespaceUri)
                    return new XmlExtensionFunction(name, namespaceUri, -1, _earlyInfo[idx].EarlyBoundType, EarlyBoundFlags).CanBind();
            }
 
            return false;
        }
 
 
        //-----------------------------------------------
        // Global variables and parameters
        //-----------------------------------------------
 
        /// <summary>
        /// Return true if the global value specified by idxValue was previously computed.
        /// </summary>
        public bool IsGlobalComputed(int index)
        {
            return _globalValues![index] != null;
        }
 
        /// <summary>
        /// Return the value that is bound to the global variable or parameter specified by idxValue.
        /// If the value has not yet been computed, then compute it now and store it in this.globalValues.
        /// </summary>
        public object GetGlobalValue(int index)
        {
            Debug.Assert(IsGlobalComputed(index), "Cannot get the value of a global value until it has been computed.");
            return _globalValues![index];
        }
 
        /// <summary>
        /// Return the value that is bound to the global variable or parameter specified by idxValue.
        /// If the value has not yet been computed, then compute it now and store it in this.globalValues.
        /// </summary>
        public void SetGlobalValue(int index, object value)
        {
            Debug.Assert(!IsGlobalComputed(index), "Global value should only be set once.");
            _globalValues![index] = value;
        }
 
 
        //-----------------------------------------------
        // Names, prefix mappings, and name filters
        //-----------------------------------------------
 
        /// <summary>
        /// Return the name table used to atomize all names used by the query.
        /// </summary>
        public XmlNameTable NameTable
        {
            get { return _nameTableQuery; }
        }
 
        /// <summary>
        /// Get the atomized name at the specified index in the array of names.
        /// </summary>
        public string GetAtomizedName(int index)
        {
            Debug.Assert(_atomizedNames != null);
            return _atomizedNames[index];
        }
 
        /// <summary>
        /// Get the name filter at the specified index in the array of filters.
        /// </summary>
        public XmlNavigatorFilter GetNameFilter(int index)
        {
            Debug.Assert(_filters != null);
            return _filters[index];
        }
 
        /// <summary>
        /// XPathNodeType.All: Filters all nodes
        /// XPathNodeType.Attribute: Filters attributes
        /// XPathNodeType.Namespace: Not allowed
        /// XPathNodeType.XXX: Filters all nodes *except* those having XPathNodeType.XXX
        /// </summary>
        public XmlNavigatorFilter GetTypeFilter(XPathNodeType nodeType)
        {
            if (nodeType == XPathNodeType.All)
                return XmlNavNeverFilter.Create();
 
            if (nodeType == XPathNodeType.Attribute)
                return XmlNavAttrFilter.Create();
 
            return XmlNavTypeFilter.Create(nodeType);
        }
 
        /// <summary>
        /// Parse the specified tag name (foo:bar) and resolve the resulting prefix.  If the prefix cannot be resolved,
        /// then throw an error.  Return an XmlQualifiedName.
        /// </summary>
        public XmlQualifiedName ParseTagName(string tagName, int indexPrefixMappings)
        {
            string localName;
            string? ns;
 
            // Parse the tagName as a prefix, localName pair and resolve the prefix
            ParseTagName(tagName, indexPrefixMappings, out _, out localName, out ns);
            return new XmlQualifiedName(localName, ns);
        }
 
        /// <summary>
        /// Parse the specified tag name (foo:bar).  Return an XmlQualifiedName consisting of the parsed local name
        /// and the specified namespace.
        /// </summary>
        public XmlQualifiedName ParseTagName(string tagName, string ns)
        {
            string localName;
 
            // Parse the tagName as a prefix, localName pair
            ValidateNames.ParseQNameThrow(tagName, out _, out localName);
            return new XmlQualifiedName(localName, ns);
        }
 
        /// <summary>
        /// Parse the specified tag name (foo:bar) and resolve the resulting prefix.  If the prefix cannot be resolved,
        /// then throw an error.  Return the prefix, localName, and namespace URI.
        /// </summary>
        internal void ParseTagName(string tagName, int idxPrefixMappings, out string prefix, out string localName, out string ns)
        {
            Debug.Assert(_prefixMappingsList != null);
 
            // Parse the tagName as a prefix, localName pair
            ValidateNames.ParseQNameThrow(tagName, out prefix, out localName);
 
            // Map the prefix to a namespace URI
            ns = null!;
            foreach (StringPair pair in _prefixMappingsList[idxPrefixMappings])
            {
                if (prefix == pair.Left)
                {
                    ns = pair.Right;
                    break;
                }
            }
 
            // Throw exception if prefix could not be resolved
            if (ns == null)
            {
                // Check for mappings that are always in-scope
                if (prefix.Length == 0)
                    ns = "";
                else if (prefix.Equals("xml"))
                    ns = XmlReservedNs.NsXml;
                // It is not correct to resolve xmlns prefix in XPath but removing it would be a breaking change.
                else if (prefix.Equals("xmlns"))
                    ns = XmlReservedNs.NsXmlNs;
                else
                    throw new XslTransformException(SR.Xslt_InvalidPrefix, prefix);
            }
        }
 
        /// <summary>
        /// Return true if the nav1's LocalName and NamespaceURI properties equal nav2's corresponding properties.
        /// </summary>
        public bool IsQNameEqual(XPathNavigator n1, XPathNavigator n2)
        {
            if ((object)n1.NameTable == (object)n2.NameTable)
            {
                // Use atomized comparison
                return (object)n1.LocalName == (object)n2.LocalName && (object)n1.NamespaceURI == (object)n2.NamespaceURI;
            }
 
            return (n1.LocalName == n2.LocalName) && (n1.NamespaceURI == n2.NamespaceURI);
        }
 
        /// <summary>
        /// Return true if the specified navigator's LocalName and NamespaceURI properties equal the argument names.
        /// </summary>
        public bool IsQNameEqual(XPathNavigator navigator, int indexLocalName, int indexNamespaceUri)
        {
            if ((object)navigator.NameTable == (object)_nameTableQuery)
            {
                // Use atomized comparison
                return ((object)GetAtomizedName(indexLocalName) == (object)navigator.LocalName &&
                        (object)GetAtomizedName(indexNamespaceUri) == (object)navigator.NamespaceURI);
            }
 
            // Use string comparison
            return (GetAtomizedName(indexLocalName) == navigator.LocalName) && (GetAtomizedName(indexNamespaceUri) == navigator.NamespaceURI);
        }
 
        /// <summary>
        /// Get the Xml query type at the specified index in the array of types.
        /// </summary>
        internal XmlQueryType GetXmlType(int idxType)
        {
            Debug.Assert(_types != null);
            return _types[idxType];
        }
 
        /// <summary>
        /// Forward call to ChangeTypeXsltArgument(XmlQueryType, object, Type).
        /// </summary>
        public object ChangeTypeXsltArgument(int indexType, object value, Type destinationType)
        {
            return ChangeTypeXsltArgument(GetXmlType(indexType), value, destinationType);
        }
 
        /// <summary>
        /// Convert from the Clr type of "value" to Clr type "destinationType" using V1 Xslt rules.
        /// These rules include converting any Rtf values to Nodes.
        /// </summary>
        internal static object ChangeTypeXsltArgument(XmlQueryType xmlType, object value, Type destinationType)
        {
            Debug.Assert(XmlILTypeHelper.GetStorageType(xmlType).IsAssignableFrom(value.GetType()),
                         "Values passed to ChangeTypeXsltArgument should be in ILGen's default Clr representation.");
 
            Debug.Assert(destinationType == typeof(object) || !destinationType.IsAssignableFrom(value.GetType()),
                         $"No need to call ChangeTypeXsltArgument since value is already assignable to destinationType {destinationType}");
 
            switch (xmlType.TypeCode)
            {
                case XmlTypeCode.String:
                    if (destinationType == typeof(DateTime))
                        value = XsltConvert.ToDateTime((string)value);
                    break;
 
                case XmlTypeCode.Double:
                    if (destinationType != typeof(double))
                        value = Convert.ChangeType(value, destinationType, CultureInfo.InvariantCulture);
                    break;
 
                case XmlTypeCode.Node:
                    Debug.Assert(xmlType != XmlQueryTypeFactory.Node && xmlType != XmlQueryTypeFactory.NodeS,
                                 "Rtf values should have been eliminated by caller.");
 
                    if (destinationType == typeof(XPathNodeIterator))
                    {
                        value = new XPathArrayIterator((IList)value);
                    }
                    else if (destinationType == typeof(XPathNavigator[]))
                    {
                        // Copy sequence to XPathNavigator[]
                        IList<XPathNavigator> seq = (IList<XPathNavigator>)value;
                        XPathNavigator[] navArray = new XPathNavigator[seq.Count];
 
                        for (int i = 0; i < seq.Count; i++)
                            navArray[i] = seq[i];
 
                        value = navArray;
                    }
                    break;
 
                case XmlTypeCode.Item:
                    {
                        // Only typeof(object) is supported as a destination type
                        if (destinationType != typeof(object))
                            throw new XslTransformException(SR.Xslt_UnsupportedClrType, destinationType.Name);
 
                        // Convert to default, backwards-compatible representation
                        //   1. NodeSet: System.Xml.XPath.XPathNodeIterator
                        //   2. Rtf: System.Xml.XPath.XPathNavigator
                        //   3. Other:   Default V1 representation
                        IList<XPathItem> seq = (IList<XPathItem>)value;
                        if (seq.Count == 1)
                        {
                            XPathItem item = seq[0];
 
                            if (item.IsNode)
                            {
                                // Node or Rtf
                                RtfNavigator? rtf = item as RtfNavigator;
                                if (rtf != null)
                                    value = rtf.ToNavigator();
                                else
                                    value = new XPathArrayIterator((IList)value);
                            }
                            else
                            {
                                // Atomic value
                                value = item.TypedValue;
                            }
                        }
                        else
                        {
                            // Nodeset
                            value = new XPathArrayIterator((IList)value);
                        }
                        break;
                    }
            }
 
            Debug.Assert(destinationType.IsAssignableFrom(value.GetType()), $"ChangeType from type {value.GetType().Name} to type {destinationType.Name} failed");
            return value;
        }
 
        /// <summary>
        /// Forward call to ChangeTypeXsltResult(XmlQueryType, object)
        /// </summary>
        public object ChangeTypeXsltResult(int indexType, object value)
        {
            return ChangeTypeXsltResult(GetXmlType(indexType), value);
        }
 
        /// <summary>
        /// Convert from the Clr type of "value" to the default Clr type that ILGen uses to represent the xml type, using
        /// the conversion rules of the xml type.
        /// </summary>
        internal object ChangeTypeXsltResult(XmlQueryType xmlType, object? value)
        {
            if (value == null)
                throw new XslTransformException(SR.Xslt_ItemNull, string.Empty);
 
            switch (xmlType.TypeCode)
            {
                case XmlTypeCode.String:
                    if (value.GetType() == typeof(DateTime))
                        value = XsltConvert.ToString((DateTime)value);
                    break;
 
                case XmlTypeCode.Double:
                    if (value.GetType() != typeof(double))
                        value = ((IConvertible)value).ToDouble(null);
 
                    break;
 
                case XmlTypeCode.Node:
                    if (!xmlType.IsSingleton)
                    {
                        XPathArrayIterator? iter = value as XPathArrayIterator;
 
                        // Special-case XPathArrayIterator in order to avoid copies
                        if (iter != null && iter.AsList is XmlQueryNodeSequence sequence)
                        {
                            value = sequence;
                        }
                        else
                        {
                            // Iterate over list and ensure it only contains nodes
                            XmlQueryNodeSequence seq = new XmlQueryNodeSequence();
                            IList? list = value as IList;
 
                            if (list != null)
                            {
                                for (int i = 0; i < list.Count; i++)
                                    seq.Add(EnsureNavigator(list[i]));
                            }
                            else
                            {
                                foreach (object o in (IEnumerable)value)
                                    seq.Add(EnsureNavigator(o));
                            }
 
                            value = seq;
                        }
 
                        // Always sort node-set by document order
                        value = ((XmlQueryNodeSequence)value).DocOrderDistinct(_docOrderCmp);
                    }
                    break;
 
                case XmlTypeCode.Item:
                    {
                        Type sourceType = value.GetType();
                        IXPathNavigable? navigable;
 
                        // If static type is item, then infer type based on dynamic value
                        switch (XsltConvert.InferXsltType(sourceType).TypeCode)
                        {
                            case XmlTypeCode.Boolean:
                                value = new XmlQueryItemSequence(new XmlAtomicValue(XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.Boolean), value));
                                break;
 
                            case XmlTypeCode.Double:
                                value = new XmlQueryItemSequence(new XmlAtomicValue(XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.Double), ((IConvertible)value).ToDouble(null)));
                                break;
 
                            case XmlTypeCode.String:
                                if (sourceType == typeof(DateTime))
                                    value = new XmlQueryItemSequence(new XmlAtomicValue(XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.String), XsltConvert.ToString((DateTime)value)));
                                else
                                    value = new XmlQueryItemSequence(new XmlAtomicValue(XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.String), value));
                                break;
 
                            case XmlTypeCode.Node:
                                // Support XPathNavigator[]
                                value = ChangeTypeXsltResult(XmlQueryTypeFactory.NodeS, value);
                                break;
 
                            case XmlTypeCode.Item:
                                // Support XPathNodeIterator
                                if (value is XPathNodeIterator)
                                {
                                    value = ChangeTypeXsltResult(XmlQueryTypeFactory.NodeS, value);
                                    break;
                                }
 
                                // Support IXPathNavigable and XPathNavigator
                                navigable = value as IXPathNavigable;
                                if (navigable != null)
                                {
                                    if (value is XPathNavigator navigator)
                                        value = new XmlQueryNodeSequence(navigator);
                                    else
                                        value = new XmlQueryNodeSequence(navigable.CreateNavigator()!);
                                    break;
                                }
 
                                throw new XslTransformException(SR.Xslt_UnsupportedClrType, sourceType.Name);
                        }
                        break;
                    }
            }
 
            Debug.Assert(XmlILTypeHelper.GetStorageType(xmlType).IsAssignableFrom(value.GetType()), $"Xml type {xmlType} is not represented in ILGen as {value.GetType().Name}");
 
            return value;
        }
 
        /// <summary>
        /// Ensure that "value" is a navigator and not null.
        /// </summary>
        private static XPathNavigator EnsureNavigator(object? value)
        {
            XPathNavigator? nav = value as XPathNavigator;
 
            if (nav == null)
                throw new XslTransformException(SR.Xslt_ItemNull, string.Empty);
 
            return nav;
        }
 
        /// <summary>
        /// Return true if the type of every item in "seq" matches the xml type identified by "idxType".
        /// </summary>
        public bool MatchesXmlType(IList<XPathItem> seq, int indexType)
        {
            XmlQueryType typBase = GetXmlType(indexType);
 
            XmlQueryCardinality card = seq.Count switch
            {
                0 => XmlQueryCardinality.Zero,
                1 => XmlQueryCardinality.One,
                _ => XmlQueryCardinality.More,
            };
 
            if (!(card <= typBase.Cardinality))
                return false;
 
            typBase = typBase.Prime;
            for (int i = 0; i < seq.Count; i++)
            {
                if (!CreateXmlType(seq[i]).IsSubtypeOf(typBase))
                    return false;
            }
 
            return true;
        }
 
        /// <summary>
        /// Return true if the type of "item" matches the xml type identified by "idxType".
        /// </summary>
        public bool MatchesXmlType(XPathItem item, int indexType)
        {
            return CreateXmlType(item).IsSubtypeOf(GetXmlType(indexType));
        }
 
        /// <summary>
        /// Return true if the type of "seq" is a subtype of a singleton type identified by "code".
        /// </summary>
        public bool MatchesXmlType(IList<XPathItem> seq, XmlTypeCode code)
        {
            if (seq.Count != 1)
                return false;
 
            return MatchesXmlType(seq[0], code);
        }
 
        /// <summary>
        /// Return true if the type of "item" is a subtype of the type identified by "code".
        /// </summary>
        public bool MatchesXmlType(XPathItem item, XmlTypeCode code)
        {
            // All atomic type codes appear after AnyAtomicType
            if (code > XmlTypeCode.AnyAtomicType)
                return !item.IsNode && item.XmlType!.TypeCode == code;
 
            // Handle node code and AnyAtomicType
            switch (code)
            {
                case XmlTypeCode.AnyAtomicType: return !item.IsNode;
                case XmlTypeCode.Node: return item.IsNode;
                case XmlTypeCode.Item: return true;
                default:
                    if (!item.IsNode)
                        return false;
 
                    switch (((XPathNavigator)item).NodeType)
                    {
                        case XPathNodeType.Root: return code == XmlTypeCode.Document;
                        case XPathNodeType.Element: return code == XmlTypeCode.Element;
                        case XPathNodeType.Attribute: return code == XmlTypeCode.Attribute;
                        case XPathNodeType.Namespace: return code == XmlTypeCode.Namespace;
                        case XPathNodeType.Text: return code == XmlTypeCode.Text;
                        case XPathNodeType.SignificantWhitespace: return code == XmlTypeCode.Text;
                        case XPathNodeType.Whitespace: return code == XmlTypeCode.Text;
                        case XPathNodeType.ProcessingInstruction: return code == XmlTypeCode.ProcessingInstruction;
                        case XPathNodeType.Comment: return code == XmlTypeCode.Comment;
                    }
                    break;
            }
 
            Debug.Fail($"XmlTypeCode {code} was not fully handled.");
            return false;
        }
 
        /// <summary>
        /// Create an XmlQueryType that represents the type of "item".
        /// </summary>
        private static XmlQueryType CreateXmlType(XPathItem item)
        {
            if (item.IsNode)
            {
                // Rtf
                RtfNavigator? rtf = item as RtfNavigator;
                if (rtf != null)
                    return XmlQueryTypeFactory.Node;
 
                // Node
                XPathNavigator nav = (XPathNavigator)item;
                switch (nav.NodeType)
                {
                    case XPathNodeType.Root:
                    case XPathNodeType.Element:
                        if (nav.XmlType == null)
                            return XmlQueryTypeFactory.Type(nav.NodeType, XmlQualifiedNameTest.New(nav.LocalName, nav.NamespaceURI), XmlSchemaComplexType.UntypedAnyType, false);
 
                        return XmlQueryTypeFactory.Type(nav.NodeType, XmlQualifiedNameTest.New(nav.LocalName, nav.NamespaceURI), nav.XmlType, nav.SchemaInfo!.SchemaElement!.IsNillable);
 
                    case XPathNodeType.Attribute:
                        if (nav.XmlType == null)
                            return XmlQueryTypeFactory.Type(nav.NodeType, XmlQualifiedNameTest.New(nav.LocalName, nav.NamespaceURI), DatatypeImplementation.UntypedAtomicType, false);
 
                        return XmlQueryTypeFactory.Type(nav.NodeType, XmlQualifiedNameTest.New(nav.LocalName, nav.NamespaceURI), nav.XmlType, false);
                }
 
                return XmlQueryTypeFactory.Type(nav.NodeType, XmlQualifiedNameTest.Wildcard, XmlSchemaComplexType.AnyType, false);
            }
 
            // Atomic value
            return XmlQueryTypeFactory.Type((XmlSchemaSimpleType)item.XmlType!, true);
        }
 
 
        //-----------------------------------------------
        // Xml collations
        //-----------------------------------------------
 
        /// <summary>
        /// Get a collation that was statically created.
        /// </summary>
        public XmlCollation GetCollation(int index)
        {
            Debug.Assert(_collations != null);
            return _collations[index];
        }
 
        /// <summary>
        /// Create a collation from a string.
        /// </summary>
        public XmlCollation? CreateCollation(string collation)
        {
            return XmlCollation.Create(collation);
        }
 
 
        //-----------------------------------------------
        // Document Ordering and Identity
        //-----------------------------------------------
 
        /// <summary>
        /// Compare the relative positions of two navigators.  Return -1 if navThis is before navThat, 1 if after, and
        /// 0 if they are positioned to the same node.
        /// </summary>
        public int ComparePosition(XPathNavigator navigatorThis, XPathNavigator navigatorThat)
        {
            return _docOrderCmp.Compare(navigatorThis, navigatorThat);
        }
 
        /// <summary>
        /// Get a comparer which guarantees a stable ordering among nodes, even those from different documents.
        /// </summary>
        public IList<XPathNavigator> DocOrderDistinct(IList<XPathNavigator> seq)
        {
            if (seq.Count <= 1)
                return seq;
 
            XmlQueryNodeSequence nodeSeq = (XmlQueryNodeSequence)seq;
 
            return nodeSeq.DocOrderDistinct(_docOrderCmp);
        }
 
        /// <summary>
        /// Generate a unique string identifier for the specified node.  Do this by asking the navigator for an identifier
        /// that is unique within the document, and then prepend a document index.
        /// </summary>
        public string GenerateId(XPathNavigator navigator)
        {
            return string.Create(CultureInfo.InvariantCulture, $"ID{_docOrderCmp.GetDocumentIndex(navigator)}{navigator.UniqueId}");
        }
 
 
        //-----------------------------------------------
        // Indexes
        //-----------------------------------------------
 
        /// <summary>
        /// If an index having the specified Id has already been created over the "context" document, then return it
        /// in "index" and return true.  Otherwise, create a new, empty index and return false.
        /// </summary>
        public bool FindIndex(XPathNavigator context, int indexId, out XmlILIndex index)
        {
            XPathNavigator navRoot;
            ArrayList docIndexes;
            Debug.Assert(context != null);
 
            // Get root of document
            navRoot = context.Clone();
            navRoot.MoveToRoot();
 
            // Search pre-existing indexes in order to determine whether the specified index has already been created
            if (_indexes != null && indexId < _indexes.Length)
            {
                docIndexes = _indexes[indexId];
                if (docIndexes != null)
                {
                    // Search for an index defined over the specified document
                    for (int i = 0; i < docIndexes.Count; i += 2)
                    {
                        // If we find a matching document, then return the index saved in the next slot
                        if (((XPathNavigator)docIndexes[i]!).IsSamePosition(navRoot))
                        {
                            index = (XmlILIndex)docIndexes[i + 1]!;
                            return true;
                        }
                    }
                }
            }
 
            // Return a new, empty index
            index = new XmlILIndex();
            return false;
        }
 
        /// <summary>
        /// Add a newly built index over the specified "context" document to the existing collection of indexes.
        /// </summary>
        public void AddNewIndex(XPathNavigator context, int indexId, XmlILIndex index)
        {
            XPathNavigator navRoot;
            ArrayList docIndexes;
            Debug.Assert(context != null);
 
            // Get root of document
            navRoot = context.Clone();
            navRoot.MoveToRoot();
 
            // Ensure that a slot exists for the new index
            if (_indexes == null)
            {
                _indexes = new ArrayList[indexId + 4];
            }
            else if (indexId >= _indexes.Length)
            {
                // Resize array
                ArrayList[] indexesNew = new ArrayList[indexId + 4];
                Array.Copy(_indexes, indexesNew, _indexes.Length);
                _indexes = indexesNew;
            }
 
            docIndexes = (ArrayList)_indexes[indexId];
            if (docIndexes == null)
            {
                docIndexes = new ArrayList();
                _indexes[indexId] = docIndexes;
            }
 
            docIndexes.Add(navRoot);
            docIndexes.Add(index);
        }
 
 
        //-----------------------------------------------
        // Output construction
        //-----------------------------------------------
 
        /// <summary>
        /// Get output writer object.
        /// </summary>
        public XmlQueryOutput Output
        {
            get { return _output; }
        }
 
        /// <summary>
        /// Start construction of a nested sequence of items. Return a new XmlQueryOutput that will be
        /// used to construct this new sequence.
        /// </summary>
        public void StartSequenceConstruction(out XmlQueryOutput output)
        {
            // Push current writer
            _stkOutput.Push(_output);
 
            // Create new writers
            output = _output = new XmlQueryOutput(this, new XmlCachedSequenceWriter());
        }
 
        /// <summary>
        /// End construction of a nested sequence of items and return the items as an IList{XPathItem}
        /// internal class.  Return previous XmlQueryOutput.
        /// </summary>
        public IList<XPathItem> EndSequenceConstruction(out XmlQueryOutput output)
        {
            IList<XPathItem> seq = ((XmlCachedSequenceWriter)_output.SequenceWriter!).ResultSequence;
 
            // Restore previous XmlQueryOutput
            output = _output = _stkOutput.Pop();
 
            return seq;
        }
 
        /// <summary>
        /// Start construction of an Rtf. Return a new XmlQueryOutput object that will be used to construct this Rtf.
        /// </summary>
        public void StartRtfConstruction(string baseUri, out XmlQueryOutput output)
        {
            // Push current writer
            _stkOutput.Push(_output);
 
            // Create new XmlQueryOutput over an Rtf writer
            output = _output = new XmlQueryOutput(this, new XmlEventCache(baseUri, true));
        }
 
        /// <summary>
        /// End construction of an Rtf and return it as an RtfNavigator.  Return previous XmlQueryOutput object.
        /// </summary>
        public XPathNavigator EndRtfConstruction(out XmlQueryOutput output)
        {
            XmlEventCache events;
 
            events = (XmlEventCache)_output.Writer!;
 
            // Restore previous XmlQueryOutput
            output = _output = _stkOutput.Pop();
 
            // Return Rtf as an RtfNavigator
            events.EndEvents();
            return new RtfTreeNavigator(events, _nameTableQuery);
        }
 
        /// <summary>
        /// Construct a new RtfTextNavigator from the specified "text".  This is much more efficient than calling
        /// StartNodeConstruction(), StartRtf(), WriteString(), EndRtf(), and EndNodeConstruction().
        /// </summary>
        public XPathNavigator TextRtfConstruction(string text, string baseUri)
        {
            return new RtfTextNavigator(text, baseUri);
        }
 
 
        //-----------------------------------------------
        // Miscellaneous
        //-----------------------------------------------
 
        /// <summary>
        /// Report query execution information to event handler.
        /// </summary>
        public void SendMessage(string message)
        {
            _ctxt.OnXsltMessageEncountered(message);
        }
 
        /// <summary>
        /// Throw an Xml exception having the specified message text.
        /// </summary>
        public void ThrowException(string text)
        {
            throw new XslTransformException(text);
        }
 
        /// <summary>
        /// Position navThis to the same location as navThat.
        /// </summary>
        internal static XPathNavigator SyncToNavigator(XPathNavigator? navigatorThis, XPathNavigator navigatorThat)
        {
            if (navigatorThis == null || !navigatorThis.MoveTo(navigatorThat))
                return navigatorThat.Clone();
 
            return navigatorThis;
        }
 
        /// <summary>
        /// Function is called in Debug mode on each time context node change.
        /// </summary>
        public static int OnCurrentNodeChanged(XPathNavigator currentNode)
        {
            return 0;
        }
 
        // 'true' if current Namespace "inherited" from it's parent. Not defined locally.
        private static bool IsInheritedNamespace(XPathNavigator node)
        {
            Debug.Assert(node.NodeType == XPathNodeType.Namespace);
            XPathNavigator nav = node.Clone();
            if (nav.MoveToParent())
            {
                if (nav.MoveToFirstNamespace(XPathNamespaceScope.Local))
                {
                    do
                    {
                        if ((object)nav.LocalName == (object)node.LocalName)
                        {
                            return false;
                        }
                    } while (nav.MoveToNextNamespace(XPathNamespaceScope.Local));
                }
            }
            return true;
        }
    }
}