File: System\Xml\Core\XmlSubtreeReader.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;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Xml;
 
namespace System.Xml
{
    internal sealed partial class XmlSubtreeReader : XmlWrappingReader, IXmlLineInfo, IXmlNamespaceResolver
    {
        //
        // Private types
        //
        private sealed class NodeData
        {
            internal XmlNodeType type;
            internal string localName;
            internal string prefix;
            internal string name;
            internal string namespaceUri;
            internal string value;
 
            internal NodeData(XmlNodeType nodeType, string localName, string prefix, string name, string namespaceUri, string value)
            {
                this.type = nodeType;
                this.localName = localName;
                this.prefix = prefix;
                this.name = name;
                this.namespaceUri = namespaceUri;
                this.value = value;
            }
 
            internal void Set(XmlNodeType nodeType, string localName, string prefix, string name, string namespaceUri, string value)
            {
                this.type = nodeType;
                this.localName = localName;
                this.prefix = prefix;
                this.name = name;
                this.namespaceUri = namespaceUri;
                this.value = value;
            }
        }
 
        private enum State
        {
            Initial = ReadState.Initial,
            Interactive = ReadState.Interactive,
            Error = ReadState.Error,
            EndOfFile = ReadState.EndOfFile,
            Closed = ReadState.Closed,
            PopNamespaceScope,
            ClearNsAttributes,
            ReadElementContentAsBase64,
            ReadElementContentAsBinHex,
            ReadContentAsBase64,
            ReadContentAsBinHex,
        }
 
        private const int AttributeActiveStates = 0x62; // 00001100010 bin
        private const int NamespaceActiveStates = 0x7E2; // 11111100010 bin
 
        //
        // Fields
        //
        private readonly int _initialDepth;
        private State _state;
 
        // namespace management
        private readonly XmlNamespaceManager _nsManager;
        private NodeData[]? _nsAttributes;
        private int _nsAttrCount;
        private int _curNsAttr = -1;
 
        private readonly string _xmlns;
        private readonly string _xmlnsUri;
 
        // incremental reading of added xmlns nodes (ReadValueChunk, ReadContentAsBase64, ReadContentAsBinHex)
        private int _nsIncReadOffset;
        private IncrementalReadDecoder? _binDecoder;
 
        // cached nodes
        private bool _useCurNode;
        private NodeData _curNode = null!;
        // node used for a text node of ReadAttributeValue or as Initial or EOF node
        private readonly NodeData _tmpNode;
 
        //
        // Constants
        //
        internal int InitialNamespaceAttributeCount = 4;
 
        //
        // Constructor
        //
        internal XmlSubtreeReader(XmlReader reader) : base(reader)
        {
            _initialDepth = reader.Depth;
            _state = State.Initial;
            _nsManager = new XmlNamespaceManager(reader.NameTable);
            _xmlns = reader.NameTable.Add("xmlns");
            _xmlnsUri = reader.NameTable.Add(XmlReservedNs.NsXmlNs);
 
            _tmpNode = new NodeData(XmlNodeType.None, string.Empty, string.Empty, string.Empty, string.Empty, string.Empty);
 
            SetCurrentNode(_tmpNode);
        }
 
        //
        // XmlReader implementation
        //
        public override XmlNodeType NodeType
        {
            get
            {
                return _useCurNode ? _curNode.type : reader.NodeType;
            }
        }
 
        public override string Name
        {
            get
            {
                return _useCurNode ? _curNode.name : reader.Name;
            }
        }
 
        public override string LocalName
        {
            get
            {
                return _useCurNode ? _curNode.localName : reader.LocalName;
            }
        }
 
        public override string NamespaceURI
        {
            get
            {
                return _useCurNode ? _curNode.namespaceUri : reader.NamespaceURI;
            }
        }
 
        public override string Prefix
        {
            get
            {
                return _useCurNode ? _curNode.prefix : reader.Prefix;
            }
        }
 
        public override string Value
        {
            get
            {
                return _useCurNode ? _curNode.value : reader.Value;
            }
        }
 
        public override int Depth
        {
            get
            {
                int depth = reader.Depth - _initialDepth;
                if (_curNsAttr != -1)
                {
                    if (_curNode.type == XmlNodeType.Text)
                    { // we are on namespace attribute value
                        depth += 2;
                    }
                    else
                    {
                        depth++;
                    }
                }
                return depth;
            }
        }
 
        public override string BaseURI
        {
            get
            {
                return reader.BaseURI;
            }
        }
 
        public override bool IsEmptyElement
        {
            get
            {
                return reader.IsEmptyElement;
            }
        }
 
        public override bool EOF
        {
            get
            {
                return _state == State.EndOfFile || _state == State.Closed;
            }
        }
 
        public override ReadState ReadState
        {
            get
            {
                if (reader.ReadState == ReadState.Error)
                {
                    return ReadState.Error;
                }
                else
                {
                    if ((int)_state <= (int)State.Closed)
                    {
                        return (ReadState)(int)_state;
                    }
                    else
                    {
                        return ReadState.Interactive;
                    }
                }
            }
        }
 
        public override XmlNameTable NameTable
        {
            get
            {
                return reader.NameTable;
            }
        }
 
