File: System\Xml\Core\XmlSubtreeReaderAsync.cs
Project: ndp\fx\src\Xml\System.Xml.csproj (System.Xml)

using System;
using System.Xml;
using System.Diagnostics;
using System.Collections;
using System.Globalization;
using System.Collections.Generic;
 
using System.Threading.Tasks;
 
namespace System.Xml {
 
    internal sealed partial class XmlSubtreeReader : XmlWrappingReader, IXmlLineInfo, IXmlNamespaceResolver {
 
        public override Task<string> GetValueAsync() {
            if (useCurNode) {
                return Task.FromResult(curNode.value);
            }
            else {
                return reader.GetValueAsync();
            }
        }
 
        public override async Task< bool > ReadAsync() {
            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 ( await reader.ReadAsync().ConfigureAwait(false) ) {
                        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 ( !await FinishReadElementContentAsBinaryAsync().ConfigureAwait(false) ) {
                        return false;
                    }
                    return await ReadAsync().ConfigureAwait(false);
 
                case State.ReadContentAsBase64:
                case State.ReadContentAsBinHex:
                    if ( !await FinishReadContentAsBinaryAsync().ConfigureAwait(false) ) {
                        return false;
                    }
                    return await ReadAsync().ConfigureAwait(false);
 
                default:
                    Debug.Assert( false );
                    return false;
            }
        }
 
        public override async Task SkipAsync() {
            switch ( state ) {
                case State.Initial:
                    await ReadAsync().ConfigureAwait(false);
                    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 ( await reader.ReadAsync().ConfigureAwait(false) ) {
                                while ( reader.NodeType != XmlNodeType.EndElement && reader.Depth > initialDepth ) {
                                    await reader.SkipAsync().ConfigureAwait(false);
                                }
                            }
                        }
                        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();
                    }
                    await reader.SkipAsync().ConfigureAwait(false);
                    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 ( await FinishReadElementContentAsBinaryAsync().ConfigureAwait(false) ) {
                        await SkipAsync().ConfigureAwait(false);
                    }
                    break;
 
                case State.ReadContentAsBase64:
                case State.ReadContentAsBinHex:
                    if ( await FinishReadContentAsBinaryAsync().ConfigureAwait(false) ) {
                        await SkipAsync().ConfigureAwait(false);
                    }
                    break;
 
                case State.Error:
                    return;
 
