File: System\Xml\Core\XmlEventCache.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.Generic;
using System.Diagnostics;
using System.Text;
using System.Xml.Schema;
using System.Xml.Xsl.Runtime;
 
namespace System.Xml
{
    /// <summary>
    /// Caches sequence of XmlEvents so that they can be replayed later.
    /// </summary>
    internal sealed class XmlEventCache : XmlRawWriter
    {
        private List<XmlEvent[]>? _pages;     // All event pages
        private XmlEvent[]? _pageCurr;        // Page that is currently being built
        private int _pageSize;               // Number of events in pageCurr
        private readonly bool _hasRootNode;           // True if the cached document has a root node, false if it's a fragment
        private StringConcat _singleText;    // If document consists of a single text node, cache it here rather than creating pages
        private readonly string _baseUri;             // Base Uri of document
 
        private enum XmlEventType
        {
            Unknown = 0,
            DocType,
            StartElem,
            StartAttr,
            EndAttr,
            CData,
            Comment,
            PI,
            Whitespace,
            String,
            Raw,
            EntRef,
            CharEnt,
            SurrCharEnt,
            Base64,
            BinHex,
            XmlDecl1,
            XmlDecl2,
            StartContent,
            EndElem,
            FullEndElem,
            Nmsp,
            EndBase64,
            Close,
            Flush,
            Dispose,
        }
 
#if DEBUG
        private const int InitialPageSize = 4;
#else
        private const int InitialPageSize = 32;
#endif
 
        public XmlEventCache(string baseUri, bool hasRootNode)
        {
            _baseUri = baseUri;
            _hasRootNode = hasRootNode;
        }
 
        public void EndEvents()
        {
            if (_singleText.Count == 0)
                AddEvent(XmlEventType.Unknown);
        }
 
 
        //-----------------------------------------------
        // XmlEventCache methods
        //-----------------------------------------------
 
        /// <summary>
        /// Return Base Uri of the document.
        /// </summary>
        public string BaseUri
        {
            get { return _baseUri; }
        }
 
        /// <summary>
        /// Return true if the cached document has a root node, false if it's a fragment.
        /// </summary>
        public bool HasRootNode
        {
            get { return _hasRootNode; }
        }
 
