File: System\Xml\Linq\XContainer.cs
Web Access
Project: src\src\libraries\System.Private.Xml.Linq\src\System.Private.Xml.Linq.csproj (System.Private.Xml.Linq)
// 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.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Debug = System.Diagnostics.Debug;
using IEnumerable = System.Collections.IEnumerable;
using Interlocked = System.Threading.Interlocked;
using StringBuilder = System.Text.StringBuilder;
 
namespace System.Xml.Linq
{
    /// <summary>
    /// Represents a node that can contain other nodes.
    /// </summary>
    /// <remarks>
    /// The two classes that derive from <see cref="XContainer"/> are
    /// <see cref="XDocument"/> and <see cref="XElement"/>.
    /// </remarks>
    public abstract class XContainer : XNode
    {
        internal object? content;
 
        internal XContainer() { }
 
        internal XContainer(XContainer other)
        {
            ArgumentNullException.ThrowIfNull(other);
 
            if (other.content is string)
            {
                this.content = other.content;
            }
            else
            {
                XNode? n = (XNode?)other.content;
                if (n != null)
                {
                    do
                    {
                        n = n.next!;
                        AppendNodeSkipNotify(n.CloneNode());
                    } while (n != other.content);
                }
            }
        }
 
        /// <summary>
        /// Get the first child node of this node.
        /// </summary>
        public XNode? FirstNode
        {
            get
            {
                return LastNode?.next;
            }
        }
 
        /// <summary>
        /// Get the last child node of this node.
        /// </summary>
        public XNode? LastNode
        {
            get
            {
                if (content == null) return null;
                XNode? n = content as XNode;
                if (n != null) return n;
                string? s = content as string;
                if (s != null)
                {
                    if (s.Length == 0) return null;
                    XText t = new XText(s);
                    t.parent = this;
                    t.next = t;
                    Interlocked.CompareExchange<object>(ref content, t, s);
                }
                return (XNode)content;
            }
        }
 
        /// <overloads>
        /// Adds the specified content as a child (or as children) to this <see cref="XContainer"/>. The
        /// content can be simple content, a collection of content objects, a parameter list
        /// of content objects, or null.
        /// </overloads>
        /// <summary>
        /// Adds the specified content as a child (or children) of this <see cref="XContainer"/>.
        /// </summary>
        /// <param name="content">
        /// A content object containing simple content or a collection of content objects
        /// to be added.
        /// </param>
        /// <remarks>
        /// When adding simple content, a number of types may be passed to this method.
        /// Valid types include:
        /// <list>
        /// <item>string</item>
        /// <item>double</item>
        /// <item>float</item>
        /// <item>decimal</item>
        /// <item>bool</item>
        /// <item>DateTime</item>
        /// <item>DateTimeOffset</item>
        /// <item>TimeSpan</item>
        /// <item>Any type implementing ToString()</item>
        /// <item>Any type implementing IEnumerable</item>
        ///
        /// </list>
        /// When adding complex content, a number of types may be passed to this method.
        /// <list>
        /// <item>XObject</item>
        /// <item>XNode</item>
        /// <item>XAttribute</item>
        /// <item>Any type implementing IEnumerable</item>
        /// </list>
        ///
        /// If an object implements IEnumerable, then the collection in the object is enumerated,
        /// and all items in the collection are added. If the collection contains simple content,
        /// then the simple content in the collection is concatenated and added as a single
        /// string of simple content. If the collection contains complex content, then each item
        /// in the collection is added separately.
        ///
        /// If content is null, nothing is added. This allows the results of a query to be passed
        /// as content. If the query returns null, no contents are added, and this method does not
        /// throw a NullReferenceException.
        ///
        /// Attributes and simple content can't be added to a document.
        ///
        /// An added attribute must have a unique name within the element to
        /// which it is being added.
        /// </remarks>
        public void Add(object? content)
        {
            if (SkipNotify())
            {
                AddContentSkipNotify(content);
                return;
            }
            if (content == null) return;
            XNode? n = content as XNode;
            if (n != null)
            {
                AddNode(n);
                return;
            }
            string? s = content as string;
            if (s != null)
            {
                AddString(s);
                return;
            }
            XAttribute? a = content as XAttribute;
            if (a != null)
            {
                AddAttribute(a);
                return;
            }
            XStreamingElement? x = content as XStreamingElement;
            if (x != null)
            {
                AddNode(new XElement(x));
                return;
            }
            object?[]? o = content as object?[];
            if (o != null)
            {
                foreach (object? obj in o) Add(obj);
                return;
            }
            IEnumerable? e = content as IEnumerable;
            if (e != null)
            {
                foreach (object? obj in e) Add(obj);
                return;
            }
            AddString(GetStringValue(content));
        }
 
        /// <summary>
        /// Adds the specified content as a child (or children) of this <see cref="XContainer"/>.
        /// </summary>
        /// <param name="content">
        /// A parameter list of content objects.
        /// </param>
        /// <remarks>
        /// See XContainer.Add(object content) for details about the content that can be added
        /// using this method.
        /// </remarks>
        public void Add(params object?[] content)
        {
            Add((object)content);
        }
 
