File: System\Xml\Linq\XDocument.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.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Encoding = System.Text.Encoding;
using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute;
 
namespace System.Xml.Linq
{
    /// <summary>
    /// Represents an XML document.
    /// </summary>
    /// <remarks>
    /// An <see cref="XDocument"/> can contain:
    /// <list>
    ///   <item>
    ///   A Document Type Declaration (DTD), see <see cref="XDocumentType"/>
    ///   </item>
    ///   <item>One root element.</item>
    ///   <item>Zero or more <see cref="XComment"/> objects.</item>
    ///   <item>Zero or more <see cref="XProcessingInstruction"/> objects.</item>
    /// </list>
    /// </remarks>
    public class XDocument : XContainer
    {
        private XDeclaration? _declaration;
 
        ///<overloads>
        /// Initializes a new instance of the <see cref="XDocument"/> class.
        /// Overloaded constructors are provided for creating a new empty
        /// <see cref="XDocument"/>, creating an <see cref="XDocument"/> with
        /// a parameter list of initial content, and as a copy of another
        /// <see cref="XDocument"/> object.
        /// </overloads>
        /// <summary>
        /// Initializes a new instance of the <see cref="XDocument"/> class.
        /// </summary>
        public XDocument()
        {
        }
 
        /// <summary>
        /// Initializes a new instance of the <see cref="XDocument"/> class with the specified content.
        /// </summary>
        /// <param name="content">
        /// A parameter list of content objects to add to this document.
        /// </param>
        /// <remarks>
        /// Valid content includes:
        /// <list>
        /// <item>Zero or one <see cref="XDocumentType"/> objects</item>
        /// <item>Zero or one elements</item>
        /// <item>Zero or more comments</item>
        /// <item>Zero or more processing instructions</item>
        /// </list>
        /// See <see cref="XContainer.Add(object)"/> for details about the content that can be added
        /// using this method.
        /// </remarks>
        public XDocument(params object?[] content)
            : this()
        {
            AddContentSkipNotify(content);
        }
 
        /// <summary>
        /// Initializes a new instance of the <see cref="XDocument"/> class
        /// with the specified <see cref="XDeclaration"/> and content.
        /// </summary>
        /// <param name="declaration">
        /// The XML declaration for the document.
        /// </param>
        /// <param name="content">
        /// The contents of the document.
        /// </param>
        /// <remarks>
        /// Valid content includes:
        /// <list>
        /// <item>Zero or one <see cref="XDocumentType"/> objects</item>
        /// <item>Zero or one elements</item>
        /// <item>Zero or more comments</item>
        /// <item>Zero or more processing instructions</item>
        /// <item></item>
        /// </list>
        /// See <see cref="XContainer.Add(object)"/> for details about the content that can be added
        /// using this method.
        /// </remarks>
        public XDocument(XDeclaration? declaration, params object?[] content)
            : this(content)
        {
            _declaration = declaration;
        }
 
        /// <summary>
        /// Initializes a new instance of the <see cref="XDocument"/> class from an
        /// existing XDocument object.
        /// </summary>
        /// <param name="other">
        /// The <see cref="XDocument"/> object that will be copied.
        /// </param>
        public XDocument(XDocument other)
            : base(other)
        {
            if (other._declaration != null)
            {
                _declaration = new XDeclaration(other._declaration);
            }
        }
 
        /// <summary>
        /// Gets the XML declaration for this document.
        /// </summary>
        public XDeclaration? Declaration
        {
            get { return _declaration; }
            set { _declaration = value; }
        }
 
        /// <summary>
        /// Gets the Document Type Definition (DTD) for this document.
        /// </summary>
        public XDocumentType? DocumentType
        {
            get
            {
                return GetFirstNode<XDocumentType>();
            }
        }
 
        /// <summary>
        /// Gets the node type for this node.
        /// </summary>
        /// <remarks>
        /// This property will always return XmlNodeType.Document.
        /// </remarks>
        public override XmlNodeType NodeType
        {
            get
            {
                return XmlNodeType.Document;
            }
        }
 
        /// <summary>
        /// Gets the root element of the XML Tree for this document.
        /// </summary>
        public XElement? Root
        {
            get
            {
                return GetFirstNode<XElement>();
            }
        }
 