        /// <summary>
        /// Replay all cached events to an XmlWriter.
        /// </summary>
        public void EventsToWriter(XmlWriter writer)
        {
            XmlEvent[] page;
            int idxPage, idxEvent;
            byte[] bytes;
            char[] chars;
            XmlRawWriter? rawWriter;
 
            // Special-case single text node at the top-level
            if (_singleText.Count != 0)
            {
                writer.WriteString(_singleText.GetResult());
                return;
            }
 
            rawWriter = writer as XmlRawWriter;
 
            // Loop over set of pages
            for (idxPage = 0; idxPage < _pages!.Count; idxPage++)
            {
                page = _pages[idxPage];
 
                // Loop over events in each page
                for (idxEvent = 0; idxEvent < page.Length; idxEvent++)
                {
                    switch (page[idxEvent].EventType)
                    {
                        case XmlEventType.Unknown:
                            // No more events
                            Debug.Assert(idxPage + 1 == _pages.Count);
                            return;
 
                        case XmlEventType.DocType:
                            writer.WriteDocType(page[idxEvent].String1!, page[idxEvent].String2, page[idxEvent].String3, (string?)page[idxEvent].Object);
                            break;
 
                        case XmlEventType.StartElem:
                            writer.WriteStartElement(page[idxEvent].String1, page[idxEvent].String2!, page[idxEvent].String3);
                            break;
 
                        case XmlEventType.StartAttr:
                            writer.WriteStartAttribute(page[idxEvent].String1, page[idxEvent].String2!, page[idxEvent].String3);
                            break;
 
                        case XmlEventType.EndAttr:
                            writer.WriteEndAttribute();
                            break;
 
                        case XmlEventType.CData:
                            writer.WriteCData(page[idxEvent].String1);
                            break;
 
                        case XmlEventType.Comment:
                            writer.WriteComment(page[idxEvent].String1);
                            break;
 
                        case XmlEventType.PI:
                            writer.WriteProcessingInstruction(page[idxEvent].String1!, page[idxEvent].String2);
                            break;
 
                        case XmlEventType.Whitespace:
                            writer.WriteWhitespace(page[idxEvent].String1);
                            break;
 
                        case XmlEventType.String:
                            writer.WriteString(page[idxEvent].String1);
                            break;
 
                        case XmlEventType.Raw:
                            writer.WriteRaw(page[idxEvent].String1!);
                            break;
 
                        case XmlEventType.EntRef:
                            writer.WriteEntityRef(page[idxEvent].String1!);
                            break;
 
                        case XmlEventType.CharEnt:
                            writer.WriteCharEntity((char)page[idxEvent].Object!);
                            break;
 
                        case XmlEventType.SurrCharEnt:
                            chars = (char[])page[idxEvent].Object!;
                            writer.WriteSurrogateCharEntity(chars[0], chars[1]);
                            break;
 
                        case XmlEventType.Base64:
                            bytes = (byte[])page[idxEvent].Object!;
                            writer.WriteBase64(bytes, 0, bytes.Length);
                            break;
 
                        case XmlEventType.BinHex:
                            bytes = (byte[])page[idxEvent].Object!;
                            writer.WriteBinHex(bytes, 0, bytes.Length);
                            break;
 
                        case XmlEventType.XmlDecl1:
                            rawWriter?.WriteXmlDeclaration((XmlStandalone)page[idxEvent].Object!);
                            break;
 
                        case XmlEventType.XmlDecl2:
                            rawWriter?.WriteXmlDeclaration(page[idxEvent].String1!);
                            break;
 
                        case XmlEventType.StartContent:
                            rawWriter?.StartElementContent();
                            break;
 
                        case XmlEventType.EndElem:
                            if (rawWriter != null)
                                rawWriter.WriteEndElement(page[idxEvent].String1!, page[idxEvent].String2!, page[idxEvent].String3!);
                            else
                                writer.WriteEndElement();
                            break;
 
                        case XmlEventType.FullEndElem:
                            if (rawWriter != null)
                                rawWriter.WriteFullEndElement(page[idxEvent].String1!, page[idxEvent].String2!, page[idxEvent].String3!);
                            else
                                writer.WriteFullEndElement();
                            break;
 
                        case XmlEventType.Nmsp:
                            if (rawWriter != null)
                                rawWriter.WriteNamespaceDeclaration(page[idxEvent].String1!, page[idxEvent].String2!);
                            else
                                writer.WriteAttributeString("xmlns", page[idxEvent].String1!, XmlReservedNs.NsXmlNs, page[idxEvent].String2);
                            break;
 
                        case XmlEventType.EndBase64:
                            rawWriter?.WriteEndBase64();
                            break;
 
                        case XmlEventType.Close:
                            writer.Close();
                            break;
 
                        case XmlEventType.Flush:
                            writer.Flush();
                            break;
 
                        case XmlEventType.Dispose:
                            ((IDisposable)writer).Dispose();
                            break;
 
                        default:
                            Debug.Fail($"Unknown event: {page[idxEvent].EventType}");
                            break;
                    }
                }
            }
 
            Debug.Fail("Unknown event should be added to end of event sequence.");
        }
 
        /// <summary>
        /// Concatenate all element text and atomic value events and return the resulting string.
        /// </summary>
        public string EventsToString()
        {
            StringBuilder bldr;
            XmlEvent[] page;
            int idxPage, idxEvent;
            bool inAttr;
 
            // Special-case single text node at the top-level
            if (_singleText.Count != 0)
                return _singleText.GetResult();
 
            bldr = new StringBuilder();
 
            // Loop over set of pages
            inAttr = false;
            for (idxPage = 0; idxPage < _pages!.Count; idxPage++)
            {
                page = _pages[idxPage];
 
                // Loop over events in each page
                for (idxEvent = 0; idxEvent < page.Length; idxEvent++)
                {
                    switch (page[idxEvent].EventType)
                    {
                        case XmlEventType.Unknown:
                            // No more events
                            Debug.Assert(idxPage + 1 == _pages.Count);
                            return bldr.ToString();
 
                        case XmlEventType.String:
                        case XmlEventType.Whitespace:
                        case XmlEventType.Raw:
                        case XmlEventType.CData:
                            // Append text
                            if (!inAttr)
                                bldr.Append(page[idxEvent].String1);
                            break;
 
                        case XmlEventType.StartAttr:
                            // Don't append text or atomic values if they appear within attributes
                            inAttr = true;
                            break;
 
                        case XmlEventType.EndAttr:
                            // No longer in an attribute
                            inAttr = false;
                            break;
                    }
                }
            }
 
            Debug.Fail("Unknown event should be added to end of event sequence.");
            return string.Empty;
        }
 
 
        //-----------------------------------------------
        // XmlWriter interface
        //-----------------------------------------------
 