        public override int AttributeCount
        {
            get
            {
                return InAttributeActiveState ? reader.AttributeCount + _nsAttrCount : 0;
            }
        }
 
        public override string? GetAttribute(string name)
        {
            if (!InAttributeActiveState)
            {
                return null;
            }
            string? attr = reader.GetAttribute(name);
            if (attr != null)
            {
                return attr;
            }
 
            for (int i = 0; i < _nsAttrCount; i++)
            {
                if (name == _nsAttributes![i].name)
                {
                    return _nsAttributes[i].value;
                }
            }
            return null;
        }
 
        public override string? GetAttribute(string name, string? namespaceURI)
        {
            if (!InAttributeActiveState)
            {
                return null;
            }
 
            string? attr = reader.GetAttribute(name, namespaceURI);
            if (attr != null)
            {
                return attr;
            }
 
            for (int i = 0; i < _nsAttrCount; i++)
            {
                if (name == _nsAttributes![i].localName && namespaceURI == _xmlnsUri)
                {
                    return _nsAttributes[i].value;
                }
            }
 
            return null;
        }
 
        public override string GetAttribute(int i)
        {
            if (!InAttributeActiveState)
            {
                throw new ArgumentOutOfRangeException(nameof(i));
            }
            int n = reader.AttributeCount;
            if (i < n)
            {
                return reader.GetAttribute(i);
            }
            else if (i - n < _nsAttrCount)
            {
                return _nsAttributes![i - n].value;
            }
            else
            {
                throw new ArgumentOutOfRangeException(nameof(i));
            }
        }
 
        public override bool MoveToAttribute(string name)
        {
            if (!InAttributeActiveState)
            {
                return false;
            }
            if (reader.MoveToAttribute(name))
            {
                _curNsAttr = -1;
                _useCurNode = false;
                return true;
            }
 
            for (int i = 0; i < _nsAttrCount; i++)
            {
                if (name == _nsAttributes![i].name)
                {
                    MoveToNsAttribute(i);
                    return true;
                }
            }
            return false;
        }
 
        public override bool MoveToAttribute(string name, string? ns)
        {
            if (!InAttributeActiveState)
            {
                return false;
            }
            if (reader.MoveToAttribute(name, ns))
            {
                _curNsAttr = -1;
                _useCurNode = false;
                return true;
            }
 
            for (int i = 0; i < _nsAttrCount; i++)
            {
                if (name == _nsAttributes![i].localName && ns == _xmlnsUri)
                {
                    MoveToNsAttribute(i);
                    return true;
                }
            }
            return false;
        }
 
        public override void MoveToAttribute(int i)
        {
            if (!InAttributeActiveState)
            {
                throw new ArgumentOutOfRangeException(nameof(i));
            }
            int n = reader.AttributeCount;
            if (i < n)
            {
                reader.MoveToAttribute(i);
                _curNsAttr = -1;
                _useCurNode = false;
            }
            else if (i - n < _nsAttrCount)
            {
                MoveToNsAttribute(i - n);
            }
            else
            {
                throw new ArgumentOutOfRangeException(nameof(i));
            }
        }
 
        public override bool MoveToFirstAttribute()
        {
            if (!InAttributeActiveState)
            {
                return false;
            }
            if (reader.MoveToFirstAttribute())
            {
                _useCurNode = false;
                return true;
            }
            if (_nsAttrCount > 0)
            {
                MoveToNsAttribute(0);
                return true;
            }
            return false;
        }
 
        public override bool MoveToNextAttribute()
        {
            if (!InAttributeActiveState)
            {
                return false;
            }
            if (_curNsAttr == -1 && reader.MoveToNextAttribute())
            {
                return true;
            }
            if (_curNsAttr + 1 < _nsAttrCount)
            {
                MoveToNsAttribute(_curNsAttr + 1);
                return true;
            }
            return false;
        }
 
        public override bool MoveToElement()
        {
            if (!InAttributeActiveState)
            {
                return false;
            }
 
            _useCurNode = false;
            //If on Namespace attribute, the base reader is already on Element node.
            if (_curNsAttr >= 0)
            {
                _curNsAttr = -1;
                Debug.Assert(reader.NodeType == XmlNodeType.Element);
                return true;
            }
            else
            {
                return reader.MoveToElement();
            }
        }
 
        public override bool ReadAttributeValue()
        {
            if (!InAttributeActiveState)
            {
                return false;
            }
            if (_curNsAttr == -1)
            {
                return reader.ReadAttributeValue();
            }
            else if (_curNode.type == XmlNodeType.Text)
            { // we are on namespace attribute value
                return false;
            }
            else
            {
                Debug.Assert(_curNode.type == XmlNodeType.Attribute);
                _tmpNode.type = XmlNodeType.Text;
                _tmpNode.value = _curNode.value;
                SetCurrentNode(_tmpNode);
                return true;
            }
        }
 