        /// <overloads>
        /// Adds the specified content as the first child (or children) of this document or element. The
        /// content can be simple content, a collection of content objects, a parameter
        /// list of content objects, or null.
        /// </overloads>
        /// <summary>
        /// Adds the specified content as the first child (or children) of this document or element.
        /// </summary>
        /// <param name="content">
        /// A content object containing simple content or a collection of content objects
        /// to be added.
        /// </param>
        /// <remarks>
        /// See <see cref="XContainer.Add(object)"/> for details about the content that can be added
        /// using this method.
        /// </remarks>
        public void AddFirst(object? content)
        {
            new Inserter(this, null).Add(content);
        }
 
        /// <summary>
        /// Adds the specified content as the first children of this document or element.
        /// </summary>
        /// <param name="content">
        /// A parameter list of content objects.
        /// </param>
        /// <remarks>
        /// See XContainer.Add(object content) for details about the content that can be added
        /// using this method.
        /// </remarks>
        /// <exception cref="InvalidOperationException">
        /// Thrown if the parent is null.
        /// </exception>
        public void AddFirst(params object?[] content)
        {
            AddFirst((object)content);
        }
 
        /// <summary>
        /// Creates an <see cref="XmlWriter"/> used to add either nodes
        /// or attributes to the <see cref="XContainer"/>. The later option
        /// applies only for <see cref="XElement"/>.
        /// </summary>
        /// <returns>An <see cref="XmlWriter"/></returns>
        public XmlWriter CreateWriter()
        {
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.ConformanceLevel = this is XDocument ? ConformanceLevel.Document : ConformanceLevel.Fragment;
            return XmlWriter.Create(new XNodeBuilder(this), settings);
        }
 
        /// <summary>
        /// Get descendant elements plus leaf nodes contained in an <see cref="XContainer"/>
        /// </summary>
        /// <returns><see cref="IEnumerable{XNode}"/> over all descendants</returns>
        public IEnumerable<XNode> DescendantNodes()
        {
            return GetDescendantNodes(false);
        }
 
        /// <summary>
        /// Returns the descendant <see cref="XElement"/>s of this <see cref="XContainer"/>.  Note this method will
        /// not return itself in the resulting IEnumerable.  See <see cref="XElement.DescendantsAndSelf()"/> if you
        /// need to include the current <see cref="XElement"/> in the results.
        /// <seealso cref="XElement.DescendantsAndSelf()"/>
        /// </summary>
        /// <returns>
        /// An IEnumerable of <see cref="XElement"/> with all of the descendants below this <see cref="XContainer"/> in the XML tree.
        /// </returns>
        public IEnumerable<XElement> Descendants()
        {
            return GetDescendants(null, false);
        }
 
        /// <summary>
        /// Returns the Descendant <see cref="XElement"/>s with the passed in <see cref="XName"/> as an IEnumerable
        /// of XElement.
        /// </summary>
        /// <param name="name">The <see cref="XName"/> to match against descendant <see cref="XElement"/>s.</param>
        /// <returns>An <see cref="IEnumerable"/> of <see cref="XElement"/></returns>
        public IEnumerable<XElement> Descendants(XName? name)
        {
            return name != null ? GetDescendants(name, false) : XElement.EmptySequence;
        }
 
        /// <summary>
        /// Returns the child element with this <see cref="XName"/> or null if there is no child element
        /// with a matching <see cref="XName"/>.
        /// <seealso cref="XContainer.Elements()"/>
        /// </summary>
        /// <param name="name">
        /// The <see cref="XName"/> to match against this <see cref="XContainer"/>s child elements.
        /// </param>
        /// <returns>
        /// An <see cref="XElement"/> child that matches the <see cref="XName"/> passed in, or null.
        /// </returns>
        public XElement? Element(XName name)
        {
            XNode? n = content as XNode;
            if (n != null)
            {
                do
                {
                    n = n.next!;
                    XElement? e = n as XElement;
                    if (e != null && e.name == name) return e;
                } while (n != content);
            }
            return null;
        }
 
        ///<overloads>
        /// Returns the child <see cref="XElement"/>s of this <see cref="XContainer"/>.
        /// </overloads>
        /// <summary>
        /// Returns all of the child elements of this <see cref="XContainer"/>.
        /// </summary>
        /// <returns>
        /// An <see cref="IEnumerable"/> over all of this <see cref="XContainer"/>'s child <see cref="XElement"/>s.
        /// </returns>
        public IEnumerable<XElement> Elements()
        {
            return GetElements(null);
        }
 
        /// <summary>
        /// Returns the child elements of this <see cref="XContainer"/> that match the <see cref="XName"/> passed in.
        /// </summary>
        /// <param name="name">
        /// The <see cref="XName"/> to match against the <see cref="XElement"/> children of this <see cref="XContainer"/>.
        /// </param>
        /// <returns>
        /// An <see cref="IEnumerable"/> of <see cref="XElement"/> children of this <see cref="XContainer"/> that have
        /// a matching <see cref="XName"/>.
        /// </returns>
        public IEnumerable<XElement> Elements(XName? name)
        {
            return name != null ? GetElements(name) : XElement.EmptySequence;
        }
 
