File: DocumentationComments\XmlDocumentationCommentTextReader.XmlStream.cs
Web Access
Project: src\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System;
using System.Diagnostics;
using System.IO;
using System.Xml;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis
{
    internal partial class XmlDocumentationCommentTextReader
    {
        internal sealed class Reader : TextReader
        {
            /// <summary>
            /// Current text to validate.
            /// </summary>
            private string _text;
 
            private int _position;
 
            /// <summary>
            /// We use <see cref="XmlReader"/> to validate XML doc comments. Unfortunately it cannot be reset and thus can't be pooled. 
            /// Each time we need to validate a fragment of XML we "append" it to the underlying text reader, implemented by this class, 
            /// and advance the reader. By the end of the fragment validation, we keep the reader open in a state 
            /// that is ready for the next fragment validation unless the fragment was invalid, in which case we need to create a new XmlReader.
            /// That is why <see cref="Read(char[], int, int) "/> pretends that the stream has extra <see cref="maxReadsPastTheEnd"/> spaces
            /// at the end. That should be sufficient for <see cref="XmlReader"/> to not reach the end of this reader before the next 
            /// fragment is appended, unless the current fragment is malformed in one way or another. 
            /// </summary>
            private const int maxReadsPastTheEnd = 100;
            private int _readsPastTheEnd;
 
            // Base the root element name on a GUID to avoid accidental (or intentional) collisions. An underscore is
            // prefixed because element names must not start with a number.
            private static readonly string s_rootElementName = "_" + Guid.NewGuid().ToString("N");
            private static readonly string s_currentElementName = "_" + Guid.NewGuid().ToString("N");
 
            // internal for testing
            internal static readonly string RootStart = "<" + s_rootElementName + ">";
            internal static readonly string CurrentStart = "<" + s_currentElementName + ">";
            internal static readonly string CurrentEnd = "</" + s_currentElementName + ">";
 
            public void Reset()
            {
                _text = null;
                _position = 0;
                _readsPastTheEnd = 0;
            }
 
            public void SetText(string text)
            {
                _text = text;
                _readsPastTheEnd = 0;
 
                // The first read shall read the <root>, 
                // the subsequents reads shall start with <current> element
                if (_position > 0)
                {
                    _position = RootStart.Length;
                }
            }
 
            // for testing
            internal int Position
            {
                get { return _position; }
            }
 
            public static bool ReachedEnd(XmlReader reader)
            {
                return reader.Depth == 1
                    && reader.NodeType == XmlNodeType.EndElement
                    && reader.Name == s_currentElementName;
            }
 
            public bool Eof
            {
                get
                {
                    return _readsPastTheEnd >= maxReadsPastTheEnd;
                }
            }
 
            public override int Read(char[] buffer, int index, int count)
            {
                if (count == 0 || Eof)
                {
                    return 0;
                }
 
                // The stream synthesizes an XML document with:
                // 1. A root element start tag
                // 2. Current element start tag
                // 3. The user text (xml fragments)
                // 4. Current element end tag
 
                int initialCount = count;
 
                // <root>
                _position += EncodeAndAdvance(RootStart, _position, buffer, ref index, ref count);
 
                // <current>
                _position += EncodeAndAdvance(CurrentStart, _position - RootStart.Length, buffer, ref index, ref count);
 
                // text
                _position += EncodeAndAdvance(_text, _position - RootStart.Length - CurrentStart.Length, buffer, ref index, ref count);
 
                // </current>
                _position += EncodeAndAdvance(CurrentEnd, _position - RootStart.Length - CurrentStart.Length - _text.Length, buffer, ref index, ref count);
 
                // Pretend that the stream doesn't end right away
                if (initialCount == count)
                {
                    _readsPastTheEnd++;
                    buffer[index] = ' ';
                    count--;
                }
 
                return initialCount - count;
            }
 
            private static int EncodeAndAdvance(string src, int srcIndex, char[] dest, ref int destIndex, ref int destCount)
            {
                if (destCount == 0 || srcIndex < 0 || srcIndex >= src.Length)
                {
                    return 0;
                }
 
                int charCount = Math.Min(src.Length - srcIndex, destCount);
                Debug.Assert(charCount > 0);
                src.CopyTo(srcIndex, dest, destIndex, charCount);
 
                destIndex += charCount;
                destCount -= charCount;
                Debug.Assert(destCount >= 0);
                return charCount;
            }
 
            public override int Read()
            {
                // XmlReader does not call this API
                throw ExceptionUtilities.Unreachable();
            }
 
            public override int Peek()
            {
                // XmlReader does not call this API
                throw ExceptionUtilities.Unreachable();
            }
        }
    }
}