        public override XmlWriterSettings? Settings
        {
            get { return null; }
        }
 
        public override void WriteDocType(string name, string? pubid, string? sysid, string? subset)
        {
            AddEvent(XmlEventType.DocType, name, pubid, sysid, subset);
        }
 
        public override void WriteStartElement(string? prefix, string localName, string? ns)
        {
            AddEvent(XmlEventType.StartElem, prefix, localName, ns);
        }
 
        public override void WriteStartAttribute(string? prefix, string localName, string? ns)
        {
            AddEvent(XmlEventType.StartAttr, prefix, localName, ns);
        }
 
        public override void WriteEndAttribute()
        {
            AddEvent(XmlEventType.EndAttr);
        }
 
        public override void WriteCData(string? text)
        {
            AddEvent(XmlEventType.CData, text);
        }
 
        public override void WriteComment(string? text)
        {
            AddEvent(XmlEventType.Comment, text);
        }
 
        public override void WriteProcessingInstruction(string name, string? text)
        {
            AddEvent(XmlEventType.PI, name, text);
        }
 
        public override void WriteWhitespace(string? ws)
        {
            AddEvent(XmlEventType.Whitespace, ws);
        }
 
        public override void WriteString(string? text)
        {
            // Special-case single text node at the top level
            if (_pages == null)
            {
                _singleText.ConcatNoDelimiter(text);
            }
            else
            {
                AddEvent(XmlEventType.String, text);
            }
        }
 
        public override void WriteChars(char[] buffer, int index, int count)
        {
            WriteString(new string(buffer, index, count));
        }
 
        public override void WriteRaw(char[] buffer, int index, int count)
        {
            WriteRaw(new string(buffer, index, count));
        }
 
        public override void WriteRaw(string data)
        {
            AddEvent(XmlEventType.Raw, data);
        }
 
        public override void WriteEntityRef(string name)
        {
            AddEvent(XmlEventType.EntRef, name);
        }
 
        public override void WriteCharEntity(char ch)
        {
            AddEvent(XmlEventType.CharEnt, (object)ch);
        }
 
        public override void WriteSurrogateCharEntity(char lowChar, char highChar)
        {
            // Save high and low characters
            char[] chars = { lowChar, highChar };
            AddEvent(XmlEventType.SurrCharEnt, (object)chars);
        }
 
        public override void WriteBase64(byte[] buffer, int index, int count)
        {
            AddEvent(XmlEventType.Base64, (object)ToBytes(buffer, index, count));
        }
 
        public override void WriteBinHex(byte[] buffer, int index, int count)
        {
            AddEvent(XmlEventType.BinHex, (object)ToBytes(buffer, index, count));
        }
 
        public override void Close()
        {
            AddEvent(XmlEventType.Close);
        }
 
        public override void Flush()
        {
            AddEvent(XmlEventType.Flush);
        }
 
        /// <summary>
        /// All other WriteValue methods are implemented by XmlWriter to delegate to WriteValue(object) or WriteValue(string), so
        /// only these two methods need to be implemented.
        /// </summary>
        public override void WriteValue(object value)
        {
            WriteString(XmlUntypedConverter.Untyped.ToString(value, this._resolver));
        }
 
        public override void WriteValue(string? value)
        {
            WriteString(value);
        }
 
        protected override void Dispose(bool disposing)
        {
            try
            {
                if (disposing)
                {
                    AddEvent(XmlEventType.Dispose);
                }
            }
            finally
            {
                base.Dispose(disposing);
            }
        }
 
 
        //-----------------------------------------------
        // XmlRawWriter interface
        //-----------------------------------------------
 
        internal override void WriteXmlDeclaration(XmlStandalone standalone)
        {
            AddEvent(XmlEventType.XmlDecl1, (object)standalone);
        }
 
        internal override void WriteXmlDeclaration(string xmldecl)
        {
            AddEvent(XmlEventType.XmlDecl2, xmldecl);
        }
 
        internal override void StartElementContent()
        {
            AddEvent(XmlEventType.StartContent);
        }
 
        internal override void WriteEndElement(string prefix, string localName, string ns)
        {
            AddEvent(XmlEventType.EndElem, prefix, localName, ns);
        }
 
        internal override void WriteFullEndElement(string prefix, string localName, string ns)
        {
            AddEvent(XmlEventType.FullEndElem, prefix, localName, ns);
        }
 
