File: System\Xml\XPath\XPathNavigatorReader.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.Diagnostics;
using System.IO;
using System.Xml.Schema;
 
namespace System.Xml.XPath
{
    /// <summary>
    /// Reader that traverses the subtree rooted at the current position of the specified navigator.
    /// </summary>
    internal class XPathNavigatorReader : XmlReader, IXmlNamespaceResolver
    {
        private enum State
        {
            Initial,
            Content,
            EndElement,
            Attribute,
            AttrVal,
            InReadBinary,
            EOF,
            Closed,
            Error,
        }
 
        private XPathNavigator _nav;
        private readonly XPathNavigator _navToRead;
        private int _depth;
        private State _state;
        private XmlNodeType _nodeType;
        private int _attrCount;
        private bool _readEntireDocument;
 
        protected IXmlLineInfo? lineInfo;
        protected IXmlSchemaInfo? schemaInfo;
 
        private ReadContentAsBinaryHelper? _readBinaryHelper;
        private State _savedState;
 
        internal const string space = "space";
 
        private static ReadOnlySpan<XmlNodeType> ConvertFromXPathNodeType =>
        [
            XmlNodeType.Document,               // XPathNodeType.Root
            XmlNodeType.Element,                // XPathNodeType.Element
            XmlNodeType.Attribute,              // XPathNodeType.Attribute
            XmlNodeType.Attribute,              // XPathNodeType.Namespace
            XmlNodeType.Text,                   // XPathNodeType.Text
            XmlNodeType.SignificantWhitespace,  // XPathNodeType.SignificantWhitespace
            XmlNodeType.Whitespace,             // XPathNodeType.Whitespace
            XmlNodeType.ProcessingInstruction,  // XPathNodeType.ProcessingInstruction
            XmlNodeType.Comment,                // XPathNodeType.Comment
            XmlNodeType.None                    // XPathNodeType.All
        ];
 
        /// <summary>
        /// Translates an XPathNodeType value into the corresponding XmlNodeType value.
        /// XPathNodeType.Whitespace and XPathNodeType.SignificantWhitespace are mapped into XmlNodeType.Text.
        /// </summary>
        internal static XmlNodeType ToXmlNodeType(XPathNodeType typ)
        {
            return ConvertFromXPathNodeType[(int)typ];
        }
 
        internal object? UnderlyingObject
        {
            get
            {
                return _nav.UnderlyingObject;
            }
        }
 
        public static XPathNavigatorReader Create(XPathNavigator navToRead)
        {
            XPathNavigator nav = navToRead.Clone();
            IXmlLineInfo? xli = nav as IXmlLineInfo;
            IXmlSchemaInfo? xsi = nav as IXmlSchemaInfo;
#if NAVREADER_SUPPORTSLINEINFO
            if (null == xsi) {
                if (null == xli) {
                    return new XPathNavigatorReader(nav, xli, xsi);
                }
                else {
                    return new XPathNavigatorReaderWithLI(nav, xli, xsi);
                }
            }
            else {
                if (null == xli) {
                    return new XPathNavigatorReaderWithSI(nav, xli, xsi);
                }
                else {
                    return new XPathNavigatorReaderWithLIAndSI(nav, xli, xsi);
                }
            }
#else
            if (null == xsi)
            {
                return new XPathNavigatorReader(nav, xli, xsi);
            }
            else
            {
                return new XPathNavigatorReaderWithSI(nav, xli, xsi);
            }
#endif
        }
 
        protected XPathNavigatorReader(XPathNavigator navToRead, IXmlLineInfo? xli, IXmlSchemaInfo? xsi)
        {
            // Need clone that can be moved independently of original navigator
            _navToRead = navToRead;
            this.lineInfo = xli;
            this.schemaInfo = xsi;
            _nav = XmlEmptyNavigator.Singleton;
            _state = State.Initial;
            _depth = 0;
            _nodeType = XPathNavigatorReader.ToXmlNodeType(_nav.NodeType);
        }
 
        protected bool IsReading
        {
            get { return _state > State.Initial && _state < State.EOF; }
        }
 
        internal override XmlNamespaceManager NamespaceManager
        {
            get { return XPathNavigator.GetNamespaces(this); }
        }
 
 
        //-----------------------------------------------
        // IXmlNamespaceResolver -- pass through to Navigator
        //-----------------------------------------------
        public override XmlNameTable NameTable
        {
            get
            {
                return _navToRead.NameTable;
            }
        }
 
        IDictionary<string, string> IXmlNamespaceResolver.GetNamespacesInScope(XmlNamespaceScope scope)
        {
            return _nav.GetNamespacesInScope(scope);
        }
 