        /// <overloads>
        /// The Load method provides multiple strategies for creating a new
        /// <see cref="XDocument"/> and initializing it from a data source containing
        /// raw XML.  Load from a file (passing in a URI to the file), a
        /// <see cref="Stream"/>, a <see cref="TextReader"/>, or an
        /// <see cref="XmlReader"/>.  Note:  Use <see cref="XDocument.Parse(string)"/>
        /// to create an <see cref="XDocument"/> from a string containing XML.
        /// <seealso cref="XDocument.Parse(string)"/>
        /// </overloads>
        /// <summary>
        /// Create a new <see cref="XDocument"/> based on the contents of the file
        /// referenced by the URI parameter passed in.  Note: Use
        /// <see cref="XDocument.Parse(string)"/> to create an <see cref="XDocument"/> from
        /// a string containing XML.
        /// <seealso cref="XmlReader.Create(string)"/>
        /// <seealso cref="XDocument.Parse(string)"/>
        /// </summary>
        /// <remarks>
        /// This method uses the <see cref="XmlReader.Create(string)"/> method to create
        /// an <see cref="XmlReader"/> to read the raw XML into the underlying
        /// XML tree.
        /// </remarks>
        /// <param name="uri">
        /// A URI string referencing the file to load into a new <see cref="XDocument"/>.
        /// </param>
        /// <returns>
        /// An <see cref="XDocument"/> initialized with the contents of the file referenced
        /// in the passed in uri parameter.
        /// </returns>
        public static XDocument Load([StringSyntax(StringSyntaxAttribute.Uri)] string uri)
        {
            return Load(uri, LoadOptions.None);
        }
 
        /// <summary>
        /// Create a new <see cref="XDocument"/> based on the contents of the file
        /// referenced by the URI parameter passed in.  Optionally, whitespace can be preserved.
        /// <see cref="XmlReader.Create(string)"/>
        /// </summary>
        /// <remarks>
        /// This method uses the <see cref="XmlReader.Create(string)"/> method to create
        /// an <see cref="XmlReader"/> to read the raw XML into an underlying
        /// XML tree.  If LoadOptions.PreserveWhitespace is enabled then
        /// the <see cref="XmlReaderSettings"/> property <see cref="XmlReaderSettings.IgnoreWhitespace"/>
        /// is set to false.
        /// </remarks>
        /// <param name="uri">
        /// A string representing the URI of the file to be loaded into a new <see cref="XDocument"/>.
        /// </param>
        /// <param name="options">
        /// A set of <see cref="LoadOptions"/>.
        /// </param>
        /// <returns>
        /// An <see cref="XDocument"/> initialized with the contents of the file referenced
        /// in the passed uri parameter.  If LoadOptions.PreserveWhitespace is enabled then
        /// all whitespace will be preserved.
        /// </returns>
        public static XDocument Load([StringSyntax(StringSyntaxAttribute.Uri)] string uri, LoadOptions options)
        {
            XmlReaderSettings rs = GetXmlReaderSettings(options);
            using (XmlReader r = XmlReader.Create(uri, rs))
            {
                return Load(r, options);
            }
        }
 
        /// <summary>
        /// Create a new <see cref="XDocument"/> and initialize its underlying XML tree using
        /// the passed <see cref="Stream"/> parameter.
        /// </summary>
        /// <param name="stream">
        /// A <see cref="Stream"/> containing the raw XML to read into the newly
        /// created <see cref="XDocument"/>.
        /// </param>
        /// <returns>
        /// A new <see cref="XDocument"/> containing the contents of the passed in
        /// <see cref="Stream"/>.
        /// </returns>
        public static XDocument Load(Stream stream)
        {
            return Load(stream, LoadOptions.None);
        }
 