        public override bool Read()
        {
            switch (_state)
            {
                case State.Initial:
                    _useCurNode = false;
                    _state = State.Interactive;
                    ProcessNamespaces();
                    return true;
 
                case State.Interactive:
                    _curNsAttr = -1;
                    _useCurNode = false;
                    reader.MoveToElement();
                    Debug.Assert(reader.Depth >= _initialDepth);
                    if (reader.Depth == _initialDepth)
                    {
                        if (reader.NodeType == XmlNodeType.EndElement ||
                            (reader.NodeType == XmlNodeType.Element && reader.IsEmptyElement))
                        {
                            _state = State.EndOfFile;
                            SetEmptyNode();
                            return false;
                        }
                        Debug.Assert(reader.NodeType == XmlNodeType.Element && !reader.IsEmptyElement);
                    }
                    if (reader.Read())
                    {
                        ProcessNamespaces();
                        return true;
                    }
                    else
                    {
                        SetEmptyNode();
                        return false;
                    }
 
                case State.EndOfFile:
                case State.Closed:
                case State.Error:
                    return false;
 
                case State.PopNamespaceScope:
                    _nsManager.PopScope();
                    goto case State.ClearNsAttributes;
 
                case State.ClearNsAttributes:
                    _nsAttrCount = 0;
                    _state = State.Interactive;
                    goto case State.Interactive;
 
                case State.ReadElementContentAsBase64:
                case State.ReadElementContentAsBinHex:
                    if (!FinishReadElementContentAsBinary())
                    {
                        return false;
                    }
                    return Read();
 
                case State.ReadContentAsBase64:
                case State.ReadContentAsBinHex:
                    if (!FinishReadContentAsBinary())
                    {
                        return false;
                    }
                    return Read();
 
                default:
                    Debug.Fail($"Unexpected state {_state}");
                    return false;
            }
        }
 
        public override void Close()
        {
            if (_state == State.Closed)
            {
                return;
            }
            try
            {
                // move the underlying reader to the next sibling
                if (_state != State.EndOfFile)
                {
                    reader.MoveToElement();
                    Debug.Assert(reader.Depth >= _initialDepth);
                    // move off the root of the subtree
                    if (reader.Depth == _initialDepth && reader.NodeType == XmlNodeType.Element && !reader.IsEmptyElement)
                    {
                        reader.Read();
                    }
                    // move to the end of the subtree, do nothing if on empty root element
                    while (reader.Depth > _initialDepth && reader.Read())
                    {
                        /* intentionally empty */
                    }
                }
            }
            catch
            { // never fail...
            }
            finally
            {
                _curNsAttr = -1;
                _useCurNode = false;
                _state = State.Closed;
                SetEmptyNode();
            }
        }
 
        public override void Skip()
        {
            switch (_state)
            {
                case State.Initial:
                    Read();
                    return;
 
                case State.Interactive:
                    _curNsAttr = -1;
                    _useCurNode = false;
                    reader.MoveToElement();
                    Debug.Assert(reader.Depth >= _initialDepth);
                    if (reader.Depth == _initialDepth)
                    {
                        if (reader.NodeType == XmlNodeType.Element && !reader.IsEmptyElement)
                        {
                            // we are on root of the subtree -> skip to the end element and set to Eof state
                            if (reader.Read())
                            {
                                while (reader.NodeType != XmlNodeType.EndElement && reader.Depth > _initialDepth)
                                {
                                    reader.Skip();
                                }
                            }
                        }
                        Debug.Assert(reader.NodeType == XmlNodeType.EndElement ||
                                      reader.NodeType == XmlNodeType.Element && reader.IsEmptyElement ||
                                      reader.ReadState != ReadState.Interactive);
                        _state = State.EndOfFile;
                        SetEmptyNode();
                        return;
                    }
 
                    if (reader.NodeType == XmlNodeType.Element && !reader.IsEmptyElement)
                    {
                        _nsManager.PopScope();
                    }
                    reader.Skip();
                    ProcessNamespaces();
 
                    Debug.Assert(reader.Depth >= _initialDepth);
                    return;
 
                case State.Closed:
                case State.EndOfFile:
                    return;
 
                case State.PopNamespaceScope:
                    _nsManager.PopScope();
                    goto case State.ClearNsAttributes;
 
                case State.ClearNsAttributes:
                    _nsAttrCount = 0;
                    _state = State.Interactive;
                    goto case State.Interactive;
 
                case State.ReadElementContentAsBase64:
                case State.ReadElementContentAsBinHex:
                    if (FinishReadElementContentAsBinary())
                    {
                        Skip();
                    }
                    break;
 
                case State.ReadContentAsBase64:
                case State.ReadContentAsBinHex:
                    if (FinishReadContentAsBinary())
                    {
                        Skip();
                    }
                    break;
 
                case State.Error:
                    return;
 
                default:
                    Debug.Fail($"Unexpected state {_state}");
                    return;
            }
        }
 
        public override object ReadContentAsObject()
        {
            try
            {
                InitReadContentAsType("ReadContentAsObject");
                object value = reader.ReadContentAsObject();
                FinishReadContentAsType();
                return value;
            }
            catch
            {
                _state = State.Error;
                throw;
            }
        }
 
        public override bool ReadContentAsBoolean()
        {
            try
            {
                InitReadContentAsType("ReadContentAsBoolean");
                bool value = reader.ReadContentAsBoolean();
                FinishReadContentAsType();
                return value;
            }
            catch
            {
                _state = State.Error;
                throw;
            }
        }
 
