| File: DocumentationComments\XmlDocumentationCommentTextReader.XmlStream.cs | Web Access |
| Project: src\roslyn\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(); } } } }