        /// <summary>
        /// Create a new <see cref="XDocument"/> and initialize its underlying XML tree using
        /// the passed <see cref="Stream"/> parameter.  Optionally whitespace handling
        /// can be preserved.
        /// </summary>
        /// <remarks>
        /// If LoadOptions.PreserveWhitespace is enabled then
        /// the underlying <see cref="XmlReaderSettings"/> property <see cref="XmlReaderSettings.IgnoreWhitespace"/>
        /// is set to false.
        /// </remarks>
        /// <param name="stream">
        /// A <see cref="Stream"/> containing the raw XML to read into the newly
        /// created <see cref="XDocument"/>.
        /// </param>
        /// <param name="options">
        /// A set of <see cref="LoadOptions"/>.
        /// </param>
        /// <returns>
        /// A new <see cref="XDocument"/> containing the contents of the passed in
        /// <see cref="Stream"/>.
        /// </returns>
        public static XDocument Load(Stream stream, LoadOptions options)
        {
            XmlReaderSettings rs = GetXmlReaderSettings(options);
            using (XmlReader r = XmlReader.Create(stream, rs))
            {
                return Load(r, options);
            }
        }
 
        /// <summary>
        /// Create a new <see cref="XDocument"/> and initialize its underlying XML tree using
        /// the passed <see cref="Stream"/> parameter.  Optionally whitespace handling
        /// can be preserved.
        /// </summary>
        /// <remarks>
        /// If LoadOptions.PreserveWhitespace is enabled then
        /// the underlying <see cref="XmlReaderSettings"/> property <see cref="XmlReaderSettings.IgnoreWhitespace"/>
        /// is set to false.
        /// </remarks>
        /// <param name="stream">
        /// A <see cref="Stream"/> containing the raw XML to read into the newly
        /// created <see cref="XDocument"/>.
        /// </param>
        /// <param name="options">
        /// A set of <see cref="LoadOptions"/>.
        /// </param>
        /// <param name="cancellationToken">
        /// A cancellation token.
        /// </param>
        /// <returns>
        /// A new <see cref="XDocument"/> containing the contents of the passed in
        /// <see cref="Stream"/>.
        /// </returns>
        public static async Task<XDocument> LoadAsync(Stream stream, LoadOptions options, CancellationToken cancellationToken)
        {
            XmlReaderSettings rs = GetXmlReaderSettings(options);
 
            rs.Async = true;
 
            using (XmlReader r = XmlReader.Create(stream, rs))
            {
                return await LoadAsync(r, options, cancellationToken).ConfigureAwait(false);
            }
        }
 
        /// <summary>
        /// Create a new <see cref="XDocument"/> and initialize its underlying XML tree using
        /// the passed <see cref="TextReader"/> parameter.
        /// </summary>
        /// <param name="textReader">
        /// A <see cref="TextReader"/> containing the raw XML to read into the newly
        /// created <see cref="XDocument"/>.
        /// </param>
        /// <returns>
        /// A new <see cref="XDocument"/> containing the contents of the passed in
        /// <see cref="TextReader"/>.
        /// </returns>
        public static XDocument Load(TextReader textReader)
        {
            return Load(textReader, LoadOptions.None);
        }
 
        /// <summary>
        /// Create a new <see cref="XDocument"/> and initialize its underlying XML tree using
        /// the passed <see cref="TextReader"/> parameter.  Optionally whitespace handling
        /// can be preserved.
        /// </summary>
        /// <remarks>
        /// If LoadOptions.PreserveWhitespace is enabled then
        /// the <see cref="XmlReaderSettings"/> property <see cref="XmlReaderSettings.IgnoreWhitespace"/>
        /// is set to false.
        /// </remarks>
        /// <param name="textReader">
        /// A <see cref="TextReader"/> containing the raw XML to read into the newly
        /// created <see cref="XDocument"/>.
        /// </param>
        /// <param name="options">
        /// A set of <see cref="LoadOptions"/>.
        /// </param>
        /// <returns>
        /// A new <see cref="XDocument"/> containing the contents of the passed in
        /// <see cref="TextReader"/>.
        /// </returns>
        public static XDocument Load(TextReader textReader, LoadOptions options)
        {
            XmlReaderSettings rs = GetXmlReaderSettings(options);
            using (XmlReader r = XmlReader.Create(textReader, rs))
            {
                return Load(r, options);
            }
        }
 
