File: System\Xml\Linq\XLinq.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.Threading;
using System.Threading.Tasks;
 
using IEnumerable = System.Collections.IEnumerable;
using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute;
 
namespace System.Xml.Linq
{
    internal struct Inserter
    {
        private readonly XContainer _parent;
        private XNode? _previous;
        private string? _text;
 
        public Inserter(XContainer parent, XNode? anchor)
        {
            _parent = parent;
            _previous = anchor;
            _text = null;
        }
 
        public void Add(object? content)
        {
            AddContent(content);
            if (_text != null)
            {
                if (_parent.content == null)
                {
                    if (_parent.SkipNotify())
                    {
                        _parent.content = _text;
                    }
                    else
                    {
                        if (_text.Length > 0)
                        {
                            InsertNode(new XText(_text));
                        }
                        else
                        {
                            if (_parent is XElement)
                            {
                                // Change in the serialization of an empty element:
                                // from empty tag to start/end tag pair
                                _parent.NotifyChanging(_parent, XObjectChangeEventArgs.Value);
                                if (_parent.content != null) throw new InvalidOperationException(SR.InvalidOperation_ExternalCode);
                                _parent.content = _text;
                                _parent.NotifyChanged(_parent, XObjectChangeEventArgs.Value);
                            }
                            else
                            {
                                _parent.content = _text;
                            }
                        }
                    }
                }
                else if (_text.Length > 0)
                {
                    XText? prevXText = _previous as XText;
                    if (prevXText != null && !(_previous is XCData))
                    {
                        prevXText.Value += _text;
                    }
                    else
                    {
                        _parent.ConvertTextToNode();
                        InsertNode(new XText(_text));
                    }
                }
            }
        }
 