        public override DateTime ReadContentAsDateTime()
        {
            try
            {
                InitReadContentAsType("ReadContentAsDateTime");
                DateTime value = reader.ReadContentAsDateTime();
                FinishReadContentAsType();
                return value;
            }
            catch
            {
                _state = State.Error;
                throw;
            }
        }
 
        public override double ReadContentAsDouble()
        {
            try
            {
                InitReadContentAsType("ReadContentAsDouble");
                double value = reader.ReadContentAsDouble();
                FinishReadContentAsType();
                return value;
            }
            catch
            {
                _state = State.Error;
                throw;
            }
        }
 
        public override float ReadContentAsFloat()
        {
            try
            {
                InitReadContentAsType("ReadContentAsFloat");
                float value = reader.ReadContentAsFloat();
                FinishReadContentAsType();
                return value;
            }
            catch
            {
                _state = State.Error;
                throw;
            }
        }
 
        public override decimal ReadContentAsDecimal()
        {
            try
            {
                InitReadContentAsType("ReadContentAsDecimal");
                decimal value = reader.ReadContentAsDecimal();
                FinishReadContentAsType();
                return value;
            }
            catch
            {
                _state = State.Error;
                throw;
            }
        }
 
        public override int ReadContentAsInt()
        {
            try
            {
                InitReadContentAsType("ReadContentAsInt");
                int value = reader.ReadContentAsInt();
                FinishReadContentAsType();
                return value;
            }
            catch
            {
                _state = State.Error;
                throw;
            }
        }
 
        public override long ReadContentAsLong()
        {
            try
            {
                InitReadContentAsType("ReadContentAsLong");
                long value = reader.ReadContentAsLong();
                FinishReadContentAsType();
                return value;
            }
            catch
            {
                _state = State.Error;
                throw;
            }
        }
 
        public override string ReadContentAsString()
        {
            try
            {
                InitReadContentAsType("ReadContentAsString");
                string value = reader.ReadContentAsString();
                FinishReadContentAsType();
                return value;
            }
            catch
            {
                _state = State.Error;
                throw;
            }
        }
 
        public override object ReadContentAs(Type returnType, IXmlNamespaceResolver? namespaceResolver)
        {
            try
            {
                InitReadContentAsType("ReadContentAs");
                object value = reader.ReadContentAs(returnType, namespaceResolver);
                FinishReadContentAsType();
                return value;
            }
            catch
            {
                _state = State.Error;
                throw;
            }
        }
 
        public override bool CanReadBinaryContent
        {
            get
            {
                return reader.CanReadBinaryContent;
            }
        }
 
        public override int ReadContentAsBase64(byte[] buffer, int index, int count)
        {
            switch (_state)
            {
                case State.Initial:
                case State.EndOfFile:
                case State.Closed:
                case State.Error:
                    return 0;
 
                case State.ClearNsAttributes:
                case State.PopNamespaceScope:
                    switch (NodeType)
                    {
                        case XmlNodeType.Element:
                            throw CreateReadContentAsException(nameof(ReadContentAsBase64));
                        case XmlNodeType.EndElement:
                            return 0;
                        case XmlNodeType.Attribute:
                            if (_curNsAttr != -1 && reader.CanReadBinaryContent)
                            {
                                CheckBuffer(buffer, index, count);
                                if (count == 0)
                                {
                                    return 0;
                                }
                                if (_nsIncReadOffset == 0)
                                {
                                    // called first time on this ns attribute
                                    if (_binDecoder != null && _binDecoder is Base64Decoder)
                                    {
                                        _binDecoder.Reset();
                                    }
                                    else
                                    {
                                        _binDecoder = new Base64Decoder();
                                    }
                                }
                                if (_nsIncReadOffset == _curNode.value.Length)
                                {
                                    return 0;
                                }
 
                                Debug.Assert(_binDecoder != null);
                                _binDecoder.SetNextOutputBuffer(buffer, index, count);
                                _nsIncReadOffset += _binDecoder.Decode(_curNode.value, _nsIncReadOffset, _curNode.value.Length - _nsIncReadOffset);
                                return _binDecoder.DecodedCount;
                            }
                            goto case XmlNodeType.Text;
                        case XmlNodeType.Text:
                            Debug.Assert(AttributeCount > 0);
                            return reader.ReadContentAsBase64(buffer, index, count);
                        default:
                            Debug.Fail($"Unexpected state {_state}");
                            return 0;
                    }
 
                case State.Interactive:
                    _state = State.ReadContentAsBase64;
                    goto case State.ReadContentAsBase64;
 
                case State.ReadContentAsBase64:
                    int read = reader.ReadContentAsBase64(buffer, index, count);
                    if (read == 0)
                    {
                        _state = State.Interactive;
                        ProcessNamespaces();
                    }
                    return read;
 
                case State.ReadContentAsBinHex:
                case State.ReadElementContentAsBase64:
                case State.ReadElementContentAsBinHex:
                    throw new InvalidOperationException(SR.Xml_MixingBinaryContentMethods);
 
                default:
                    Debug.Fail($"Unexpected state {_state}");
                    return 0;
            }
        }
 