        ///<overloads>
        /// Returns the content of this <see cref="XContainer"/>.  Note that the content does not
        /// include <see cref="XAttribute"/>s.
        /// <seealso cref="XElement.Attributes()"/>
        /// </overloads>
        /// <summary>
        /// Returns the content of this <see cref="XContainer"/> as an <see cref="IEnumerable"/> of <see cref="object"/>.  Note
        /// that the content does not include <see cref="XAttribute"/>s.
        /// <seealso cref="XElement.Attributes()"/>
        /// </summary>
        /// <returns>The contents of this <see cref="XContainer"/></returns>
        public IEnumerable<XNode> Nodes()
        {
            XNode? n = LastNode;
            if (n != null)
            {
                do
                {
                    n = n.next!;
                    yield return n;
                } while (n.parent == this && n != content);
            }
        }
 
        /// <summary>
        /// Removes the nodes from this <see cref="XContainer"/>.  Note this
        /// methods does not remove attributes.  See <see cref="XElement.RemoveAttributes()"/>.
        /// <seealso cref="XElement.RemoveAttributes()"/>
        /// </summary>
        public void RemoveNodes()
        {
            if (SkipNotify())
            {
                RemoveNodesSkipNotify();
                return;
            }
            while (content != null)
            {
                string? s = content as string;
                if (s != null)
                {
                    if (s.Length > 0)
                    {
                        ConvertTextToNode();
                    }
                    else
                    {
                        if (this is XElement)
                        {
                            // Change in the serialization of an empty element:
                            // from start/end tag pair to empty tag
                            NotifyChanging(this, XObjectChangeEventArgs.Value);
                            if ((object)s != (object)content) throw new InvalidOperationException(SR.InvalidOperation_ExternalCode);
                            content = null;
                            NotifyChanged(this, XObjectChangeEventArgs.Value);
                        }
                        else
                        {
                            content = null;
                        }
                    }
                }
                XNode? last = content as XNode;
                if (last != null)
                {
                    XNode n = last.next!;
                    NotifyChanging(n, XObjectChangeEventArgs.Remove);
                    if (last != content || n != last.next) throw new InvalidOperationException(SR.InvalidOperation_ExternalCode);
                    if (n != last)
                    {
                        last.next = n.next;
                    }
                    else
                    {
                        content = null;
                    }
                    n.parent = null;
                    n.next = null;
                    NotifyChanged(n, XObjectChangeEventArgs.Remove);
                }
            }
        }
 
        /// <overloads>
        /// Replaces the children nodes of this document or element with the specified content. The
        /// content can be simple content, a collection of content objects, a parameter
        /// list of content objects, or null.
        /// </overloads>
        /// <summary>
        /// Replaces the children nodes of this document or element with the specified content.
        /// </summary>
        /// <param name="content">
        /// A content object containing simple content or a collection of content objects
        /// that replace the children nodes.
        /// </param>
        /// <remarks>
        /// See XContainer.Add(object content) for details about the content that can be added
        /// using this method.
        /// </remarks>
        public void ReplaceNodes(object? content)
        {
            content = GetContentSnapshot(content);
            RemoveNodes();
            Add(content);
        }
 
        /// <summary>
        /// Replaces the children nodes of this document or element with the specified content.
        /// </summary>
        /// <param name="content">
        /// A parameter list of content objects.
        /// </param>
        /// <remarks>
        /// See XContainer.Add(object content) for details about the content that can be added
        /// using this method.
        /// </remarks>
        public void ReplaceNodes(params object?[] content)
        {
            ReplaceNodes((object)content);
        }
 
        internal virtual void AddAttribute(XAttribute a)
        {
        }
 
        internal virtual void AddAttributeSkipNotify(XAttribute a)
        {
        }
 
        internal void AddContentSkipNotify(object? content)
        {
            if (content == null) return;
            XNode? n = content as XNode;
            if (n != null)
            {
                AddNodeSkipNotify(n);
                return;
            }
            string? s = content as string;
            if (s != null)
            {
                AddStringSkipNotify(s);
                return;
            }
            XAttribute? a = content as XAttribute;
            if (a != null)
            {
                AddAttributeSkipNotify(a);
                return;
            }
            XStreamingElement? x = content as XStreamingElement;
            if (x != null)
            {
                AddNodeSkipNotify(new XElement(x));
                return;
            }
            object?[]? o = content as object?[];
            if (o != null)
            {
                foreach (object? obj in o) AddContentSkipNotify(obj);
                return;
            }
            IEnumerable? e = content as IEnumerable;
            if (e != null)
            {
                foreach (object? obj in e) AddContentSkipNotify(obj);
                return;
            }
            AddStringSkipNotify(GetStringValue(content));
        }
 
        internal void AddNode(XNode n)
        {
            ValidateNode(n, this);
            if (n.parent != null)
            {
                n = n.CloneNode();
            }
            else
            {
                XNode p = this;
                while (p.parent != null) p = p.parent;
                if (n == p) n = n.CloneNode();
            }
            ConvertTextToNode();
            AppendNode(n);
        }
 
        internal void AddNodeSkipNotify(XNode n)
        {
            ValidateNode(n, this);
            if (n.parent != null)
            {
                n = n.CloneNode();
            }
            else
            {
                XNode p = this;
                while (p.parent != null) p = p.parent;
                if (n == p) n = n.CloneNode();
            }
            ConvertTextToNode();
            AppendNodeSkipNotify(n);
        }
 