        string? IXmlNamespaceResolver.LookupNamespace(string prefix)
        {
            return _nav.LookupNamespace(prefix);
        }
 
        string? IXmlNamespaceResolver.LookupPrefix(string namespaceName)
        {
            return _nav.LookupPrefix(namespaceName);
        }
 
        //-----------------------------------------------
        // XmlReader -- pass through to Navigator
        //-----------------------------------------------
 
        public override XmlReaderSettings Settings
        {
            get
            {
                XmlReaderSettings rs = new XmlReaderSettings();
                rs.NameTable = this.NameTable;
                rs.ConformanceLevel = ConformanceLevel.Fragment;
                rs.CheckCharacters = false;
                rs.ReadOnly = true;
                return rs;
            }
        }
 
        public override IXmlSchemaInfo? SchemaInfo
        {
            get
            {
                // Special case attribute text (this.nav points to attribute even though current state is Text)
                if (_nodeType == XmlNodeType.Text)
                    return null;
                return _nav.SchemaInfo;
            }
        }
 
        public override System.Type ValueType
        {
            get { return _nav.ValueType; }
        }
 
        public override XmlNodeType NodeType
        {
            get { return _nodeType; }
        }
 
        public override string NamespaceURI
        {
            get
            {
                //NamespaceUri for namespace nodes is different in case of XPathNavigator and Reader
                if (_nav.NodeType == XPathNodeType.Namespace)
                    return this.NameTable.Add(XmlReservedNs.NsXmlNs);
                //Special case attribute text node
                if (this.NodeType == XmlNodeType.Text)
                    return string.Empty;
                return _nav.NamespaceURI;
            }
        }
 
        public override string LocalName
        {
            get
            {
                //Default namespace in case of reader has a local name value of 'xmlns'
                if (_nav.NodeType == XPathNodeType.Namespace && _nav.LocalName.Length == 0)
                    return this.NameTable.Add("xmlns");
                //Special case attribute text node
                if (this.NodeType == XmlNodeType.Text)
                    return string.Empty;
                return _nav.LocalName;
            }
        }
 
        public override string Prefix
        {
            get
            {
                //Prefix for namespace nodes is different in case of XPathNavigator and Reader
                if (_nav.NodeType == XPathNodeType.Namespace && _nav.LocalName.Length != 0)
                    return this.NameTable.Add("xmlns");
                //Special case attribute text node
                if (this.NodeType == XmlNodeType.Text)
                    return string.Empty;
                return _nav.Prefix;
            }
        }
 
        public override string BaseURI
        {
            get
            {
                //reader returns BaseUri even before read method is called.
                if (_state == State.Initial)
                    return _navToRead.BaseURI;
                return _nav.BaseURI;
            }
        }
 
        public override bool IsEmptyElement
        {
            get
            {
                return _nav.IsEmptyElement;
            }
        }
 
        public override XmlSpace XmlSpace
        {
            get
            {
                XPathNavigator tempNav = _nav.Clone();
                do
                {
                    if (tempNav.MoveToAttribute(XPathNavigatorReader.space, XmlReservedNs.NsXml))
                    {
                        switch (tempNav.Value.AsSpan().Trim(XmlConvert.WhitespaceChars))
                        {
                            case "default":
                                return XmlSpace.Default;
                            case "preserve":
                                return XmlSpace.Preserve;
                            default:
                                break;
                        }
                        tempNav.MoveToParent();
                    }
                }
                while (tempNav.MoveToParent());
                return XmlSpace.None;
            }
        }
 
        public override string XmlLang
        {
            get
            {
                return _nav.XmlLang;
            }
        }
 
        public override bool HasValue
        {
            get
            {
                if ((_nodeType != XmlNodeType.Element)
                    && (_nodeType != XmlNodeType.Document)
                    && (_nodeType != XmlNodeType.EndElement)
                    && (_nodeType != XmlNodeType.None))
                    return true;
                return false;
            }
        }
 
        public override string Value
        {
            get
            {
                if ((_nodeType != XmlNodeType.Element)
                    && (_nodeType != XmlNodeType.Document)
                    && (_nodeType != XmlNodeType.EndElement)
                    && (_nodeType != XmlNodeType.None))
                    return _nav.Value;
                return string.Empty;
            }
        }
 