        /// <summary>
        /// Create a new <see cref="XDocument"/> and initialize its underlying XML tree using
        /// the passed <see cref="TextReader"/> parameter.  Optionally whitespace handling
        /// can be preserved.
        /// </summary>
        /// <remarks>
        /// If LoadOptions.PreserveWhitespace is enabled then
        /// the <see cref="XmlReaderSettings"/> property <see cref="XmlReaderSettings.IgnoreWhitespace"/>
        /// is set to false.
        /// </remarks>
        /// <param name="textReader">
        /// A <see cref="TextReader"/> containing the raw XML to read into the newly
        /// created <see cref="XDocument"/>.
        /// </param>
        /// <param name="options">
        /// A set of <see cref="LoadOptions"/>.
        /// </param>
        /// <param name="cancellationToken">
        /// A cancellation token.
        /// </param>
        /// <returns>
        /// A new <see cref="XDocument"/> containing the contents of the passed in
        /// <see cref="TextReader"/>.
        /// </returns>
        public static async Task<XDocument> LoadAsync(TextReader textReader, LoadOptions options, CancellationToken cancellationToken)
        {
            XmlReaderSettings rs = GetXmlReaderSettings(options);
 
            rs.Async = true;
 
            using (XmlReader r = XmlReader.Create(textReader, rs))
            {
                return await LoadAsync(r, options, cancellationToken).ConfigureAwait(false);
            }
        }
 
        /// <summary>
        /// Create a new <see cref="XDocument"/> containing the contents of the
        /// passed in <see cref="XmlReader"/>.
        /// </summary>
        /// <param name="reader">
        /// An <see cref="XmlReader"/> containing the XML to be read into the new
        /// <see cref="XDocument"/>.
        /// </param>
        /// <returns>
        /// A new <see cref="XDocument"/> containing the contents of the passed
        /// in <see cref="XmlReader"/>.
        /// </returns>
        public static XDocument Load(XmlReader reader)
        {
            return Load(reader, LoadOptions.None);
        }
 
        /// <summary>
        /// Create a new <see cref="XDocument"/> containing the contents of the
        /// passed in <see cref="XmlReader"/>.
        /// </summary>
        /// <param name="reader">
        /// An <see cref="XmlReader"/> containing the XML to be read into the new
        /// <see cref="XDocument"/>.
        /// </param>
        /// <param name="options">
        /// A set of <see cref="LoadOptions"/>.
        /// </param>
        /// <returns>
        /// A new <see cref="XDocument"/> containing the contents of the passed
        /// in <see cref="XmlReader"/>.
        /// </returns>
        public static XDocument Load(XmlReader reader, LoadOptions options)
        {
            ArgumentNullException.ThrowIfNull(reader);
 
            if (reader.ReadState == ReadState.Initial) reader.Read();
 
            XDocument d = InitLoad(reader, options);
            d.ReadContentFrom(reader, options);
 
            if (!reader.EOF) throw new InvalidOperationException(SR.InvalidOperation_ExpectedEndOfFile);
            if (d.Root == null) throw new InvalidOperationException(SR.InvalidOperation_MissingRoot);
            return d;
        }
 
        /// <summary>
        /// Create a new <see cref="XDocument"/> containing the contents of the
        /// passed in <see cref="XmlReader"/>.
        /// </summary>
        /// <param name="reader">
        /// An <see cref="XmlReader"/> containing the XML to be read into the new
        /// <see cref="XDocument"/>.
        /// </param>
        /// <param name="options">
        /// A set of <see cref="LoadOptions"/>.
        /// </param>
        /// <param name="cancellationToken">
        /// A cancellation token.
        /// </param>
        /// <returns>
        /// A new <see cref="XDocument"/> containing the contents of the passed
        /// in <see cref="XmlReader"/>.
        /// </returns>
        public static Task<XDocument> LoadAsync(XmlReader reader, LoadOptions options, CancellationToken cancellationToken)
        {
            ArgumentNullException.ThrowIfNull(reader);
 
            if (cancellationToken.IsCancellationRequested)
                return Task.FromCanceled<XDocument>(cancellationToken);
            return LoadAsyncInternal(reader, options, cancellationToken);
        }
 