        internal void AddString(string s)
        {
            ValidateString(s);
            if (content == null)
            {
                if (s.Length > 0)
                {
                    AppendNode(new XText(s));
                }
                else
                {
                    if (this is XElement)
                    {
                        // Change in the serialization of an empty element:
                        // from empty tag to start/end tag pair
                        NotifyChanging(this, XObjectChangeEventArgs.Value);
                        if (content != null) throw new InvalidOperationException(SR.InvalidOperation_ExternalCode);
                        content = s;
                        NotifyChanged(this, XObjectChangeEventArgs.Value);
                    }
                    else
                    {
                        content = s;
                    }
                }
            }
            else if (s.Length > 0)
            {
                ConvertTextToNode();
                XText? tn = content as XText;
                if (tn != null && !(tn is XCData))
                {
                    tn.Value += s;
                }
                else
                {
                    AppendNode(new XText(s));
                }
            }
        }
 
        internal void AddStringSkipNotify(string s)
        {
            ValidateString(s);
            if (content == null)
            {
                content = s;
            }
            else if (s.Length > 0)
            {
                string? stringContent = content as string;
                if (stringContent != null)
                {
                    content = stringContent + s;
                }
                else
                {
                    XText? tn = content as XText;
                    if (tn != null && !(tn is XCData))
                    {
                        tn.text += s;
                    }
                    else
                    {
                        AppendNodeSkipNotify(new XText(s));
                    }
                }
            }
        }
 
        internal void AppendNode(XNode n)
        {
            bool notify = NotifyChanging(n, XObjectChangeEventArgs.Add);
            if (n.parent != null) throw new InvalidOperationException(SR.InvalidOperation_ExternalCode);
            AppendNodeSkipNotify(n);
            if (notify) NotifyChanged(n, XObjectChangeEventArgs.Add);
        }
 
        internal void AppendNodeSkipNotify(XNode n)
        {
            n.parent = this;
            if (content == null || content is string)
            {
                n.next = n;
            }
            else
            {
                XNode x = (XNode)content;
                n.next = x.next;
                x.next = n;
            }
            content = n;
        }
 
        internal override void AppendText(StringBuilder sb)
        {
            string? s = content as string;
            if (s != null)
            {
                sb.Append(s);
            }
            else
            {
                XNode? n = (XNode?)content;
                if (n != null)
                {
                    do
                    {
                        n = n.next!;
                        n.AppendText(sb);
                    } while (n != content);
                }
            }
        }
 
        private string? GetTextOnly()
        {
            if (content == null) return null;
            string? s = content as string;
            if (s == null)
            {
                XNode n = (XNode)content;
                do
                {
                    n = n.next!;
                    if (n.NodeType != XmlNodeType.Text) return null;
                    s += ((XText)n).Value;
                } while (n != content);
            }
            return s;
        }
 
        private string CollectText(ref XNode? n)
        {
            string s = "";
            while (n != null && n.NodeType == XmlNodeType.Text)
            {
                s += ((XText)n).Value;
                n = n != content ? n.next : null;
            }
            return s;
        }
 
        internal bool ContentsEqual(XContainer e)
        {
            if (content == e.content) return true;
            string? s = GetTextOnly();
            if (s != null) return s == e.GetTextOnly();
            XNode? n1 = content as XNode;
            XNode? n2 = e.content as XNode;
            if (n1 != null && n2 != null)
            {
                n1 = n1.next;
                n2 = n2.next;
                while (true)
                {
                    if (CollectText(ref n1) != e.CollectText(ref n2)) break;
                    if (n1 == null && n2 == null) return true;
                    if (n1 == null || n2 == null || !n1.DeepEquals(n2)) break;
                    n1 = n1 != content ? n1.next : null;
                    n2 = n2 != e.content ? n2.next : null;
                }
            }
            return false;
        }
 
        internal int ContentsHashCode()
        {
            string? s = GetTextOnly();
            if (s != null) return s.GetHashCode();
            int h = 0;
            XNode? n = content as XNode;
            if (n != null)
            {
                do
                {
                    n = n.next;
                    string text = CollectText(ref n);
                    if (text.Length > 0)
                    {
                        h ^= text.GetHashCode();
                    }
                    if (n == null) break;
                    h ^= n.GetDeepHashCode();
                } while (n != content);
            }
            return h;
        }
 
        internal void ConvertTextToNode()
        {
            string? s = content as string;
            if (!string.IsNullOrEmpty(s))
            {
                XText t = new XText(s);
                t.parent = this;
                t.next = t;
                content = t;
            }
        }
 
        internal IEnumerable<XNode> GetDescendantNodes(bool self)
        {
            if (self) yield return this;
            XNode n = this;
            while (true)
            {
                XContainer? c = n as XContainer;
                XNode? first;
                if (c != null && (first = c.FirstNode) != null)
                {
                    n = first;
                }
                else
                {
                    while (n != null && n != this && n == n.parent!.content) n = n.parent;
                    if (n == null || n == this) break;
                    n = n.next!;
                }
                yield return n;
            }
        }
 
        internal IEnumerable<XElement> GetDescendants(XName? name, bool self)
        {
            if (self)
            {
                XElement e = (XElement)this;
                if (name == null || e.name == name) yield return e;
            }
            XNode n = this;
            XContainer? c = this;
            while (true)
            {
                if (c != null && c.content is XNode)
                {
                    n = ((XNode)c.content).next!;
                }
                else
                {
                    while (n != this && n == n.parent!.content) n = n.parent;
                    if (n == this) break;
                    n = n.next!;
                }
                XElement? e = n as XElement;
                if (e != null && (name == null || e.name == name)) yield return e;
                c = e;
            }
        }
 