        private XPathNavigator? GetElemNav()
        {
            XPathNavigator? tempNav;
            switch (_state)
            {
                case State.Content:
                    return _nav.Clone();
                case State.Attribute:
                case State.AttrVal:
                    tempNav = _nav.Clone();
                    if (tempNav.MoveToParent())
                        return tempNav;
                    break;
                case State.InReadBinary:
                    _state = _savedState;
                    XPathNavigator? nav = GetElemNav();
                    _state = State.InReadBinary;
                    return nav;
            }
            return null;
        }
 
        private XPathNavigator? GetElemNav(out int depth)
        {
            XPathNavigator? nav = null;
            switch (_state)
            {
                case State.Content:
                    if (_nodeType == XmlNodeType.Element)
                        nav = _nav.Clone();
                    depth = _depth;
                    break;
                case State.Attribute:
                    nav = _nav.Clone();
                    nav.MoveToParent();
                    depth = _depth - 1;
                    break;
                case State.AttrVal:
                    nav = _nav.Clone();
                    nav.MoveToParent();
                    depth = _depth - 2;
                    break;
                case State.InReadBinary:
                    _state = _savedState;
                    nav = GetElemNav(out depth);
                    _state = State.InReadBinary;
                    break;
                default:
                    depth = _depth;
                    break;
            }
            return nav;
        }
 
        private void MoveToAttr(XPathNavigator nav, int depth)
        {
            _nav.MoveTo(nav);
            _depth = depth;
            _nodeType = XmlNodeType.Attribute;
            _state = State.Attribute;
        }
 
        public override int AttributeCount
        {
            get
            {
                if (_attrCount < 0)
                {
                    // attribute count works for element, regardless of where you are in start tag
                    XPathNavigator? tempNav = GetElemNav();
                    int count = 0;
                    if (null != tempNav)
                    {
                        if (tempNav.MoveToFirstNamespace(XPathNamespaceScope.Local))
                        {
                            do
                            {
                                count++;
                            } while (tempNav.MoveToNextNamespace((XPathNamespaceScope.Local)));
                            tempNav.MoveToParent();
                        }
                        if (tempNav.MoveToFirstAttribute())
                        {
                            do
                            {
                                count++;
                            } while (tempNav.MoveToNextAttribute());
                        }
                    }
                    _attrCount = count;
                }
                return _attrCount;
            }
        }
 
        public override string? GetAttribute(string name)
        {
            // reader allows calling GetAttribute, even when positioned inside attributes
            XPathNavigator nav = _nav;
            switch (nav.NodeType)
            {
                case XPathNodeType.Element:
                    break;
                case XPathNodeType.Attribute:
                    nav = nav.Clone();
                    if (!nav.MoveToParent())
                        return null;
                    break;
                default:
                    return null;
            }
            string prefix, localname;
            ValidateNames.SplitQName(name, out prefix, out localname);
            if (0 == prefix.Length)
            {
                if (localname == "xmlns")
                    return nav.GetNamespace(string.Empty);
                if ((object)nav == (object)_nav)
                    nav = nav.Clone();
                if (nav.MoveToAttribute(localname, string.Empty))
                    return nav.Value;
            }
            else
            {
                if (prefix == "xmlns")
                    return nav.GetNamespace(localname);
                if ((object)nav == (object)_nav)
                    nav = nav.Clone();
                if (nav.MoveToFirstAttribute())
                {
                    do
                    {
                        if (nav.LocalName == localname && nav.Prefix == prefix)
                            return nav.Value;
                    } while (nav.MoveToNextAttribute());
                }
            }
            return null;
        }
 
        public override string? GetAttribute(string localName, string? namespaceURI)
        {
            ArgumentNullException.ThrowIfNull(localName);
 
            // reader allows calling GetAttribute, even when positioned inside attributes
            XPathNavigator nav = _nav;
            switch (nav.NodeType)
            {
                case XPathNodeType.Element:
                    break;
                case XPathNodeType.Attribute:
                    nav = nav.Clone();
                    if (!nav.MoveToParent())
                        return null;
                    break;
                default:
                    return null;
            }
            // are they really looking for a namespace-decl?
            if (namespaceURI == XmlReservedNs.NsXmlNs)
            {
                if (localName == "xmlns")
                    localName = string.Empty;
                return nav.GetNamespace(localName);
            }
            if (null == namespaceURI)
                namespaceURI = string.Empty;
            // We need to clone the navigator and move the clone to the attribute to see whether the attribute exists,
            // because XPathNavigator.GetAttribute return string.Empty for both when the attribute is not there or when
            // it has an empty value. XmlReader.GetAttribute must return null if the attribute does not exist.
            if ((object)nav == (object)_nav)
                nav = nav.Clone();
            if (nav.MoveToAttribute(localName, namespaceURI))
            {
                return nav.Value;
            }
            else
            {
                return null;
            }
        }
 