        private void AddContent(object? content)
        {
            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;
            }
            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) AddContent(obj);
                return;
            }
            IEnumerable? e = content as IEnumerable;
            if (e != null)
            {
                foreach (object? obj in e) AddContent(obj);
                return;
            }
            if (content is XAttribute) throw new ArgumentException(SR.Argument_AddAttribute);
            AddString(XContainer.GetStringValue(content));
        }
 
        private void AddNode(XNode n)
        {
            _parent.ValidateNode(n, _previous);
            if (n.parent != null)
            {
                n = n.CloneNode();
            }
            else
            {
                XNode p = _parent;
                while (p.parent != null) p = p.parent;
                if (n == p) n = n.CloneNode();
            }
            _parent.ConvertTextToNode();
            if (_text != null)
            {
                if (_text.Length > 0)
                {
                    XText? prevXText = _previous as XText;
                    if (prevXText != null && !(_previous is XCData))
                    {
                        prevXText.Value += _text;
                    }
                    else
                    {
                        InsertNode(new XText(_text));
                    }
                }
                _text = null;
            }
            InsertNode(n);
        }
 
        private void AddString(string s)
        {
            _parent.ValidateString(s);
            _text += s;
        }
 
        // Prepends if previous == null, otherwise inserts after previous
        private void InsertNode(XNode n)
        {
            bool notify = _parent.NotifyChanging(n, XObjectChangeEventArgs.Add);
            if (n.parent != null) throw new InvalidOperationException(SR.InvalidOperation_ExternalCode);
            n.parent = _parent;
            if (_parent.content == null || _parent.content is string)
            {
                n.next = n;
                _parent.content = n;
            }
            else if (_previous == null)
            {
                XNode last = (XNode)_parent.content;
                n.next = last.next;
                last.next = n;
            }
            else
            {
                n.next = _previous.next;
                _previous.next = n;
                if (_parent.content == _previous) _parent.content = n;
            }
            _previous = n;
            if (notify) _parent.NotifyChanged(n, XObjectChangeEventArgs.Add);
        }
    }
 
    internal struct NamespaceCache
    {
        private XNamespace _ns;
        private string _namespaceName;
 
        public XNamespace Get(string namespaceName)
        {
            if ((object)namespaceName == (object)_namespaceName) return _ns;
            _namespaceName = namespaceName;
            _ns = XNamespace.Get(namespaceName);
            return _ns;
        }
    }
 
    internal struct ElementWriter
    {
        private readonly XmlWriter _writer;
        private NamespaceResolver _resolver;
 
        public ElementWriter(XmlWriter writer)
        {
            _writer = writer;
            _resolver = default;
        }
 
        public void WriteElement(XElement e)
        {
            PushAncestors(e);
            XElement root = e;
            XNode n = e;
            while (true)
            {
                XElement? current = n as XElement;
                if (current != null)
                {
                    WriteStartElement(current);
                    if (current.content == null)
                    {
                        WriteEndElement();
                    }
                    else
                    {
                        string? s = current.content as string;
                        if (s != null)
                        {
                            _writer.WriteString(s);
                            WriteFullEndElement();
                        }
                        else
                        {
                            n = ((XNode)current.content).next!;
                            continue;
                        }
                    }
                }
                else
                {
                    n.WriteTo(_writer);
                }
                while (n != root && n == n.parent!.content)
                {
                    n = n.parent;
                    WriteFullEndElement();
                }
                if (n == root) break;
                n = n.next!;
            }
        }
 
        public async Task WriteElementAsync(XElement e, CancellationToken cancellationToken)
        {
            PushAncestors(e);
            XElement root = e;
            XNode n = e;
            while (true)
            {
                XElement? current = n as XElement;
                if (current != null)
                {
                    await WriteStartElementAsync(current, cancellationToken).ConfigureAwait(false);
                    if (current.content == null)
                    {
                        await WriteEndElementAsync(cancellationToken).ConfigureAwait(false);
                    }
                    else
                    {
                        string? s = current.content as string;
                        if (s != null)
                        {
                            cancellationToken.ThrowIfCancellationRequested();
                            await _writer.WriteStringAsync(s).ConfigureAwait(false);
                            await WriteFullEndElementAsync(cancellationToken).ConfigureAwait(false);
                        }
                        else
                        {
                            n = ((XNode)current.content).next!;
                            continue;
                        }
                    }
                }
                else
                {
                    await n.WriteToAsync(_writer, cancellationToken).ConfigureAwait(false);
                }
                while (n != root && n == n.parent!.content)
                {
                    n = n.parent;
                    await WriteFullEndElementAsync(cancellationToken).ConfigureAwait(false);
                }
                if (n == root) break;
                n = n.next!;
            }
        }
 
        private string? GetPrefixOfNamespace(XNamespace ns, bool allowDefaultNamespace)
        {
            string namespaceName = ns.NamespaceName;
            if (namespaceName.Length == 0) return string.Empty;
            string? prefix = _resolver.GetPrefixOfNamespace(ns, allowDefaultNamespace);
            if (prefix != null) return prefix;
            if ((object)namespaceName == (object)XNamespace.xmlPrefixNamespace) return "xml";
            if ((object)namespaceName == (object)XNamespace.xmlnsPrefixNamespace) return "xmlns";
            return null;
        }
 
        private void PushAncestors(XElement? e)
        {
            while (true)
            {
                e = e!.parent as XElement;
                if (e == null) break;
                XAttribute? a = e.lastAttr;
                if (a != null)
                {
                    do
                    {
                        a = a.next!;
                        if (a.IsNamespaceDeclaration)
                        {
                            _resolver.AddFirst(a.Name.NamespaceName.Length == 0 ? string.Empty : a.Name.LocalName, XNamespace.Get(a.Value));
                        }
                    } while (a != e.lastAttr);
                }
            }
        }
 
        private void PushElement(XElement e)
        {
            _resolver.PushScope();
            XAttribute? a = e.lastAttr;
            if (a != null)
            {
                do
                {
                    a = a.next!;
                    if (a.IsNamespaceDeclaration)
                    {
                        _resolver.Add(a.Name.NamespaceName.Length == 0 ? string.Empty : a.Name.LocalName, XNamespace.Get(a.Value));
                    }
                } while (a != e.lastAttr);
            }
        }
 
        private void WriteEndElement()
        {
            _writer.WriteEndElement();
            _resolver.PopScope();
        }
 
        private async Task WriteEndElementAsync(CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            await _writer.WriteEndElementAsync().ConfigureAwait(false);
            _resolver.PopScope();
        }
 
        private void WriteFullEndElement()
        {
            _writer.WriteFullEndElement();
            _resolver.PopScope();
        }
 
        private async Task WriteFullEndElementAsync(CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            await _writer.WriteFullEndElementAsync().ConfigureAwait(false);
            _resolver.PopScope();
        }
 
        private void WriteStartElement(XElement e)
        {
            PushElement(e);
            XNamespace ns = e.Name.Namespace;
            _writer.WriteStartElement(GetPrefixOfNamespace(ns, true), e.Name.LocalName, ns.NamespaceName);
            XAttribute? a = e.lastAttr;
            if (a != null)
            {
                do
                {
                    a = a.next!;
                    ns = a.Name.Namespace;
                    string localName = a.Name.LocalName;
                    string namespaceName = ns.NamespaceName;
                    _writer.WriteAttributeString(GetPrefixOfNamespace(ns, false), localName, namespaceName.Length == 0 && localName == "xmlns" ? XNamespace.xmlnsPrefixNamespace : namespaceName, a.Value);
                } while (a != e.lastAttr);
            }
        }
 
        private async Task WriteStartElementAsync(XElement e, CancellationToken cancellationToken)
        {
            PushElement(e);
            XNamespace ns = e.Name.Namespace;
            await _writer.WriteStartElementAsync(GetPrefixOfNamespace(ns, true), e.Name.LocalName, ns.NamespaceName).ConfigureAwait(false);
            XAttribute? a = e.lastAttr;
            if (a != null)
            {
                do
                {
                    a = a.next!;
                    ns = a.Name.Namespace;
                    string localName = a.Name.LocalName;
                    string namespaceName = ns.NamespaceName;
                    cancellationToken.ThrowIfCancellationRequested();
                    await _writer.WriteAttributeStringAsync(GetPrefixOfNamespace(ns, false), localName, namespaceName.Length == 0 && localName == "xmlns" ? XNamespace.xmlnsPrefixNamespace : namespaceName, a.Value).ConfigureAwait(false);
                } while (a != e.lastAttr);
            }
        }
    }
 
    internal struct NamespaceResolver
    {
        private sealed class NamespaceDeclaration
        {
            public string prefix = null!;
            public XNamespace ns = null!;
            public int scope;
            public NamespaceDeclaration prev = null!;
        }
 
        private int _scope;
        private NamespaceDeclaration? _declaration;
        private NamespaceDeclaration? _rover;
 
        public void PushScope()
        {
            _scope++;
        }
 
        public void PopScope()
        {
            NamespaceDeclaration? d = _declaration;
            if (d != null)
            {
                do
                {
                    d = d.prev;
                    if (d.scope != _scope) break;
                    if (d == _declaration)
                    {
                        _declaration = null;
                    }
                    else
                    {
                        _declaration!.prev = d.prev;
                    }
                    _rover = null;
                } while (d != _declaration && _declaration != null);
            }
            _scope--;
        }
 
        public void Add(string prefix, XNamespace ns)
        {
            NamespaceDeclaration d = new NamespaceDeclaration();
            d.prefix = prefix;
            d.ns = ns;
            d.scope = _scope;
            if (_declaration == null)
            {
                _declaration = d;
            }
            else
            {
                d.prev = _declaration.prev;
            }
            _declaration.prev = d;
            _rover = null;
        }
 
        public void AddFirst(string prefix, XNamespace ns)
        {
            NamespaceDeclaration d = new NamespaceDeclaration();
            d.prefix = prefix;
            d.ns = ns;
            d.scope = _scope;
            if (_declaration == null)
            {
                d.prev = d;
            }
            else
            {
                d.prev = _declaration.prev;
                _declaration.prev = d;
            }
            _declaration = d;
            _rover = null;
        }
 
        // Only elements allow default namespace declarations. The rover
        // caches the last namespace declaration used by an element.
        public string? GetPrefixOfNamespace(XNamespace ns, bool allowDefaultNamespace)
        {
            if (_rover != null && _rover.ns == ns && (allowDefaultNamespace || _rover.prefix.Length > 0)) return _rover.prefix;
            NamespaceDeclaration? d = _declaration;
            if (d != null)
            {
                do
                {
                    d = d.prev;
                    if (d.ns == ns)
                    {
                        NamespaceDeclaration x = _declaration!.prev;
                        while (x != d && x.prefix != d.prefix)
                        {
                            x = x.prev;
                        }
                        if (x == d)
                        {
                            if (allowDefaultNamespace)
                            {
                                _rover = d;
                                return d.prefix;
                            }
                            else if (d.prefix.Length > 0)
                            {
                                return d.prefix;
                            }
                        }
                    }
                } while (d != _declaration);
            }
            return null;
        }
    }
 
    internal struct StreamingElementWriter
    {
        private readonly XmlWriter _writer;
        private XStreamingElement? _element;
        private readonly List<XAttribute> _attributes;
        private NamespaceResolver _resolver;
 
        public StreamingElementWriter(XmlWriter w)
        {
            _writer = w;
            _element = null;
            _attributes = new List<XAttribute>();
            _resolver = default;
        }
 
        private void FlushElement()
        {
            if (_element != null)
            {
                PushElement();
                XNamespace ns = _element.Name.Namespace;
                _writer.WriteStartElement(GetPrefixOfNamespace(ns, true), _element.Name.LocalName, ns.NamespaceName);
                foreach (XAttribute a in _attributes)
                {
                    ns = a.Name.Namespace;
                    string localName = a.Name.LocalName;
                    string namespaceName = ns.NamespaceName;
                    _writer.WriteAttributeString(GetPrefixOfNamespace(ns, false), localName, namespaceName.Length == 0 && localName == "xmlns" ? XNamespace.xmlnsPrefixNamespace : namespaceName, a.Value);
                }
                _element = null;
                _attributes.Clear();
            }
        }
 
        private string? GetPrefixOfNamespace(XNamespace ns, bool allowDefaultNamespace)
        {
            string namespaceName = ns.NamespaceName;
            if (namespaceName.Length == 0) return string.Empty;
            string? prefix = _resolver.GetPrefixOfNamespace(ns, allowDefaultNamespace);
            if (prefix != null) return prefix;
            if ((object)namespaceName == (object)XNamespace.xmlPrefixNamespace) return "xml";
            if ((object)namespaceName == (object)XNamespace.xmlnsPrefixNamespace) return "xmlns";
            return null;
        }
 
        private void PushElement()
        {
            _resolver.PushScope();
            foreach (XAttribute a in _attributes)
            {
                if (a.IsNamespaceDeclaration)
                {
                    _resolver.Add(a.Name.NamespaceName.Length == 0 ? string.Empty : a.Name.LocalName, XNamespace.Get(a.Value));
                }
            }
        }
 
        private void Write(object? content)
        {
            if (content == null) return;
            XNode? n = content as XNode;
            if (n != null)
            {
                WriteNode(n);
                return;
            }
            string? s = content as string;
            if (s != null)
            {
                WriteString(s);
                return;
            }
            XAttribute? a = content as XAttribute;
            if (a != null)
            {
                WriteAttribute(a);
                return;
            }
            XStreamingElement? x = content as XStreamingElement;
            if (x != null)
            {
                WriteStreamingElement(x);
                return;
            }
            object[]? o = content as object[];
            if (o != null)
            {
                foreach (object obj in o) Write(obj);
                return;
            }
            IEnumerable? e = content as IEnumerable;
            if (e != null)
            {
                foreach (object obj in e) Write(obj);
                return;
            }
            WriteString(XContainer.GetStringValue(content));
        }
 
        private void WriteAttribute(XAttribute a)
        {
            if (_element == null) throw new InvalidOperationException(SR.InvalidOperation_WriteAttribute);
            _attributes.Add(a);
        }
 
        private void WriteNode(XNode n)
        {
            FlushElement();
            n.WriteTo(_writer);
        }
 
        internal void WriteStreamingElement(XStreamingElement e)
        {
            FlushElement();
            _element = e;
            Write(e.content);
            FlushElement();
            _writer.WriteEndElement();
            _resolver.PopScope();
        }
 
        private void WriteString(string s)
        {
            FlushElement();
            _writer.WriteString(s);
        }
    }
 
    /// <summary>
    /// Specifies the event type when an event is raised for an <see cref="XObject"/>.
    /// </summary>
    public enum XObjectChange
    {
        /// <summary>
        /// An <see cref="XObject"/> has been or will be added to an <see cref="XContainer"/>.
        /// </summary>
        Add,
 
        /// <summary>
        /// An <see cref="XObject"/> has been or will be removed from an <see cref="XContainer"/>.
        /// </summary>
        Remove,
 
        /// <summary>
        /// An <see cref="XObject"/> has been or will be renamed.
        /// </summary>
        Name,
 
        /// <summary>
        /// The value of an <see cref="XObject"/> has been or will be changed.
        /// There is a special case for elements. Change in the serialization
        /// of an empty element (either from an empty tag to start/end tag
        /// pair or vice versa) raises this event.
        /// </summary>
        Value,
    }
 
    /// <summary>
    /// Specifies a set of options for Load().
    /// </summary>
    [Flags]
    public enum LoadOptions
    {
        /// <summary>Default options.</summary>
        None = 0x00000000,
 
        /// <summary>Preserve whitespace.</summary>
        PreserveWhitespace = 0x00000001,
 
        /// <summary>Set the BaseUri property.</summary>
        SetBaseUri = 0x00000002,
 
        /// <summary>Set the IXmlLineInfo.</summary>
        SetLineInfo = 0x00000004,
    }
 
    /// <summary>
    /// Specifies a set of options for Save().
    /// </summary>
    [Flags]
    public enum SaveOptions
    {
        /// <summary>Default options.</summary>
        None = 0x00000000,
 
        /// <summary>Disable formatting.</summary>
        DisableFormatting = 0x00000001,
 
        /// <summary>Remove duplicate namespace declarations.</summary>
        OmitDuplicateNamespaces = 0x00000002,
    }
 
    /// <summary>
    /// Specifies a set of options for CreateReader().
    /// </summary>
    [Flags]
    public enum ReaderOptions
    {
        /// <summary>Default options.</summary>
        None = 0x00000000,
 
        /// <summary>Remove duplicate namespace declarations.</summary>
        OmitDuplicateNamespaces = 0x00000001,
    }
}