        private IEnumerable<XElement> GetElements(XName? name)
        {
            XNode? n = content as XNode;
            if (n != null)
            {
                do
                {
                    n = n.next!;
                    XElement? e = n as XElement;
                    if (e != null && (name == null || e.name == name)) yield return e;
                } while (n.parent == this && n != content);
            }
        }
 
        internal static string GetStringValue(object value)
        {
            string? s = value switch
            {
                string stringValue => stringValue,
                int intValue => XmlConvert.ToString(intValue),
                double doubleValue => XmlConvert.ToString(doubleValue),
                long longValue => XmlConvert.ToString(longValue),
                float floatValue => XmlConvert.ToString(floatValue),
                decimal decimalValue => XmlConvert.ToString(decimalValue),
                short shortValue => XmlConvert.ToString(shortValue),
                sbyte sbyteValue => XmlConvert.ToString(sbyteValue),
                bool boolValue => XmlConvert.ToString(boolValue),
                DateTime dtValue => XmlConvert.ToString(dtValue, XmlDateTimeSerializationMode.RoundtripKind),
                DateTimeOffset dtoValue => XmlConvert.ToString(dtoValue),
                TimeSpan tsValue => XmlConvert.ToString(tsValue),
                XObject => throw new ArgumentException(SR.Argument_XObjectValue),
                _ => value.ToString()
            };
 
            if (s == null) throw new ArgumentException(SR.Argument_ConvertToString);
            return s;
        }
 
        internal void ReadContentFrom(XmlReader r)
        {
            if (r.ReadState != ReadState.Interactive) throw new InvalidOperationException(SR.InvalidOperation_ExpectedInteractive);
 
            ContentReader cr = new ContentReader(this);
            while (cr.ReadContentFrom(this, r) && r.Read()) ;
        }
 
        internal void ReadContentFrom(XmlReader r, LoadOptions o)
        {
            if ((o & (LoadOptions.SetBaseUri | LoadOptions.SetLineInfo)) == 0)
            {
                ReadContentFrom(r);
                return;
            }
            if (r.ReadState != ReadState.Interactive) throw new InvalidOperationException(SR.InvalidOperation_ExpectedInteractive);
 
            ContentReader cr = new ContentReader(this, r, o);
            while (cr.ReadContentFromContainer(this, r) && r.Read()) ;
        }
 
        internal async Task ReadContentFromAsync(XmlReader r, CancellationToken cancellationToken)
        {
            if (r.ReadState != ReadState.Interactive) throw new InvalidOperationException(SR.InvalidOperation_ExpectedInteractive);
 
            ContentReader cr = new ContentReader(this);
            do
            {
                cancellationToken.ThrowIfCancellationRequested();
            }
            while (await cr.ReadContentFromAsync(this, r).ConfigureAwait(false) && await r.ReadAsync().ConfigureAwait(false));
        }
 
        internal async Task ReadContentFromAsync(XmlReader r, LoadOptions o, CancellationToken cancellationToken)
        {
            if ((o & (LoadOptions.SetBaseUri | LoadOptions.SetLineInfo)) == 0)
            {
                await ReadContentFromAsync(r, cancellationToken).ConfigureAwait(false);
                return;
            }
            if (r.ReadState != ReadState.Interactive) throw new InvalidOperationException(SR.InvalidOperation_ExpectedInteractive);
 
            ContentReader cr = new ContentReader(this, r, o);
            do
            {
                cancellationToken.ThrowIfCancellationRequested();
            }
            while (await cr.ReadContentFromContainerAsync(this, r).ConfigureAwait(false) && await r.ReadAsync().ConfigureAwait(false));
        }
 
        private sealed class ContentReader
        {
            private NamespaceCache _eCache;
            private NamespaceCache _aCache;
            private readonly IXmlLineInfo? _lineInfo;
            private XContainer _currentContainer;
            private string? _baseUri;
 
            public ContentReader(XContainer rootContainer)
            {
                _currentContainer = rootContainer;
            }
 
            public ContentReader(XContainer rootContainer, XmlReader r, LoadOptions o)
            {
                _currentContainer = rootContainer;
                _baseUri = (o & LoadOptions.SetBaseUri) != 0 ? r.BaseURI : null;
                _lineInfo = (o & LoadOptions.SetLineInfo) != 0 ? r as IXmlLineInfo : null;
            }
 