        private static string? GetNamespaceByIndex(XPathNavigator nav, int index, out int count)
        {
            string thisValue = nav.Value;
            string? value = null;
            if (nav.MoveToNextNamespace(XPathNamespaceScope.Local))
            {
                value = GetNamespaceByIndex(nav, index, out count);
            }
            else
            {
                count = 0;
            }
            if (count == index)
            {
                Debug.Assert(value == null);
                value = thisValue;
            }
            count++;
            return value;
        }
 
        public override string GetAttribute(int index)
        {
            if (index < 0)
                goto Error;
            XPathNavigator? nav = GetElemNav();
            if (null == nav)
                goto Error;
            if (nav.MoveToFirstNamespace(XPathNamespaceScope.Local))
            {
                // namespaces are returned in reverse order,
                // but we want to return them in the correct order,
                // so first count the namespaces
                int nsCount;
                string? value = GetNamespaceByIndex(nav, index, out nsCount);
                if (null != value)
                {
                    return value;
                }
                index -= nsCount;
                nav.MoveToParent();
            }
            if (nav.MoveToFirstAttribute())
            {
                do
                {
                    if (index == 0)
                        return nav.Value;
                    index--;
                } while (nav.MoveToNextAttribute());
            }
        // can't find it... error
        Error:
            throw new ArgumentOutOfRangeException(nameof(index));
        }
 
 
        public override bool MoveToAttribute(string localName, string? namespaceName)
        {
            ArgumentNullException.ThrowIfNull(localName);
 
            int depth;
            XPathNavigator? nav = GetElemNav(out depth);
            if (null != nav)
            {
                if (namespaceName == XmlReservedNs.NsXmlNs)
                {
                    if (localName == "xmlns")
                        localName = string.Empty;
                    if (nav.MoveToFirstNamespace(XPathNamespaceScope.Local))
                    {
                        do
                        {
                            if (nav.LocalName == localName)
                                goto FoundMatch;
                        } while (nav.MoveToNextNamespace(XPathNamespaceScope.Local));
                    }
                }
                else
                {
                    if (null == namespaceName)
                        namespaceName = string.Empty;
                    if (nav.MoveToAttribute(localName, namespaceName))
                        goto FoundMatch;
                }
            }
            return false;
 
        FoundMatch:
            if (_state == State.InReadBinary)
            {
                _readBinaryHelper!.Finish();
                _state = _savedState;
            }
            MoveToAttr(nav, depth + 1);
            return true;
        }
 
        public override bool MoveToFirstAttribute()
        {
            int depth;
            XPathNavigator? nav = GetElemNav(out depth);
            if (null != nav)
            {
                if (nav.MoveToFirstNamespace(XPathNamespaceScope.Local))
                {
                    // attributes are in reverse order
                    while (nav.MoveToNextNamespace(XPathNamespaceScope.Local))
                        ;
                    goto FoundMatch;
                }
                if (nav.MoveToFirstAttribute())
                {
                    goto FoundMatch;
                }
            }
            return false;
        FoundMatch:
            if (_state == State.InReadBinary)
            {
                _readBinaryHelper!.Finish();
                _state = _savedState;
            }
            MoveToAttr(nav, depth + 1);
            return true;
        }
 
        public override bool MoveToNextAttribute()
        {
            switch (_state)
            {
                case State.Content:
                    return MoveToFirstAttribute();
 
                case State.Attribute:
                    {
                        if (XPathNodeType.Attribute == _nav.NodeType)
                            return _nav.MoveToNextAttribute();
 
                        // otherwise it is on a namespace... namespace are in reverse order
                        Debug.Assert(XPathNodeType.Namespace == _nav.NodeType);
                        XPathNavigator nav = _nav.Clone();
                        if (!nav.MoveToParent())
                            return false; // shouldn't happen
                        if (!nav.MoveToFirstNamespace(XPathNamespaceScope.Local))
                            return false; // shouldn't happen
                        if (nav.IsSamePosition(_nav))
                        {
                            // this was the last one... start walking attributes
                            nav.MoveToParent();
                            if (!nav.MoveToFirstAttribute())
                                return false;
                            // otherwise we are there
                            _nav.MoveTo(nav);
                            return true;
                        }
                        else
                        {
                            XPathNavigator prev = nav.Clone();
                            while (true)
                            {
                                if (!nav.MoveToNextNamespace(XPathNamespaceScope.Local))
                                {
                                    Debug.Fail("Couldn't find Namespace Node! Should not happen!");
                                    return false;
                                }
                                if (nav.IsSamePosition(_nav))
                                {
                                    _nav.MoveTo(prev);
                                    return true;
                                }
                                prev.MoveTo(nav);
                            }
                            // found previous namespace position
                        }
                    }
                case State.AttrVal:
                    _depth--;
                    _state = State.Attribute;
                    if (!MoveToNextAttribute())
                    {
                        _depth++;
                        _state = State.AttrVal;
                        return false;
                    }
                    _nodeType = XmlNodeType.Attribute;
                    return true;
 
                case State.InReadBinary:
                    _state = _savedState;
                    if (!MoveToNextAttribute())
                    {
                        _state = State.InReadBinary;
                        return false;
                    }
                    _readBinaryHelper!.Finish();
                    return true;
 
                default:
                    return false;
            }
        }
 