        private static async Task<XDocument> LoadAsyncInternal(XmlReader reader, LoadOptions options, CancellationToken cancellationToken)
        {
            if (reader.ReadState == ReadState.Initial)
            {
                await reader.ReadAsync().ConfigureAwait(false);
            }
 
            XDocument d = InitLoad(reader, options);
            await d.ReadContentFromAsync(reader, options, cancellationToken).ConfigureAwait(false);
 
            if (!reader.EOF) throw new InvalidOperationException(SR.InvalidOperation_ExpectedEndOfFile);
            if (d.Root == null) throw new InvalidOperationException(SR.InvalidOperation_MissingRoot);
            return d;
        }
 
        /// <summary>
        /// Performs shared initialization between Load and LoadAsync.
        /// </summary>
        private static XDocument InitLoad(XmlReader reader, LoadOptions options)
        {
            XDocument d = new XDocument();
            if ((options & LoadOptions.SetBaseUri) != 0)
            {
                string? baseUri = reader.BaseURI;
                if (!string.IsNullOrEmpty(baseUri))
                {
                    d.SetBaseUri(baseUri);
                }
            }
            if ((options & LoadOptions.SetLineInfo) != 0)
            {
                IXmlLineInfo? li = reader as IXmlLineInfo;
                if (li != null && li.HasLineInfo())
                {
                    d.SetLineInfo(li.LineNumber, li.LinePosition);
                }
            }
            if (reader.NodeType == XmlNodeType.XmlDeclaration)
            {
                d.Declaration = new XDeclaration(reader);
            }
            return d;
        }
 
        /// <overloads>
        /// Create a new <see cref="XDocument"/> from a string containing
        /// XML.  Optionally whitespace can be preserved.
        /// </overloads>
        /// <summary>
        /// Create a new <see cref="XDocument"/> from a string containing
        /// XML.
        /// </summary>
        /// <param name="text">
        /// A string containing XML.
        /// </param>
        /// <returns>
        /// An <see cref="XDocument"/> containing an XML tree initialized from the
        /// passed in XML string.
        /// </returns>
        public static XDocument Parse(string text)
        {
            return Parse(text, LoadOptions.None);
        }
 
        /// <summary>
        /// Create a new <see cref="XDocument"/> from a string containing
        /// XML.  Optionally whitespace can be preserved.
        /// </summary>
        /// <remarks>
        /// This method uses <see cref="XmlReader.Create(TextReader, XmlReaderSettings)"/>,
        /// passing it a StringReader constructed from the passed in XML String. If
        /// <see cref="LoadOptions.PreserveWhitespace"/> is enabled then
        /// <see cref="XmlReaderSettings.IgnoreWhitespace"/> is set to false. See
        /// <see cref="XmlReaderSettings.IgnoreWhitespace"/> for more information on
        /// whitespace handling.
        /// </remarks>
        /// <param name="text">
        /// A string containing XML.
        /// </param>
        /// <param name="options">
        /// A set of <see cref="LoadOptions"/>.
        /// </param>
        /// <returns>
        /// An <see cref="XDocument"/> containing an XML tree initialized from the
        /// passed in XML string.
        /// </returns>
        public static XDocument Parse(string text, LoadOptions options)
        {
            using (StringReader sr = new StringReader(text))
            {
                XmlReaderSettings rs = GetXmlReaderSettings(options);
                using (XmlReader r = XmlReader.Create(sr, rs))
                {
                    return Load(r, options);
                }
            }
        }
 
        /// <summary>
        /// Output this <see cref="XDocument"/> to the passed in <see cref="Stream"/>.
        /// </summary>
        /// <remarks>
        /// The format will be indented by default.  If you want
        /// no indenting then use the SaveOptions version of Save (see
        /// <see cref="XDocument.Save(Stream, SaveOptions)"/>) enabling
        /// SaveOptions.DisableFormatting
        /// There is also an option SaveOptions.OmitDuplicateNamespaces for removing duplicate namespace declarations.
        /// Or instead use the SaveOptions as an annotation on this node or its ancestors, then this method will use those options.
        /// </remarks>
        /// <param name="stream">
        /// The <see cref="Stream"/> to output this <see cref="XDocument"/> to.
        /// </param>
        public void Save(Stream stream)
        {
            Save(stream, GetSaveOptionsFromAnnotations());
        }
 