            public bool ReadContentFrom(XContainer rootContainer, XmlReader r)
            {
                switch (r.NodeType)
                {
                    case XmlNodeType.Element:
                        XElement e = new XElement(_eCache.Get(r.NamespaceURI).GetName(r.LocalName));
                        if (r.MoveToFirstAttribute())
                        {
                            do
                            {
                                e.AppendAttributeSkipNotify(new XAttribute(_aCache.Get(r.Prefix.Length == 0 ? string.Empty : r.NamespaceURI).GetName(r.LocalName), r.Value));
                            } while (r.MoveToNextAttribute());
                            r.MoveToElement();
                        }
                        _currentContainer.AddNodeSkipNotify(e);
                        if (!r.IsEmptyElement)
                        {
                            _currentContainer = e;
                        }
                        break;
                    case XmlNodeType.EndElement:
                        _currentContainer.content ??= string.Empty;
                        if (_currentContainer == rootContainer) return false;
                        _currentContainer = _currentContainer.parent!;
                        break;
                    case XmlNodeType.Text:
                    case XmlNodeType.SignificantWhitespace:
                    case XmlNodeType.Whitespace:
                        _currentContainer.AddStringSkipNotify(r.Value);
                        break;
                    case XmlNodeType.CDATA:
                        _currentContainer.AddNodeSkipNotify(new XCData(r.Value));
                        break;
                    case XmlNodeType.Comment:
                        _currentContainer.AddNodeSkipNotify(new XComment(r.Value));
                        break;
                    case XmlNodeType.ProcessingInstruction:
                        _currentContainer.AddNodeSkipNotify(new XProcessingInstruction(r.Name, r.Value));
                        break;
                    case XmlNodeType.DocumentType:
                        _currentContainer.AddNodeSkipNotify(new XDocumentType(r.LocalName, r.GetAttribute("PUBLIC"), r.GetAttribute("SYSTEM"), r.Value));
                        break;
                    case XmlNodeType.EntityReference:
                        if (!r.CanResolveEntity) throw new InvalidOperationException(SR.InvalidOperation_UnresolvedEntityReference);
                        r.ResolveEntity();
                        break;
                    case XmlNodeType.EndEntity:
                        break;
                    default:
                        throw new InvalidOperationException(SR.Format(SR.InvalidOperation_UnexpectedNodeType, r.NodeType));
                }
                return true;
            }
 
            public async ValueTask<bool> ReadContentFromAsync(XContainer rootContainer, XmlReader r)
            {
                switch (r.NodeType)
                {
                    case XmlNodeType.Element:
                        XElement e = new XElement(_eCache.Get(r.NamespaceURI).GetName(r.LocalName));
                        if (r.MoveToFirstAttribute())
                        {
                            do
                            {
                                e.AppendAttributeSkipNotify(new XAttribute(
                                    _aCache.Get(r.Prefix.Length == 0 ? string.Empty : r.NamespaceURI).GetName(r.LocalName),
                                    await r.GetValueAsync().ConfigureAwait(false)));
                            } while (r.MoveToNextAttribute());
                            r.MoveToElement();
                        }
                        _currentContainer.AddNodeSkipNotify(e);
                        if (!r.IsEmptyElement)
                        {
                            _currentContainer = e;
                        }
                        break;
                    case XmlNodeType.EndElement:
                        _currentContainer.content ??= string.Empty;
                        if (_currentContainer == rootContainer) return false;
                        _currentContainer = _currentContainer.parent!;
                        break;
                    case XmlNodeType.Text:
                    case XmlNodeType.SignificantWhitespace:
                    case XmlNodeType.Whitespace:
                        _currentContainer.AddStringSkipNotify(await r.GetValueAsync().ConfigureAwait(false));
                        break;
                    case XmlNodeType.CDATA:
                        _currentContainer.AddNodeSkipNotify(new XCData(await r.GetValueAsync().ConfigureAwait(false)));
                        break;
                    case XmlNodeType.Comment:
                        _currentContainer.AddNodeSkipNotify(new XComment(await r.GetValueAsync().ConfigureAwait(false)));
                        break;
                    case XmlNodeType.ProcessingInstruction:
                        _currentContainer.AddNodeSkipNotify(new XProcessingInstruction(r.Name, await r.GetValueAsync().ConfigureAwait(false)));
                        break;
                    case XmlNodeType.DocumentType:
                        _currentContainer.AddNodeSkipNotify(new XDocumentType(r.LocalName, r.GetAttribute("PUBLIC"), r.GetAttribute("SYSTEM"), await r.GetValueAsync().ConfigureAwait(false)));
                        break;
                    case XmlNodeType.EntityReference:
                        if (!r.CanResolveEntity) throw new InvalidOperationException(SR.InvalidOperation_UnresolvedEntityReference);
                        r.ResolveEntity();
                        break;
                    case XmlNodeType.EndEntity:
                        break;
                    default:
                        throw new InvalidOperationException(SR.Format(SR.InvalidOperation_UnexpectedNodeType, r.NodeType));
                }
                return true;
            }
 