        public override bool MoveToAttribute(string name)
        {
            int depth;
            XPathNavigator? nav = GetElemNav(out depth);
            if (null == nav)
                return false;
 
            string prefix, localname;
            ValidateNames.SplitQName(name, out prefix, out localname);
 
            // watch for a namespace name
            bool IsXmlnsNoPrefix;
            if ((IsXmlnsNoPrefix = (0 == prefix.Length && localname == "xmlns"))
                || (prefix == "xmlns"))
            {
                if (IsXmlnsNoPrefix)
                    localname = string.Empty;
                if (nav.MoveToFirstNamespace(XPathNamespaceScope.Local))
                {
                    do
                    {
                        if (nav.LocalName == localname)
                            goto FoundMatch;
                    } while (nav.MoveToNextNamespace(XPathNamespaceScope.Local));
                }
            }
            else if (0 == prefix.Length)
            {
                // the empty prefix always means empty namespaceUri for attributes
                if (nav.MoveToAttribute(localname, string.Empty))
                    goto FoundMatch;
            }
            else
            {
                if (nav.MoveToFirstAttribute())
                {
                    do
                    {
                        if (nav.LocalName == localname && nav.Prefix == prefix)
                            goto FoundMatch;
                    } while (nav.MoveToNextAttribute());
                }
            }
            return false;
 
        FoundMatch:
            if (_state == State.InReadBinary)
            {
                _readBinaryHelper!.Finish();
                _state = _savedState;
            }
            MoveToAttr(nav, depth + 1);
            return true;
        }
 
        public override bool MoveToElement()
        {
            switch (_state)
            {
                case State.Attribute:
                case State.AttrVal:
                    if (!_nav.MoveToParent())
                        return false;
                    _depth--;
                    if (_state == State.AttrVal)
                        _depth--;
                    _state = State.Content;
                    _nodeType = XmlNodeType.Element;
                    return true;
                case State.InReadBinary:
                    _state = _savedState;
                    if (!MoveToElement())
                    {
                        _state = State.InReadBinary;
                        return false;
                    }
                    _readBinaryHelper!.Finish();
                    break;
            }
            return false;
        }
 
        public override bool EOF
        {
            get
            {
                return _state == State.EOF;
            }
        }
 
        public override ReadState ReadState
        {
            get
            {
                switch (_state)
                {
                    case State.Initial:
                        return ReadState.Initial;
                    case State.Content:
                    case State.EndElement:
                    case State.Attribute:
                    case State.AttrVal:
                    case State.InReadBinary:
                        return ReadState.Interactive;
                    case State.EOF:
                        return ReadState.EndOfFile;
                    case State.Closed:
                        return ReadState.Closed;
                    default:
                        return ReadState.Error;
                }
            }
        }
 
        public override void ResolveEntity()
        {
            throw new InvalidOperationException(SR.Xml_InvalidOperation);
        }
 
        public override bool ReadAttributeValue()
        {
            if (_state == State.InReadBinary)
            {
                _readBinaryHelper!.Finish();
                _state = _savedState;
            }
            if (_state == State.Attribute)
            {
                _state = State.AttrVal;
                _nodeType = XmlNodeType.Text;
                _depth++;
                return true;
            }
            return false;
        }
 
        public override bool CanReadBinaryContent
        {
            get
            {
                return true;
            }
        }
 