        public override int ReadElementContentAsBase64(byte[] buffer, int index, int count)
        {
            switch (_state)
            {
                case State.Initial:
                case State.EndOfFile:
                case State.Closed:
                case State.Error:
                    return 0;
 
                case State.Interactive:
                case State.PopNamespaceScope:
                case State.ClearNsAttributes:
                    if (!InitReadElementContentAsBinary(State.ReadElementContentAsBase64))
                    {
                        return 0;
                    }
                    goto case State.ReadElementContentAsBase64;
 
                case State.ReadElementContentAsBase64:
                    int read = reader.ReadContentAsBase64(buffer, index, count);
                    if (read > 0 || count == 0)
                    {
                        return read;
                    }
                    if (NodeType != XmlNodeType.EndElement)
                    {
                        throw new XmlException(SR.Xml_InvalidNodeType, reader.NodeType.ToString(), reader as IXmlLineInfo);
                    }
 
                    // pop namespace scope
                    _state = State.Interactive;
                    ProcessNamespaces();
 
                    // set eof state or move off the end element
                    if (reader.Depth == _initialDepth)
                    {
                        _state = State.EndOfFile;
                        SetEmptyNode();
                    }
                    else
                    {
                        Read();
                    }
                    return 0;
 
                case State.ReadContentAsBase64:
                case State.ReadContentAsBinHex:
                case State.ReadElementContentAsBinHex:
                    throw new InvalidOperationException(SR.Xml_MixingBinaryContentMethods);
 
                default:
                    Debug.Fail($"Unexpected state {_state}");
                    return 0;
            }
        }
 
        public override int ReadContentAsBinHex(byte[] buffer, int index, int count)
        {
            switch (_state)
            {
                case State.Initial:
                case State.EndOfFile:
                case State.Closed:
                case State.Error:
                    return 0;
 
                case State.ClearNsAttributes:
                case State.PopNamespaceScope:
                    switch (NodeType)
                    {
                        case XmlNodeType.Element:
                            throw CreateReadContentAsException(nameof(ReadContentAsBinHex));
                        case XmlNodeType.EndElement:
                            return 0;
                        case XmlNodeType.Attribute:
                            if (_curNsAttr != -1 && reader.CanReadBinaryContent)
                            {
                                CheckBuffer(buffer, index, count);
                                if (count == 0)
                                {
                                    return 0;
                                }
                                if (_nsIncReadOffset == 0)
                                {
                                    // called first time on this ns attribute
                                    if (_binDecoder != null && _binDecoder is BinHexDecoder)
                                    {
                                        _binDecoder.Reset();
                                    }
                                    else
                                    {
                                        _binDecoder = new BinHexDecoder();
                                    }
                                }
                                if (_nsIncReadOffset == _curNode.value.Length)
                                {
                                    return 0;
                                }
 
                                Debug.Assert(_binDecoder != null);
                                _binDecoder.SetNextOutputBuffer(buffer, index, count);
                                _nsIncReadOffset += _binDecoder.Decode(_curNode.value, _nsIncReadOffset, _curNode.value.Length - _nsIncReadOffset);
                                return _binDecoder.DecodedCount;
                            }
                            goto case XmlNodeType.Text;
                        case XmlNodeType.Text:
                            Debug.Assert(AttributeCount > 0);
                            return reader.ReadContentAsBinHex(buffer, index, count);
                        default:
                            Debug.Fail($"Unexpected state {_state}");
                            return 0;
                    }
 
                case State.Interactive:
                    _state = State.ReadContentAsBinHex;
                    goto case State.ReadContentAsBinHex;
 
                case State.ReadContentAsBinHex:
                    int read = reader.ReadContentAsBinHex(buffer, index, count);
                    if (read == 0)
                    {
                        _state = State.Interactive;
                        ProcessNamespaces();
                    }
                    return read;
 
                case State.ReadContentAsBase64:
                case State.ReadElementContentAsBase64:
                case State.ReadElementContentAsBinHex:
                    throw new InvalidOperationException(SR.Xml_MixingBinaryContentMethods);
 
                default:
                    Debug.Fail($"Unexpected state {_state}");
                    return 0;
            }
        }
 
        public override int ReadElementContentAsBinHex(byte[] buffer, int index, int count)
        {
            switch (_state)
            {
                case State.Initial:
                case State.EndOfFile:
                case State.Closed:
                case State.Error:
                    return 0;
 
                case State.Interactive:
                case State.PopNamespaceScope:
                case State.ClearNsAttributes:
                    if (!InitReadElementContentAsBinary(State.ReadElementContentAsBinHex))
                    {
                        return 0;
                    }
                    goto case State.ReadElementContentAsBinHex;
                case State.ReadElementContentAsBinHex:
                    int read = reader.ReadContentAsBinHex(buffer, index, count);
                    if (read > 0 || count == 0)
                    {
                        return read;
                    }
                    if (NodeType != XmlNodeType.EndElement)
                    {
                        throw new XmlException(SR.Xml_InvalidNodeType, reader.NodeType.ToString(), reader as IXmlLineInfo);
                    }
 
                    // pop namespace scope
                    _state = State.Interactive;
                    ProcessNamespaces();
 
                    // set eof state or move off the end element
                    if (reader.Depth == _initialDepth)
                    {
                        _state = State.EndOfFile;
                        SetEmptyNode();
                    }
                    else
                    {
                        Read();
                    }
                    return 0;
 
                case State.ReadContentAsBase64:
                case State.ReadContentAsBinHex:
                case State.ReadElementContentAsBase64:
                    throw new InvalidOperationException(SR.Xml_MixingBinaryContentMethods);
 
                default:
                    Debug.Fail($"Unexpected state {_state}");
                    return 0;
            }
        }
 