            public bool ReadContentFromContainer(XContainer rootContainer, XmlReader r)
            {
                XNode? newNode = null;
                string baseUri = r.BaseURI;
 
                switch (r.NodeType)
                {
                    case XmlNodeType.Element:
                    {
                        XElement e = new XElement(_eCache.Get(r.NamespaceURI).GetName(r.LocalName));
                        if (_baseUri != null && _baseUri != baseUri)
                        {
                            e.SetBaseUri(baseUri);
                        }
                        if (_lineInfo != null && _lineInfo.HasLineInfo())
                        {
                            e.SetLineInfo(_lineInfo.LineNumber, _lineInfo.LinePosition);
                        }
                        if (r.MoveToFirstAttribute())
                        {
                            do
                            {
                                XAttribute a = new XAttribute(_aCache.Get(r.Prefix.Length == 0 ? string.Empty : r.NamespaceURI).GetName(r.LocalName), r.Value);
                                if (_lineInfo != null && _lineInfo.HasLineInfo())
                                {
                                    a.SetLineInfo(_lineInfo.LineNumber, _lineInfo.LinePosition);
                                }
                                e.AppendAttributeSkipNotify(a);
                            } while (r.MoveToNextAttribute());
                            r.MoveToElement();
                        }
                        _currentContainer.AddNodeSkipNotify(e);
                        if (!r.IsEmptyElement)
                        {
                            _currentContainer = e;
                            if (_baseUri != null)
                            {
                                _baseUri = baseUri;
                            }
                        }
                        break;
                    }
                    case XmlNodeType.EndElement:
                    {
                        _currentContainer.content ??= string.Empty;
                        // Store the line info of the end element tag.
                        // Note that since we've got EndElement the current container must be an XElement
                        XElement? e = _currentContainer as XElement;
                        Debug.Assert(e != null, "EndElement received but the current container is not an element.");
                        if (e != null && _lineInfo != null && _lineInfo.HasLineInfo())
                        {
                                e.SetEndElementLineInfo(_lineInfo.LineNumber, _lineInfo.LinePosition);
                        }
                        if (_currentContainer == rootContainer) return false;
                        if (_baseUri != null && _currentContainer.HasBaseUri)
                        {
                                _baseUri = _currentContainer.parent!.BaseUri;
                        }
                        _currentContainer = _currentContainer.parent!;
                        break;
                    }
                    case XmlNodeType.Text:
                    case XmlNodeType.SignificantWhitespace:
                    case XmlNodeType.Whitespace:
                        if ((_baseUri != null && _baseUri != baseUri) ||
                            (_lineInfo != null && _lineInfo.HasLineInfo()))
                        {
                            newNode = new XText(r.Value);
                        }
                        else
                        {
                            _currentContainer.AddStringSkipNotify(r.Value);
                        }
                        break;
                    case XmlNodeType.CDATA:
                        newNode = new XCData(r.Value);
                        break;
                    case XmlNodeType.Comment:
                        newNode = new XComment(r.Value);
                        break;
                    case XmlNodeType.ProcessingInstruction:
                        newNode = new XProcessingInstruction(r.Name, r.Value);
                        break;
                    case XmlNodeType.DocumentType:
                        newNode = new XDocumentType(r.LocalName, r.GetAttribute("PUBLIC"), r.GetAttribute("SYSTEM"), r.Value);
                        break;
                    case XmlNodeType.EntityReference:
                        if (!r.CanResolveEntity) throw new InvalidOperationException(SR.InvalidOperation_UnresolvedEntityReference);
                        r.ResolveEntity();
                        break;
                    case XmlNodeType.EndEntity:
                        break;
                    default:
                        throw new InvalidOperationException(SR.Format(SR.InvalidOperation_UnexpectedNodeType, r.NodeType));
                }
 
                if (newNode != null)
                {
                    if (_baseUri != null && _baseUri != baseUri)
                    {
                        newNode.SetBaseUri(baseUri);
                    }
 
                    if (_lineInfo != null && _lineInfo.HasLineInfo())
                    {
                        newNode.SetLineInfo(_lineInfo.LineNumber, _lineInfo.LinePosition);
                    }
 
                    _currentContainer.AddNodeSkipNotify(newNode);
                }
 
                return true;
            }
 
            public async ValueTask<bool> ReadContentFromContainerAsync(XContainer rootContainer, XmlReader r)
            {
                XNode? newNode = null;
                string baseUri = r.BaseURI!;
 
                switch (r.NodeType)
                {
                    case XmlNodeType.Element:
                        {
                            XElement e = new XElement(_eCache.Get(r.NamespaceURI).GetName(r.LocalName));
                            if (_baseUri != null && _baseUri != baseUri)
                            {
                                e.SetBaseUri(baseUri);
                            }
                            if (_lineInfo != null && _lineInfo.HasLineInfo())
                            {
                                e.SetLineInfo(_lineInfo.LineNumber, _lineInfo.LinePosition);
                            }
                            if (r.MoveToFirstAttribute())
                            {
                                do
                                {
                                    XAttribute a = new XAttribute(
                                        _aCache.Get(r.Prefix.Length == 0 ? string.Empty : r.NamespaceURI).GetName(r.LocalName),
                                        await r.GetValueAsync().ConfigureAwait(false));
                                    if (_lineInfo != null && _lineInfo.HasLineInfo())
                                    {
                                        a.SetLineInfo(_lineInfo.LineNumber, _lineInfo.LinePosition);
                                    }
                                    e.AppendAttributeSkipNotify(a);
                                } while (r.MoveToNextAttribute());
                                r.MoveToElement();
                            }
                            _currentContainer.AddNodeSkipNotify(e);
                            if (!r.IsEmptyElement)
                            {
                                _currentContainer = e;
                                if (_baseUri != null)
                                {
                                    _baseUri = baseUri;
                                }
                            }
                            break;
                        }
                    case XmlNodeType.EndElement:
                        {
                            _currentContainer.content ??= string.Empty;
                            // Store the line info of the end element tag.
                            // Note that since we've got EndElement the current container must be an XElement
                            XElement? e = _currentContainer as XElement;
                            Debug.Assert(e != null, "EndElement received but the current container is not an element.");
                            if (e != null && _lineInfo != null && _lineInfo.HasLineInfo())
                            {
                                e.SetEndElementLineInfo(_lineInfo.LineNumber, _lineInfo.LinePosition);
                            }
                            if (_currentContainer == rootContainer) return false;
                            if (_baseUri != null && _currentContainer.HasBaseUri)
                            {
                                _baseUri = _currentContainer.parent!.BaseUri;
                            }
                            _currentContainer = _currentContainer.parent!;
                            break;
                        }
                    case XmlNodeType.Text:
                    case XmlNodeType.SignificantWhitespace:
                    case XmlNodeType.Whitespace:
                        if ((_baseUri != null && _baseUri != baseUri) ||
                            (_lineInfo != null && _lineInfo.HasLineInfo()))
                        {
                            newNode = new XText(await r.GetValueAsync().ConfigureAwait(false));
                        }
                        else
                        {
                            _currentContainer.AddStringSkipNotify(await r.GetValueAsync().ConfigureAwait(false));
                        }
                        break;
                    case XmlNodeType.CDATA:
                        newNode = new XCData(await r.GetValueAsync().ConfigureAwait(false));
                        break;
                    case XmlNodeType.Comment:
                        newNode = new XComment(await r.GetValueAsync().ConfigureAwait(false));
                        break;
                    case XmlNodeType.ProcessingInstruction:
                        newNode = new XProcessingInstruction(r.Name, await r.GetValueAsync().ConfigureAwait(false));
                        break;
                    case XmlNodeType.DocumentType:
                        newNode = new XDocumentType(r.LocalName, r.GetAttribute("PUBLIC"), r.GetAttribute("SYSTEM"), await r.GetValueAsync().ConfigureAwait(false));
                        break;
                    case XmlNodeType.EntityReference:
                        if (!r.CanResolveEntity) throw new InvalidOperationException(SR.InvalidOperation_UnresolvedEntityReference);
                        r.ResolveEntity();
                        break;
                    case XmlNodeType.EndEntity:
                        break;
                    default:
                        throw new InvalidOperationException(SR.Format(SR.InvalidOperation_UnexpectedNodeType, r.NodeType));
                }
 
                if (newNode != null)
                {
                    if (_baseUri != null && _baseUri != baseUri)
                    {
                        newNode.SetBaseUri(baseUri);
                    }
 
                    if (_lineInfo != null && _lineInfo.HasLineInfo())
                    {
                        newNode.SetLineInfo(_lineInfo.LineNumber, _lineInfo.LinePosition);
                    }
 
                    _currentContainer.AddNodeSkipNotify(newNode);
                }
 
                return true;
            }
        }
 