        public override int ReadContentAsBase64(byte[] buffer, int index, int count)
        {
            if (ReadState != ReadState.Interactive)
            {
                return 0;
            }
 
            // init ReadContentAsBinaryHelper when called first time
            if (_state != State.InReadBinary)
            {
                _readBinaryHelper = ReadContentAsBinaryHelper.CreateOrReset(_readBinaryHelper, this);
                _savedState = _state;
            }
 
            // turn off InReadBinary state in order to have a normal Read() behavior when called from readBinaryHelper
            _state = _savedState;
 
            // call to the helper
            int readCount = _readBinaryHelper!.ReadContentAsBase64(buffer, index, count);
 
            // turn on InReadBinary state again and return
            _savedState = _state;
            _state = State.InReadBinary;
            return readCount;
        }
 
        public override int ReadContentAsBinHex(byte[] buffer, int index, int count)
        {
            if (ReadState != ReadState.Interactive)
            {
                return 0;
            }
 
            // init ReadContentAsBinaryHelper when called first time
            if (_state != State.InReadBinary)
            {
                _readBinaryHelper = ReadContentAsBinaryHelper.CreateOrReset(_readBinaryHelper, this);
                _savedState = _state;
            }
 
            // turn off InReadBinary state in order to have a normal Read() behavior when called from readBinaryHelper
            _state = _savedState;
 
            // call to the helper
            int readCount = _readBinaryHelper!.ReadContentAsBinHex(buffer, index, count);
 
            // turn on InReadBinary state again and return
            _savedState = _state;
            _state = State.InReadBinary;
            return readCount;
        }
 
        public override int ReadElementContentAsBase64(byte[] buffer, int index, int count)
        {
            if (ReadState != ReadState.Interactive)
            {
                return 0;
            }
 
            // init ReadContentAsBinaryHelper when called first time
            if (_state != State.InReadBinary)
            {
                _readBinaryHelper = ReadContentAsBinaryHelper.CreateOrReset(_readBinaryHelper, this);
                _savedState = _state;
            }
 
            // turn off InReadBinary state in order to have a normal Read() behavior when called from readBinaryHelper
            _state = _savedState;
 
            // call to the helper
            int readCount = _readBinaryHelper!.ReadElementContentAsBase64(buffer, index, count);
 
            // turn on InReadBinary state again and return
            _savedState = _state;
            _state = State.InReadBinary;
            return readCount;
        }
 
        public override int ReadElementContentAsBinHex(byte[] buffer, int index, int count)
        {
            if (ReadState != ReadState.Interactive)
            {
                return 0;
            }
 
            // init ReadContentAsBinaryHelper when called first time
            if (_state != State.InReadBinary)
            {
                _readBinaryHelper = ReadContentAsBinaryHelper.CreateOrReset(_readBinaryHelper, this);
                _savedState = _state;
            }
 
            // turn off InReadBinary state in order to have a normal Read() behavior when called from readBinaryHelper
            _state = _savedState;
 
            // call to the helper
            int readCount = _readBinaryHelper!.ReadElementContentAsBinHex(buffer, index, count);
 
            // turn on InReadBinary state again and return
            _savedState = _state;
            _state = State.InReadBinary;
            return readCount;
        }
 
        public override string? LookupNamespace(string prefix)
        {
            return _nav.LookupNamespace(prefix);
        }
 
        /// <summary>
        /// Current depth in subtree.
        /// </summary>
        public override int Depth
        {
            get { return _depth; }
        }
 
        /// <summary>
        /// Move to the next reader state.  Return false if that is ReaderState.Closed.
        /// </summary>
        public override bool Read()
        {
            _attrCount = -1;
            switch (_state)
            {
                case State.Error:
                case State.Closed:
                case State.EOF:
                    return false;
 
                case State.Initial:
                    // Starting state depends on the navigator's item type
                    _nav = _navToRead;
                    _state = State.Content;
                    if (XPathNodeType.Root == _nav.NodeType)
                    {
                        if (!_nav.MoveToFirstChild())
                        {
                            SetEOF();
                            return false;
                        }
                        _readEntireDocument = true;
                    }
                    else if (XPathNodeType.Attribute == _nav.NodeType)
                    {
                        _state = State.Attribute;
                    }
                    _nodeType = ToXmlNodeType(_nav.NodeType);
                    break;
 
                case State.Content:
                    if (_nav.MoveToFirstChild())
                    {
                        _nodeType = ToXmlNodeType(_nav.NodeType);
                        _depth++;
                        _state = State.Content;
                    }
                    else if (_nodeType == XmlNodeType.Element
                        && !_nav.IsEmptyElement)
                    {
                        _nodeType = XmlNodeType.EndElement;
                        _state = State.EndElement;
                    }
                    else
                        goto case State.EndElement;
                    break;
 
                case State.EndElement:
                    if (0 == _depth && !_readEntireDocument)
                    {
                        SetEOF();
                        return false;
                    }
                    else if (_nav.MoveToNext())
                    {
                        _nodeType = ToXmlNodeType(_nav.NodeType);
                        _state = State.Content;
                    }
                    else if (_depth > 0 && _nav.MoveToParent())
                    {
                        Debug.Assert(_nav.NodeType == XPathNodeType.Element, $"{_nav.NodeType} == XPathNodeType.Element");
                        _nodeType = XmlNodeType.EndElement;
                        _state = State.EndElement;
                        _depth--;
                    }
                    else
                    {
                        SetEOF();
                        return false;
                    }
                    break;
 
                case State.Attribute:
                case State.AttrVal:
                    if (!_nav.MoveToParent())
                    {
                        SetEOF();
                        return false;
                    }
                    _nodeType = ToXmlNodeType(_nav.NodeType);
                    _depth--;
                    if (_state == State.AttrVal)
                        _depth--;
                    goto case State.Content;
                case State.InReadBinary:
                    _state = _savedState;
                    _readBinaryHelper!.Finish();
                    return Read();
            }
            return true;
        }
 
 
        /// <summary>
        /// End reading by transitioning into the Closed state.
        /// </summary>
        public override void Close()
        {
            _nav = XmlEmptyNavigator.Singleton;
            _nodeType = XmlNodeType.None;
            _state = State.Closed;
            _depth = 0;
        }
 