        public override bool CanReadValueChunk
        {
            get
            {
                return reader.CanReadValueChunk;
            }
        }
 
        public override int ReadValueChunk(char[] buffer, int index, int count)
        {
            switch (_state)
            {
                case State.Initial:
                case State.EndOfFile:
                case State.Closed:
                case State.Error:
                    return 0;
 
                case State.ClearNsAttributes:
                case State.PopNamespaceScope:
                    // ReadValueChunk implementation on added xmlns attributes
                    if (_curNsAttr != -1 && reader.CanReadValueChunk)
                    {
                        CheckBuffer(buffer, index, count);
                        int copyCount = _curNode.value.Length - _nsIncReadOffset;
                        if (copyCount > count)
                        {
                            copyCount = count;
                        }
                        if (copyCount > 0)
                        {
                            _curNode.value.CopyTo(_nsIncReadOffset, buffer, index, copyCount);
                        }
                        _nsIncReadOffset += copyCount;
                        return copyCount;
                    }
                    // Otherwise fall back to the case State.Interactive.
                    // No need to clean ns attributes or pop scope because the reader when ReadValueChunk is called
                    // - on Element errors
                    // - on EndElement errors
                    // - on Attribute does not move
                    // and that's all where State.ClearNsAttributes or State.PopnamespaceScope can be set
                    goto case State.Interactive;
 
                case State.Interactive:
                    return reader.ReadValueChunk(buffer, index, count);
 
                case State.ReadElementContentAsBase64:
                case State.ReadElementContentAsBinHex:
                case State.ReadContentAsBase64:
                case State.ReadContentAsBinHex:
                    throw new InvalidOperationException(SR.Xml_MixingReadValueChunkWithBinary);
 
                default:
                    Debug.Fail($"Unexpected state {_state}");
                    return 0;
            }
        }
 
        public override string? LookupNamespace(string prefix)
        {
            return ((IXmlNamespaceResolver)this).LookupNamespace(prefix);
        }
 
        //
        // IDisposable interface
        //
        protected override void Dispose(bool disposing)
        {
            // note: we do not want to dispose the underlying reader
            this.Close();
        }
 
        //
        // IXmlLineInfo implementation
        //
        int IXmlLineInfo.LineNumber
        {
            get
            {
                if (!_useCurNode)
                {
                    if (reader is IXmlLineInfo lineInfo)
                    {
                        return lineInfo.LineNumber;
                    }
                }
                return 0;
            }
        }
 
        int IXmlLineInfo.LinePosition
        {
            get
            {
                if (!_useCurNode)
                {
                    if (reader is IXmlLineInfo lineInfo)
                    {
                        return lineInfo.LinePosition;
                    }
                }
                return 0;
            }
        }
 
        bool IXmlLineInfo.HasLineInfo()
        {
            return reader is IXmlLineInfo;
        }
 
        //
        // IXmlNamespaceResolver implementation
        //
        IDictionary<string, string> IXmlNamespaceResolver.GetNamespacesInScope(XmlNamespaceScope scope)
        {
            if (!InNamespaceActiveState)
            {
                return new Dictionary<string, string>();
            }
            return _nsManager.GetNamespacesInScope(scope);
        }
 
        string? IXmlNamespaceResolver.LookupNamespace(string prefix)
        {
            if (!InNamespaceActiveState)
            {
                return null;
            }
            return _nsManager.LookupNamespace(prefix);
        }
 
        string? IXmlNamespaceResolver.LookupPrefix(string namespaceName)
        {
            if (!InNamespaceActiveState)
            {
                return null;
            }
            return _nsManager.LookupPrefix(namespaceName);
        }
 
        //
        // Private methods
        //
        private void ProcessNamespaces()
        {
            switch (reader.NodeType)
            {
                case XmlNodeType.Element:
                    _nsManager.PushScope();
 
                    string prefix = reader.Prefix;
                    string ns = reader.NamespaceURI;
                    if (_nsManager.LookupNamespace(prefix) != ns)
                    {
                        AddNamespace(prefix, ns);
                    }
 
                    if (reader.MoveToFirstAttribute())
                    {
                        do
                        {
                            prefix = reader.Prefix;
                            ns = reader.NamespaceURI;
 
                            if (Ref.Equal(ns, _xmlnsUri))
                            {
                                if (prefix.Length == 0)
                                {
                                    _nsManager.AddNamespace(string.Empty, reader.Value);
                                    RemoveNamespace(string.Empty, _xmlns);
                                }
                                else
                                {
                                    prefix = reader.LocalName;
                                    _nsManager.AddNamespace(prefix, reader.Value);
                                    RemoveNamespace(_xmlns, prefix);
                                }
                            }
                            else if (prefix.Length != 0 && _nsManager.LookupNamespace(prefix) != ns)
                            {
                                AddNamespace(prefix, ns);
                            }
                        } while (reader.MoveToNextAttribute());
                        reader.MoveToElement();
                    }
 
                    if (reader.IsEmptyElement)
                    {
                        _state = State.PopNamespaceScope;
                    }
                    break;
                case XmlNodeType.EndElement:
                    _state = State.PopNamespaceScope;
                    break;
            }
        }
 