        /// <summary>
        /// Output this <see cref="XDocument"/> to a <see cref="Stream"/>.
        /// </summary>
        /// <param name="stream">
        /// The <see cref="Stream"/> to output the XML to.
        /// </param>
        /// <param name="options">
        /// If SaveOptions.DisableFormatting is enabled the output is not indented.
        /// If SaveOptions.OmitDuplicateNamespaces is enabled duplicate namespace declarations will be removed.
        /// </param>
        public void Save(Stream stream, SaveOptions options)
        {
            XmlWriterSettings ws = GetXmlWriterSettings(options);
            if (_declaration != null && !string.IsNullOrEmpty(_declaration.Encoding))
            {
                try
                {
                    ws.Encoding = Encoding.GetEncoding(_declaration.Encoding);
                }
                catch (ArgumentException)
                {
                }
            }
            using (XmlWriter w = XmlWriter.Create(stream, ws))
            {
                Save(w);
            }
        }
 
        /// <summary>
        /// Output this <see cref="XDocument"/> to a <see cref="Stream"/>.
        /// </summary>
        /// <param name="stream">
        /// The <see cref="Stream"/> to output the XML to.
        /// </param>
        /// <param name="options">
        /// If SaveOptions.DisableFormatting is enabled the output is not indented.
        /// If SaveOptions.OmitDuplicateNamespaces is enabled duplicate namespace declarations will be removed.
        /// </param>
        /// <param name="cancellationToken">A cancellation token.</param>
        public async Task SaveAsync(Stream stream, SaveOptions options, CancellationToken cancellationToken)
        {
            XmlWriterSettings ws = GetXmlWriterSettings(options);
 
            ws.Async = true;
 
            if (_declaration != null && !string.IsNullOrEmpty(_declaration.Encoding))
            {
                try
                {
                    ws.Encoding = Encoding.GetEncoding(_declaration.Encoding);
                }
                catch (ArgumentException)
                {
                }
            }
 
            XmlWriter w = XmlWriter.Create(stream, ws);
            await using (w.ConfigureAwait(false))
            {
                await WriteToAsync(w, cancellationToken).ConfigureAwait(false);
                await w.FlushAsync().ConfigureAwait(false);
            }
        }
 
        /// <summary>
        /// Output this <see cref="XDocument"/> to the passed in <see cref="TextWriter"/>.
        /// </summary>
        /// <remarks>
        /// The format will be indented by default.  If you want
        /// no indenting then use the SaveOptions version of Save (see
        /// <see cref="XDocument.Save(TextWriter, SaveOptions)"/>) enabling
        /// SaveOptions.DisableFormatting
        /// There is also an option SaveOptions.OmitDuplicateNamespaces for removing duplicate namespace declarations.
        /// Or instead use the SaveOptions as an annotation on this node or its ancestors, then this method will use those options.
        /// </remarks>
        /// <param name="textWriter">
        /// The <see cref="TextWriter"/> to output this <see cref="XDocument"/> to.
        /// </param>
        public void Save(TextWriter textWriter)
        {
            Save(textWriter, GetSaveOptionsFromAnnotations());
        }
 
        /// <summary>
        /// Output this <see cref="XDocument"/> to a <see cref="TextWriter"/>.
        /// </summary>
        /// <param name="textWriter">
        /// The <see cref="TextWriter"/> to output the XML to.
        /// </param>
        /// <param name="options">
        /// If SaveOptions.DisableFormatting is enabled the output is not indented.
        /// If SaveOptions.OmitDuplicateNamespaces is enabled duplicate namespace declarations will be removed.
        /// </param>
        public void Save(TextWriter textWriter, SaveOptions options)
        {
            XmlWriterSettings ws = GetXmlWriterSettings(options);
            using (XmlWriter w = XmlWriter.Create(textWriter, ws))
            {
                Save(w);
            }
        }
 
        /// <summary>
        /// Output this <see cref="XDocument"/> to an <see cref="XmlWriter"/>.
        /// </summary>
        /// <param name="writer">
        /// The <see cref="XmlWriter"/> to output the XML to.
        /// </param>
        public void Save(XmlWriter writer)
        {
            WriteTo(writer);
        }
 