        /// <summary>
        /// set reader to EOF state
        /// </summary>
        private void SetEOF()
        {
            _nav = XmlEmptyNavigator.Singleton;
            _nodeType = XmlNodeType.None;
            _state = State.EOF;
            _depth = 0;
        }
    }
 
#if NAVREADER_SUPPORTSLINEINFO
    internal sealed class XPathNavigatorReaderWithLI : XPathNavigatorReader, System.Xml.IXmlLineInfo {
        internal XPathNavigatorReaderWithLI( XPathNavigator navToRead, IXmlLineInfo xli, IXmlSchemaInfo? xsi )
            : base( navToRead, xli, xsi ) {
        }
 
        //-----------------------------------------------
        // IXmlLineInfo
        //-----------------------------------------------
 
        public virtual bool HasLineInfo() { return IsReading ? this.lineInfo.HasLineInfo() : false; }
        public virtual int LineNumber { get { return IsReading ? this.lineInfo.LineNumber : 0; } }
        public virtual int LinePosition { get { return IsReading ? this.lineInfo.LinePosition : 0; } }
    }
 
    internal sealed class XPathNavigatorReaderWithLIAndSI : XPathNavigatorReaderWithLI, System.Xml.IXmlLineInfo, System.Xml.Schema.IXmlSchemaInfo {
        internal XPathNavigatorReaderWithLIAndSI( XPathNavigator navToRead, IXmlLineInfo xli, IXmlSchemaInfo xsi )
            : base( navToRead, xli, xsi ) {
        }
 
        //-----------------------------------------------
        // IXmlSchemaInfo
        //-----------------------------------------------
 
        public virtual XmlSchemaValidity Validity { get { return IsReading ? this.schemaInfo.Validity : XmlSchemaValidity.NotKnown; } }
        public override bool IsDefault { get { return IsReading ? this.schemaInfo.IsDefault : false; } }
        public virtual bool IsNil { get { return IsReading ? this.schemaInfo.IsNil : false; } }
        public virtual XmlSchemaSimpleType MemberType { get { return IsReading ? this.schemaInfo.MemberType : null; } }
        public virtual XmlSchemaType SchemaType { get { return IsReading ? this.schemaInfo.SchemaType : null; } }
        public virtual XmlSchemaElement SchemaElement { get { return IsReading ? this.schemaInfo.SchemaElement : null; } }
        public virtual XmlSchemaAttribute SchemaAttribute { get { return IsReading ? this.schemaInfo.SchemaAttribute : null; } }
    }
#endif
 
    internal sealed class XPathNavigatorReaderWithSI : XPathNavigatorReader, System.Xml.Schema.IXmlSchemaInfo
    {
        internal XPathNavigatorReaderWithSI(XPathNavigator navToRead, IXmlLineInfo? xli, IXmlSchemaInfo xsi)
            : base(navToRead, xli, xsi)
        {
            schemaInfo = xsi;
        }
 
        //-----------------------------------------------
        // IXmlSchemaInfo
        //-----------------------------------------------
 