        private void AddNamespace(string prefix, string ns)
        {
            _nsManager.AddNamespace(prefix, ns);
 
            int index = _nsAttrCount++;
            _nsAttributes ??= new NodeData[InitialNamespaceAttributeCount];
 
            if (index == _nsAttributes.Length)
            {
                NodeData[] newNsAttrs = new NodeData[_nsAttributes.Length * 2];
                Array.Copy(_nsAttributes, newNsAttrs, index);
                _nsAttributes = newNsAttrs;
            }
 
            string localName;
            string attrPrefix;
            string name;
 
            if (prefix.Length == 0)
            {
                localName = _xmlns;
                attrPrefix = string.Empty;
                name = _xmlns;
            }
            else
            {
                localName = prefix;
                attrPrefix = _xmlns;
                name = reader.NameTable.Add($"{_xmlns}:{prefix}");
            }
 
            if (_nsAttributes[index] == null)
            {
                _nsAttributes[index] = new NodeData(XmlNodeType.Attribute, localName, attrPrefix, name, _xmlnsUri, ns);
            }
            else
            {
                _nsAttributes[index].Set(XmlNodeType.Attribute, localName, attrPrefix, name, _xmlnsUri, ns);
            }
 
            Debug.Assert(_state == State.ClearNsAttributes || _state == State.Interactive || _state == State.PopNamespaceScope);
            _state = State.ClearNsAttributes;
 
            _curNsAttr = -1;
        }
 
        private void RemoveNamespace(string prefix, string localName)
        {
            for (int i = 0; i < _nsAttrCount; i++)
            {
                if (Ref.Equal(prefix, _nsAttributes![i].prefix) &&
                    Ref.Equal(localName, _nsAttributes[i].localName))
                {
                    if (i < _nsAttrCount - 1)
                    {
                        // swap
                        NodeData tmpNodeData = _nsAttributes[i];
                        _nsAttributes[i] = _nsAttributes[_nsAttrCount - 1];
                        _nsAttributes[_nsAttrCount - 1] = tmpNodeData;
                    }
                    _nsAttrCount--;
                    break;
                }
            }
        }
 
        private void MoveToNsAttribute(int index)
        {
            Debug.Assert(index >= 0 && index <= _nsAttrCount);
            reader.MoveToElement();
            _curNsAttr = index;
            _nsIncReadOffset = 0;
            SetCurrentNode(_nsAttributes![index]);
        }
 
        private bool InitReadElementContentAsBinary(State binaryState)
        {
            if (NodeType != XmlNodeType.Element)
            {
                throw reader.CreateReadElementContentAsException(nameof(ReadElementContentAsBase64));
            }
 
            bool isEmpty = IsEmptyElement;
 
            // move to content or off the empty element
            if (!Read() || isEmpty)
            {
                return false;
            }
            // special-case child element and end element
            switch (NodeType)
            {
                case XmlNodeType.Element:
                    throw new XmlException(SR.Xml_InvalidNodeType, reader.NodeType.ToString(), reader as IXmlLineInfo);
                case XmlNodeType.EndElement:
                    // pop scope & move off end element
                    ProcessNamespaces();
                    Read();
                    return false;
            }
 
            Debug.Assert(_state == State.Interactive);
            _state = binaryState;
            return true;
        }
 
        private bool FinishReadElementContentAsBinary()
        {
            Debug.Assert(_state == State.ReadElementContentAsBase64 || _state == State.ReadElementContentAsBinHex);
 
            byte[] bytes = new byte[256];
            if (_state == State.ReadElementContentAsBase64)
            {
                while (reader.ReadContentAsBase64(bytes, 0, 256) > 0) ;
            }
            else
            {
                while (reader.ReadContentAsBinHex(bytes, 0, 256) > 0) ;
            }
 
            if (NodeType != XmlNodeType.EndElement)
            {
                throw new XmlException(SR.Xml_InvalidNodeType, reader.NodeType.ToString(), reader as IXmlLineInfo);
            }
 
            // pop namespace scope
            _state = State.Interactive;
            ProcessNamespaces();
 
            // check eof
            if (reader.Depth == _initialDepth)
            {
                _state = State.EndOfFile;
                SetEmptyNode();
                return false;
            }
            // move off end element
            return Read();
        }
 
        private bool FinishReadContentAsBinary()
        {
            Debug.Assert(_state == State.ReadContentAsBase64 || _state == State.ReadContentAsBinHex);
 
            byte[] bytes = new byte[256];
            if (_state == State.ReadContentAsBase64)
            {
                while (reader.ReadContentAsBase64(bytes, 0, 256) > 0) ;
            }
            else
            {
                while (reader.ReadContentAsBinHex(bytes, 0, 256) > 0) ;
            }
 
            _state = State.Interactive;
            ProcessNamespaces();
 
            // check eof
            if (reader.Depth == _initialDepth)
            {
                _state = State.EndOfFile;
                SetEmptyNode();
                return false;
            }
            return true;
        }
 