                default:
                    Debug.Assert( false );
                    return;
            }
        }
 
        public override async Task< object > ReadContentAsObjectAsync() {
            try {
                InitReadContentAsType( "ReadContentAsObject" );
                object value = await reader.ReadContentAsObjectAsync().ConfigureAwait(false);
                FinishReadContentAsType();
                return value;
            }
            catch {
                state = State.Error;
                throw;
            }
        }
 
        public override async Task< string > ReadContentAsStringAsync() {
            try {
                InitReadContentAsType( "ReadContentAsString" );
                string value = await reader.ReadContentAsStringAsync().ConfigureAwait(false);
                FinishReadContentAsType();
                return value;
            }
            catch {
                state = State.Error;
                throw;
            }
        }
 
        public override async Task< object > ReadContentAsAsync( Type returnType, IXmlNamespaceResolver namespaceResolver ) {
            try {
                InitReadContentAsType( "ReadContentAs" );
                object value = await reader.ReadContentAsAsync( returnType, namespaceResolver ).ConfigureAwait(false);
                FinishReadContentAsType();
                return value;
            }
            catch {
                state = State.Error;
                throw;
            }
        }
 
        public override async Task< int > ReadContentAsBase64Async( 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( "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;
                                }
                                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 await reader.ReadContentAsBase64Async( buffer, index, count ).ConfigureAwait(false);
                        default:
                            Debug.Assert( false );
                            return 0;
                    }
 
                case State.Interactive:
                    state = State.ReadContentAsBase64;
                    goto case State.ReadContentAsBase64;
 
                case State.ReadContentAsBase64:
                    int read = await reader.ReadContentAsBase64Async( buffer, index, count ).ConfigureAwait(false);
                    if ( read == 0 ) {
                        state = State.Interactive;
                        ProcessNamespaces();
                    }
                    return read;
 
                case State.ReadContentAsBinHex:
                case State.ReadElementContentAsBase64:
                case State.ReadElementContentAsBinHex:
                    throw new InvalidOperationException( Res.GetString( Res.Xml_MixingBinaryContentMethods ) );
 
                default:
                    Debug.Assert( false );
                    return 0;
            }
        }
 
        public override async Task< int > ReadElementContentAsBase64Async( 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 ( !await InitReadElementContentAsBinaryAsync( State.ReadElementContentAsBase64 ).ConfigureAwait(false) ) {
                        return 0;
                    }
                    goto case State.ReadElementContentAsBase64;
 
                case State.ReadElementContentAsBase64:
                    int read = await reader.ReadContentAsBase64Async( buffer, index, count ).ConfigureAwait(false);
                    if ( read > 0 || count == 0 ) {
                        return read;
                    }
                    if ( NodeType != XmlNodeType.EndElement ) {
                        throw new XmlException(Res.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 {
                        await ReadAsync().ConfigureAwait(false);
                    }
                    return 0;
 
                case State.ReadContentAsBase64:
                case State.ReadContentAsBinHex:
                case State.ReadElementContentAsBinHex:
                    throw new InvalidOperationException( Res.GetString( Res.Xml_MixingBinaryContentMethods ) );
 
                default:
                    Debug.Assert( false );
                    return 0;
            }
        }
 
        public override async Task< int > ReadContentAsBinHexAsync( 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( "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;
                                }
                                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 await reader.ReadContentAsBinHexAsync( buffer, index, count ).ConfigureAwait(false);
                        default:
                            Debug.Assert( false );
                            return 0;
                    }
 
                case State.Interactive:
                    state = State.ReadContentAsBinHex;
                    goto case State.ReadContentAsBinHex;
 
                case State.ReadContentAsBinHex:
                    int read = await reader.ReadContentAsBinHexAsync( buffer, index, count ).ConfigureAwait(false);
                    if ( read == 0 ) {
                        state = State.Interactive;
                        ProcessNamespaces();
                    }
                    return read;
 
                case State.ReadContentAsBase64:
                case State.ReadElementContentAsBase64:
                case State.ReadElementContentAsBinHex:
                    throw new InvalidOperationException( Res.GetString( Res.Xml_MixingBinaryContentMethods ) );
 
                default:
                    Debug.Assert( false );
                    return 0;
            }
        }
 
        public override async Task< int > ReadElementContentAsBinHexAsync( 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 ( !await InitReadElementContentAsBinaryAsync( State.ReadElementContentAsBinHex ).ConfigureAwait(false) ) {
                        return 0;
                    }
                    goto case State.ReadElementContentAsBinHex;
                case State.ReadElementContentAsBinHex:
                    int read = await reader.ReadContentAsBinHexAsync( buffer, index, count ).ConfigureAwait(false);
                    if ( read > 0  || count == 0 ) {
                        return read;
                    }
                    if ( NodeType != XmlNodeType.EndElement ) {
                        throw new XmlException(Res.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 {
                        await ReadAsync().ConfigureAwait(false);
                    }
                    return 0;
 
                case State.ReadContentAsBase64:
                case State.ReadContentAsBinHex:
                case State.ReadElementContentAsBase64:
                    throw new InvalidOperationException( Res.GetString( Res.Xml_MixingBinaryContentMethods ) );
 
                default:
                    Debug.Assert( false );
                    return 0;
            }
        }
 
        public override Task< int > ReadValueChunkAsync( char[] buffer, int index, int count ) {
            switch (state) {
                case State.Initial:
                case State.EndOfFile:
                case State.Closed:
                case State.Error:
                    return Task.FromResult(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 Task.FromResult(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.ReadValueChunkAsync(buffer, index, count);
 
                case State.ReadElementContentAsBase64:
                case State.ReadElementContentAsBinHex:
                case State.ReadContentAsBase64:
                case State.ReadContentAsBinHex:
                    throw new InvalidOperationException( Res.GetString( Res.Xml_MixingReadValueChunkWithBinary ) );
 
                default:
                    Debug.Assert( false );
                    return Task.FromResult(0);
            }
        }
 
        private async Task< bool > InitReadElementContentAsBinaryAsync( State binaryState ) {
            if ( NodeType != XmlNodeType.Element ) {
                throw reader.CreateReadElementContentAsException( "ReadElementContentAsBase64" );
            }
 
            bool isEmpty = IsEmptyElement;
 
            // move to content or off the empty element
            if ( !await ReadAsync().ConfigureAwait(false) || isEmpty ) {
                return false;
            }
            // special-case child element and end element
            switch ( NodeType ) {
                case XmlNodeType.Element:
                    throw new XmlException(Res.Xml_InvalidNodeType, reader.NodeType.ToString(), reader as IXmlLineInfo);
                case XmlNodeType.EndElement:
                    // pop scope & move off end element
                    ProcessNamespaces();
                    await ReadAsync().ConfigureAwait(false);
                    return false;
            }
 
            Debug.Assert( state == State.Interactive );
            state = binaryState;
            return true;
        }
 
        private async Task< bool > FinishReadElementContentAsBinaryAsync() {
            Debug.Assert( state == State.ReadElementContentAsBase64 || state == State.ReadElementContentAsBinHex );
 
            byte[] bytes = new byte[256];
            if ( state == State.ReadElementContentAsBase64 ) {
                while ( await reader.ReadContentAsBase64Async( bytes, 0, 256 ).ConfigureAwait(false) > 0 ) ;
            }
            else {
                while ( await reader.ReadContentAsBinHexAsync( bytes, 0, 256 ).ConfigureAwait(false) > 0 ) ;
            }
 
            if ( NodeType != XmlNodeType.EndElement ) {
                throw new XmlException(Res.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 await ReadAsync().ConfigureAwait(false);
        }
 
        private async Task< bool > FinishReadContentAsBinaryAsync() {
            Debug.Assert( state == State.ReadContentAsBase64 || state == State.ReadContentAsBinHex );
 
            byte[] bytes = new byte[256];
            if ( state == State.ReadContentAsBase64 ) {
                while ( await reader.ReadContentAsBase64Async( bytes, 0, 256 ).ConfigureAwait(false) > 0 ) ;
            }
            else {
                while ( await reader.ReadContentAsBinHexAsync( bytes, 0, 256 ).ConfigureAwait(false) > 0 ) ;
            }
 
            state = State.Interactive;
            ProcessNamespaces();
 
            // check eof
            if ( reader.Depth == initialDepth ) {
                 state = State.EndOfFile;
                 SetEmptyNode();
                 return false;
            }
            return true;
        }
 
    }
}