        /// <summary>
        /// Output this <see cref="XDocument"/> to a <see cref="TextWriter"/>.
        /// </summary>
        /// <param name="textWriter">
        /// The <see cref="TextWriter"/> to output the XML to.
        /// </param>
        /// <param name="options">
        /// If SaveOptions.DisableFormatting is enabled the output is not indented.
        /// If SaveOptions.OmitDuplicateNamespaces is enabled duplicate namespace declarations will be removed.
        /// </param>
        /// <param name="cancellationToken">A cancellation token.</param>
        public async Task SaveAsync(TextWriter textWriter, SaveOptions options, CancellationToken cancellationToken)
        {
            XmlWriterSettings ws = GetXmlWriterSettings(options);
 
            ws.Async = true;
 
            XmlWriter w = XmlWriter.Create(textWriter, ws);
            await using (w.ConfigureAwait(false))
            {
                await WriteToAsync(w, cancellationToken).ConfigureAwait(false);
                await w.FlushAsync().ConfigureAwait(false);
            }
        }
 
        ///<overloads>
        /// Outputs this <see cref="XDocument"/>'s underlying XML tree.  The output can
        /// be saved to a file, a <see cref="Stream"/>, a <see cref="TextWriter"/>,
        /// or an <see cref="XmlWriter"/>.  Optionally whitespace can be preserved.
        /// </overloads>
        /// <summary>
        /// Output this <see cref="XDocument"/> to a file.
        /// </summary>
        /// <remarks>
        /// The format will be indented by default.  If you want
        /// no indenting then use the SaveOptions version of Save (see
        /// <see cref="XDocument.Save(string, SaveOptions)"/>) enabling
        /// SaveOptions.DisableFormatting.
        /// There is also an option SaveOptions.OmitDuplicateNamespaces for removing duplicate namespace declarations.
        /// Or instead use the SaveOptions as an annotation on this node or its ancestors, then this method will use those options.
        /// </remarks>
        /// <param name="fileName">
        /// The name of the file to output the XML to.
        /// </param>
        public void Save(string fileName)
        {
            Save(fileName, GetSaveOptionsFromAnnotations());
        }
 
        /// <summary>
        /// Output this <see cref="XDocument"/> to an <see cref="XmlWriter"/>.
        /// </summary>
        /// <param name="writer">
        /// The <see cref="XmlWriter"/> to output the XML to.
        /// </param>
        /// <param name="cancellationToken">
        /// A cancellation token.
        /// </param>
        public Task SaveAsync(XmlWriter writer, CancellationToken cancellationToken)
        {
            return WriteToAsync(writer, cancellationToken);
        }
 
        /// <summary>
        /// Output this <see cref="XDocument"/> to a file.
        /// </summary>
        /// <param name="fileName">
        /// The name of the file to output the XML to.
        /// </param>
        /// <param name="options">
        /// If SaveOptions.DisableFormatting is enabled the output is not indented.
        /// If SaveOptions.OmitDuplicateNamespaces is enabled duplicate namespace declarations will be removed.
        /// </param>
        public void Save(string fileName, SaveOptions options)
        {
            XmlWriterSettings ws = GetXmlWriterSettings(options);
            if (_declaration != null && !string.IsNullOrEmpty(_declaration.Encoding))
            {
                try
                {
                    ws.Encoding = Encoding.GetEncoding(_declaration.Encoding);
                }
                catch (ArgumentException)
                {
                }
            }
 
            using (XmlWriter w = XmlWriter.Create(fileName, ws))
            {
                Save(w);
            }
        }
 
        /// <summary>
        /// Output this <see cref="XDocument"/>'s underlying XML tree to the
        /// passed in <see cref="XmlWriter"/>.
        /// <seealso cref="XDocument.Save(XmlWriter)"/>
        /// </summary>
        /// <param name="writer">
        /// The <see cref="XmlWriter"/> to output the content of this
        /// <see cref="XDocument"/>.
        /// </param>
        public override void WriteTo(XmlWriter writer)
        {
            ArgumentNullException.ThrowIfNull(writer);
 
            if (_declaration != null && _declaration.Standalone == "yes")
            {
                writer.WriteStartDocument(true);
            }
            else if (_declaration != null && _declaration.Standalone == "no")
            {
                writer.WriteStartDocument(false);
            }
            else
            {
                writer.WriteStartDocument();
            }
            WriteContentTo(writer);
            writer.WriteEndDocument();
        }
 