        private bool InAttributeActiveState
        {
            get
            {
#if DEBUG
                Debug.Assert(0 == (AttributeActiveStates & (1 << (int)State.Initial)));
                Debug.Assert(0 != (AttributeActiveStates & (1 << (int)State.Interactive)));
                Debug.Assert(0 == (AttributeActiveStates & (1 << (int)State.Error)));
                Debug.Assert(0 == (AttributeActiveStates & (1 << (int)State.EndOfFile)));
                Debug.Assert(0 == (AttributeActiveStates & (1 << (int)State.Closed)));
                Debug.Assert(0 != (AttributeActiveStates & (1 << (int)State.PopNamespaceScope)));
                Debug.Assert(0 != (AttributeActiveStates & (1 << (int)State.ClearNsAttributes)));
                Debug.Assert(0 == (AttributeActiveStates & (1 << (int)State.ReadElementContentAsBase64)));
                Debug.Assert(0 == (AttributeActiveStates & (1 << (int)State.ReadElementContentAsBinHex)));
                Debug.Assert(0 == (AttributeActiveStates & (1 << (int)State.ReadContentAsBase64)));
                Debug.Assert(0 == (AttributeActiveStates & (1 << (int)State.ReadContentAsBinHex)));
#endif
                return 0 != (AttributeActiveStates & (1 << (int)_state));
            }
        }
 
        private bool InNamespaceActiveState
        {
            get
            {
#if DEBUG
                Debug.Assert(0 == (NamespaceActiveStates & (1 << (int)State.Initial)));
                Debug.Assert(0 != (NamespaceActiveStates & (1 << (int)State.Interactive)));
                Debug.Assert(0 == (NamespaceActiveStates & (1 << (int)State.Error)));
                Debug.Assert(0 == (NamespaceActiveStates & (1 << (int)State.EndOfFile)));
                Debug.Assert(0 == (NamespaceActiveStates & (1 << (int)State.Closed)));
                Debug.Assert(0 != (NamespaceActiveStates & (1 << (int)State.PopNamespaceScope)));
                Debug.Assert(0 != (NamespaceActiveStates & (1 << (int)State.ClearNsAttributes)));
                Debug.Assert(0 != (NamespaceActiveStates & (1 << (int)State.ReadElementContentAsBase64)));
                Debug.Assert(0 != (NamespaceActiveStates & (1 << (int)State.ReadElementContentAsBinHex)));
                Debug.Assert(0 != (NamespaceActiveStates & (1 << (int)State.ReadContentAsBase64)));
                Debug.Assert(0 != (NamespaceActiveStates & (1 << (int)State.ReadContentAsBinHex)));
#endif
                return 0 != (NamespaceActiveStates & (1 << (int)_state));
            }
        }
 
        private void SetEmptyNode()
        {
            Debug.Assert(_tmpNode.localName == string.Empty && _tmpNode.prefix == string.Empty && _tmpNode.name == string.Empty && _tmpNode.namespaceUri == string.Empty);
            _tmpNode.type = XmlNodeType.None;
            _tmpNode.value = string.Empty;
 
            _curNode = _tmpNode;
            _useCurNode = true;
        }
 
        private void SetCurrentNode(NodeData node)
        {
            _curNode = node;
            _useCurNode = true;
        }
 
        private void InitReadContentAsType(string methodName)
        {
            switch (_state)
            {
                case State.Initial:
                case State.EndOfFile:
                case State.Closed:
                case State.Error:
                    throw new InvalidOperationException(SR.Xml_ClosedOrErrorReader);
 
                case State.Interactive:
                    return;
 
                case State.PopNamespaceScope:
                case State.ClearNsAttributes:
                    // no need to clean ns attributes or pop scope because the reader when ReadContentAs is called
                    // - on Element errors
                    // - on Attribute does not move
                    // - on EndElement does not move
                    // and that's all where State.ClearNsAttributes or State.PopNamespacScope can be set
                    return;
 
                case State.ReadElementContentAsBase64:
                case State.ReadElementContentAsBinHex:
                case State.ReadContentAsBase64:
                case State.ReadContentAsBinHex:
                    throw new InvalidOperationException(SR.Xml_MixingReadValueChunkWithBinary);
 
                default:
                    Debug.Fail($"Unexpected state {_state}");
                    break;
            }
            throw CreateReadContentAsException(methodName);
        }
 
        private void FinishReadContentAsType()
        {
            Debug.Assert(_state == State.Interactive ||
                          _state == State.PopNamespaceScope ||
                          _state == State.ClearNsAttributes);
 
            switch (NodeType)
            {
                case XmlNodeType.Element:
                    // new element we moved to - process namespaces
                    ProcessNamespaces();
                    break;
                case XmlNodeType.EndElement:
                    // end element we've stayed on or have been moved to
                    _state = State.PopNamespaceScope;
                    break;
                case XmlNodeType.Attribute:
                    // stayed on attribute, do nothing
                    break;
            }
        }
 
        private static void CheckBuffer(Array buffer, int index, int count)
        {
            ArgumentNullException.ThrowIfNull(buffer);
 
            ArgumentOutOfRangeException.ThrowIfNegative(count);
            ArgumentOutOfRangeException.ThrowIfNegative(index);
            ArgumentOutOfRangeException.ThrowIfGreaterThan(count, buffer.Length - index);
        }
    }
}