        internal void RemoveNode(XNode n)
        {
            bool notify = NotifyChanging(n, XObjectChangeEventArgs.Remove);
            if (n.parent != this) throw new InvalidOperationException(SR.InvalidOperation_ExternalCode);
 
            Debug.Assert(content != null);
            XNode p = (XNode)content;
            while (p.next != n) p = p.next!;
            if (p == n)
            {
                content = null;
            }
            else
            {
                if (content == n) content = p;
                p.next = n.next;
            }
            n.parent = null;
            n.next = null;
            if (notify) NotifyChanged(n, XObjectChangeEventArgs.Remove);
        }
 
        private void RemoveNodesSkipNotify()
        {
            XNode? n = content as XNode;
            if (n != null)
            {
                do
                {
                    XNode next = n.next!;
                    n.parent = null;
                    n.next = null;
                    n = next;
                } while (n != content);
            }
            content = null;
        }
 
        // Validate insertion of the given node. previous is the node after which insertion
        // will occur. previous == null means at beginning, previous == this means at end.
        internal virtual void ValidateNode(XNode node, XNode? previous)
        {
        }
 
        internal virtual void ValidateString(string s)
        {
        }
 
        internal void WriteContentTo(XmlWriter writer)
        {
            if (content != null)
            {
                string? stringContent = content as string;
                if (stringContent != null)
                {
                    if (this is XDocument)
                    {
                        writer.WriteWhitespace(stringContent);
                    }
                    else
                    {
                        writer.WriteString(stringContent);
                    }
                }
                else
                {
                    XNode n = (XNode)content;
                    do
                    {
                        n = n.next!;
                        n.WriteTo(writer);
                    } while (n != content);
                }
            }
        }
 
        internal async Task WriteContentToAsync(XmlWriter writer, CancellationToken cancellationToken)
        {
            if (content != null)
            {
                string? stringContent = content as string;
 
                if (stringContent != null)
                {
                    cancellationToken.ThrowIfCancellationRequested();
 
                    Task tWrite;
 
                    if (this is XDocument)
                    {
                        tWrite = writer.WriteWhitespaceAsync(stringContent);
                    }
                    else
                    {
                        tWrite = writer.WriteStringAsync(stringContent);
                    }
 
                    await tWrite.ConfigureAwait(false);
                }
                else
                {
                    XNode n = (XNode)content;
                    do
                    {
                        n = n.next!;
                        await n.WriteToAsync(writer, cancellationToken).ConfigureAwait(false);
                    } while (n != content);
                }
            }
        }
 
        private static void AddContentToList(List<object?> list, object? content)
        {
            IEnumerable? e = content is string ? null : content as IEnumerable;
            if (e == null)
            {
                list.Add(content);
            }
            else
            {
                foreach (object? obj in e)
                {
                    if (obj != null) AddContentToList(list, obj);
                }
            }
        }
 
        [return: NotNullIfNotNull(nameof(content))]
        internal static object? GetContentSnapshot(object? content)
        {
            if (content is string || !(content is IEnumerable)) return content;
            List<object?> list = new List<object?>();
            AddContentToList(list, content);
            return list;
        }
    }
}