        internal override void WriteNamespaceDeclaration(string prefix, string ns)
        {
            AddEvent(XmlEventType.Nmsp, prefix, ns);
        }
 
        internal override void WriteEndBase64()
        {
            AddEvent(XmlEventType.EndBase64);
        }
 
 
        //-----------------------------------------------
        // Helper methods
        //-----------------------------------------------
 
        private void AddEvent(XmlEventType eventType)
        {
            int idx = NewEvent();
            _pageCurr![idx].InitEvent(eventType);
        }
 
        private void AddEvent(XmlEventType eventType, string? s1)
        {
            int idx = NewEvent();
            _pageCurr![idx].InitEvent(eventType, s1);
        }
 
        private void AddEvent(XmlEventType eventType, string? s1, string? s2)
        {
            int idx = NewEvent();
            _pageCurr![idx].InitEvent(eventType, s1, s2);
        }
 
        private void AddEvent(XmlEventType eventType, string? s1, string? s2, string? s3)
        {
            int idx = NewEvent();
            _pageCurr![idx].InitEvent(eventType, s1, s2, s3);
        }
 
        private void AddEvent(XmlEventType eventType, string? s1, string? s2, string? s3, object? o)
        {
            int idx = NewEvent();
            _pageCurr![idx].InitEvent(eventType, s1, s2, s3, o);
        }
 
        private void AddEvent(XmlEventType eventType, object? o)
        {
            int idx = NewEvent();
            _pageCurr![idx].InitEvent(eventType, o);
        }
 
        private int NewEvent()
        {
            if (_pages == null)
            {
                _pages = new List<XmlEvent[]>();
                _pageCurr = new XmlEvent[InitialPageSize];
                _pages.Add(_pageCurr);
 
                if (_singleText.Count != 0)
                {
                    // Review: There is no need to concatenate the strings here
                    _pageCurr[0].InitEvent(XmlEventType.String, _singleText.GetResult());
                    _pageSize++;
                    _singleText.Clear();
                }
            }
            else if (_pageSize >= _pageCurr!.Length)
            {
                // Create new page
                _pageCurr = new XmlEvent[_pageSize * 2];
                _pages.Add(_pageCurr);
                _pageSize = 0;
            }
 
            return _pageSize++;
        }
 
        /// <summary>
        /// Create a standalone buffer that doesn't need an index or count passed along with it.
        /// </summary>
        private static byte[] ToBytes(byte[] buffer, int index, int count)
        {
            if (index != 0 || count != buffer.Length)
            {
                if (buffer.Length - index > count)
                    count = buffer.Length - index;
 
                byte[] bufferNew = new byte[count];
                Array.Copy(buffer, index, bufferNew, 0, count);
 
                return bufferNew;
            }
 
            return buffer;
        }
 
 
        /// <summary>
        /// Caches information for XML events like BeginElement, String, and EndAttribute so that they can be replayed later.
        /// </summary>
        private struct XmlEvent
        {
            private XmlEventType _eventType;
            private string? _s1;
            private string? _s2;
            private string? _s3;
            private object? _o;
 
            public void InitEvent(XmlEventType eventType)
            {
                _eventType = eventType;
            }
 
            public void InitEvent(XmlEventType eventType, string? s1)
            {
                _eventType = eventType;
                _s1 = s1;
            }
 
            public void InitEvent(XmlEventType eventType, string? s1, string? s2)
            {
                _eventType = eventType;
                _s1 = s1;
                _s2 = s2;
            }
 
            public void InitEvent(XmlEventType eventType, string? s1, string? s2, string? s3)
            {
                _eventType = eventType;
                _s1 = s1;
                _s2 = s2;
                _s3 = s3;
            }
 
            public void InitEvent(XmlEventType eventType, string? s1, string? s2, string? s3, object? o)
            {
                _eventType = eventType;
                _s1 = s1;
                _s2 = s2;
                _s3 = s3;
                _o = o;
            }
 
            public void InitEvent(XmlEventType eventType, object? o)
            {
                _eventType = eventType;
                _o = o;
            }
 
            public XmlEventType EventType
            {
                get { return _eventType; }
            }
 
            public string? String1
            {
                get { return _s1; }
            }
 
            public string? String2
            {
                get { return _s2; }
            }
 
            public string? String3
            {
                get { return _s3; }
            }
 
            public object? Object
            {
                get { return _o; }
            }
        }
    }
}