        public XmlSchemaValidity Validity { get { return IsReading ? this.schemaInfo!.Validity : XmlSchemaValidity.NotKnown; } }
        public override bool IsDefault { get { return IsReading ? this.schemaInfo!.IsDefault : false; } }
        public bool IsNil { get { return IsReading ? this.schemaInfo!.IsNil : false; } }
        public XmlSchemaSimpleType? MemberType { get { return IsReading ? this.schemaInfo!.MemberType : null; } }
        public XmlSchemaType? SchemaType { get { return IsReading ? this.schemaInfo!.SchemaType : null; } }
        public XmlSchemaElement? SchemaElement { get { return IsReading ? this.schemaInfo!.SchemaElement : null; } }
        public XmlSchemaAttribute? SchemaAttribute { get { return IsReading ? this.schemaInfo!.SchemaAttribute : null; } }
    }
 
    /// <summary>
    /// The XmlEmptyNavigator exposes a document node with no children.
    /// Only one XmlEmptyNavigator exists per AppDomain (Singleton).  That's why the constructor is private.
    /// Use the Singleton property to get the EmptyNavigator.
    /// </summary>
    internal sealed class XmlEmptyNavigator : XPathNavigator
    {
        private static volatile XmlEmptyNavigator? s_singleton;
 
        private XmlEmptyNavigator()
        {
        }
 
        public static XmlEmptyNavigator Singleton => XmlEmptyNavigator.s_singleton ??= new XmlEmptyNavigator();
 
        //-----------------------------------------------
        // XmlReader
        //-----------------------------------------------
 
        public override XPathNodeType NodeType
        {
            get { return XPathNodeType.All; }
        }
 
        public override string NamespaceURI
        {
            get { return string.Empty; }
        }
 
        public override string LocalName
        {
            get { return string.Empty; }
        }
 
        public override string Name
        {
            get { return string.Empty; }
        }
 
        public override string Prefix
        {
            get { return string.Empty; }
        }
 
        public override string BaseURI
        {
            get { return string.Empty; }
        }
 
        public override string Value
        {
            get { return string.Empty; }
        }
 
        public override bool IsEmptyElement
        {
            get { return false; }
        }
 
        public override string XmlLang
        {
            get { return string.Empty; }
        }
 
        public override bool HasAttributes
        {
            get { return false; }
        }
 
        public override bool HasChildren
        {
            get { return false; }
        }
 
 
        //-----------------------------------------------
        // IXmlNamespaceResolver
        //-----------------------------------------------
 
        public override XmlNameTable NameTable
        {
            get { return new NameTable(); }
        }
 
        public override bool MoveToFirstChild()
        {
            return false;
        }
 
        public override void MoveToRoot()
        {
            //always on root
            return;
        }
 
        public override bool MoveToNext()
        {
            return false;
        }
 
        public override bool MoveToPrevious()
        {
            return false;
        }
 
        public override bool MoveToFirst()
        {
            return false;
        }
 
        public override bool MoveToFirstAttribute()
        {
            return false;
        }
 
        public override bool MoveToNextAttribute()
        {
            return false;
        }
 
        public override bool MoveToId(string id)
        {
            return false;
        }
 
        public override string GetAttribute(string localName, string namespaceName)
        {
            Debug.Fail("This shouldn't be called.");
            return null!;
        }
 
        public override bool MoveToAttribute(string localName, string namespaceName)
        {
            return false;
        }
 
        public override string GetNamespace(string name)
        {
            Debug.Fail("This shouldn't be called.");
            return null!;
        }
 
        public override bool MoveToNamespace(string prefix)
        {
            return false;
        }
 
 
        public override bool MoveToFirstNamespace(XPathNamespaceScope scope)
        {
            return false;
        }
 
        public override bool MoveToNextNamespace(XPathNamespaceScope scope)
        {
            return false;
        }
 
        public override bool MoveToParent()
        {
            return false;
        }
 
        public override bool MoveTo(XPathNavigator other)
        {
            // Only one instance of XmlEmptyNavigator exists on the system
            return (object)this == (object?)other;
        }
 
        public override XmlNodeOrder ComparePosition(XPathNavigator? other)
        {
            // Only one instance of XmlEmptyNavigator exists on the system
            return ((object)this == (object?)other) ? XmlNodeOrder.Same : XmlNodeOrder.Unknown;
        }
 
        public override bool IsSamePosition(XPathNavigator other)
        {
            // Only one instance of XmlEmptyNavigator exists on the system
            return (object)this == (object)other;
        }
 
 
        //-----------------------------------------------
        // XPathNavigator2
        //-----------------------------------------------
        public override XPathNavigator Clone()
        {
            // Singleton, so clone just returns this
            return this;
        }
    }
}