        /// <summary>
        /// Output this <see cref="XDocument"/>'s underlying XML tree to the
        /// passed in <see cref="XmlWriter"/>.
        /// <seealso cref="XDocument.Save(XmlWriter)"/>
        /// </summary>
        /// <param name="writer">
        /// The <see cref="XmlWriter"/> to output the content of this
        /// <see cref="XDocument"/>.
        /// </param>
        /// <param name="cancellationToken">A cancellation token.</param>
        public override Task WriteToAsync(XmlWriter writer, CancellationToken cancellationToken)
        {
            ArgumentNullException.ThrowIfNull(writer);
 
            if (cancellationToken.IsCancellationRequested)
                return Task.FromCanceled(cancellationToken);
            return WriteToAsyncInternal(writer, cancellationToken);
        }
 
        private async Task WriteToAsyncInternal(XmlWriter writer, CancellationToken cancellationToken)
        {
            Task tStart;
            if (_declaration != null && _declaration.Standalone == "yes")
            {
                tStart = writer.WriteStartDocumentAsync(true);
            }
            else if (_declaration != null && _declaration.Standalone == "no")
            {
                tStart = writer.WriteStartDocumentAsync(false);
            }
            else
            {
                tStart = writer.WriteStartDocumentAsync();
            }
            await tStart.ConfigureAwait(false);
 
            await WriteContentToAsync(writer, cancellationToken).ConfigureAwait(false);
            await writer.WriteEndDocumentAsync().ConfigureAwait(false);
        }
 
        internal override void AddAttribute(XAttribute a)
        {
            throw new ArgumentException(SR.Argument_AddAttribute);
        }
 
        internal override void AddAttributeSkipNotify(XAttribute a)
        {
            throw new ArgumentException(SR.Argument_AddAttribute);
        }
 
        internal override XNode CloneNode()
        {
            return new XDocument(this);
        }
 
        internal override bool DeepEquals(XNode node)
        {
            XDocument? other = node as XDocument;
            return other != null && ContentsEqual(other);
        }
 
        internal override int GetDeepHashCode()
        {
            return ContentsHashCode();
        }
 
        private T? GetFirstNode<T>() where T : XNode
        {
            XNode? n = content as XNode;
            if (n != null)
            {
                do
                {
                    n = n.next!;
                    T? e = n as T;
                    if (e != null) return e;
                } while (n != content);
            }
            return null;
        }
 
        internal override void ValidateNode(XNode node, XNode? previous)
        {
            switch (node.NodeType)
            {
                case XmlNodeType.Text:
                    ValidateString(((XText)node).Value);
                    break;
                case XmlNodeType.Element:
                    ValidateDocument(previous, XmlNodeType.DocumentType, XmlNodeType.None);
                    break;
                case XmlNodeType.DocumentType:
                    ValidateDocument(previous, XmlNodeType.None, XmlNodeType.Element);
                    break;
                case XmlNodeType.CDATA:
                    throw new ArgumentException(SR.Format(SR.Argument_AddNode, XmlNodeType.CDATA));
                case XmlNodeType.Document:
                    throw new ArgumentException(SR.Format(SR.Argument_AddNode, XmlNodeType.Document));
            }
        }
 
        private void ValidateDocument(XNode? previous, XmlNodeType allowBefore, XmlNodeType allowAfter)
        {
            XNode? n = content as XNode;
            if (n != null)
            {
                if (previous == null) allowBefore = allowAfter;
                do
                {
                    n = n.next!;
                    XmlNodeType nt = n.NodeType;
                    if (nt == XmlNodeType.Element || nt == XmlNodeType.DocumentType)
                    {
                        if (nt != allowBefore) throw new InvalidOperationException(SR.InvalidOperation_DocumentStructure);
                        allowBefore = XmlNodeType.None;
                    }
                    if (n == previous) allowBefore = allowAfter;
                } while (n != content);
            }
        }
 
        internal override void ValidateString(string s)
        {
            if (s.AsSpan().ContainsAnyExcept(" \t\r\n"))
            {
                throw new ArgumentException(SR.Argument_AddNonWhitespace);
            }
        }
    }
}