File: System\Xml\XmlBaseWriter.cs
Web Access
Project: src\src\libraries\System.Private.DataContractSerialization\src\System.Private.DataContractSerialization.csproj (System.Private.DataContractSerialization)
// 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;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
 
namespace System.Xml
{
    internal abstract class XmlBaseWriter : XmlDictionaryWriter, IFragmentCapableXmlDictionaryWriter
    {
        private XmlNodeWriter _writer = null!; // initialized in SetOutput
        private readonly NamespaceManager _nsMgr;
        private Element[]? _elements;
        private int _depth;
        private string? _attributeLocalName;
        private string? _attributeValue;
        private bool _isXmlAttribute;
        private bool _isXmlnsAttribute;
        private WriteState _writeState;
        private DocumentState _documentState;
        private byte[]? _trailBytes;
        private int _trailByteCount;
        private XmlStreamNodeWriter _nodeWriter = null!; // initialized in SetOutput
        private XmlSigningNodeWriter? _signingWriter;
        private XmlUTF8NodeWriter? _textFragmentWriter;
        private XmlNodeWriter? _oldWriter;
        private Stream? _oldStream;
        private int _oldNamespaceBoundary;
        private bool _inList;
        private const string xmlnsNamespace = "http://www.w3.org/2000/xmlns/";
        private const string xmlNamespace = "http://www.w3.org/XML/1998/namespace";
        private static readonly string[] s_prefixes = { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z" };
 
        protected XmlBaseWriter()
        {
            _nsMgr = new NamespaceManager();
            _writeState = WriteState.Start;
            _documentState = DocumentState.None;
        }
 
        protected void SetOutput(XmlStreamNodeWriter writer)
        {
            _inList = false;
            _writer = writer;
            _nodeWriter = writer;
            _writeState = WriteState.Start;
            _documentState = DocumentState.None;
            _nsMgr.Clear();
            if (_depth != 0)
            {
                _elements = null;
                _depth = 0;
            }
            _attributeLocalName = null;
            _attributeValue = null;
            _oldWriter = null;
            _oldStream = null;
        }
 
        public override void Flush()
        {
            if (IsClosed)
                ThrowClosed();
 
            _writer.Flush();
        }
 
        public override Task FlushAsync()
        {
            if (IsClosed)
                ThrowClosed();
 
            return _writer.FlushAsync();
        }
 
        public override void Close()
        {
            if (IsClosed)
                return;
 
            try
            {
                FinishDocument();
                AutoComplete(WriteState.Closed);
                _writer.Flush();
            }
            finally
            {
                _nsMgr.Close();
                if (_depth != 0)
                {
                    _elements = null;
                    _depth = 0;
                }
                _attributeValue = null;
                _attributeLocalName = null;
                _nodeWriter.Close();
                _signingWriter?.Close();
                _textFragmentWriter?.Close();
                _oldWriter = null;
                _oldStream = null;
            }
        }
 
        protected bool IsClosed
        {
            get { return _writeState == WriteState.Closed; }
        }
 
        [DoesNotReturn]
        protected static void ThrowClosed()
        {
            throw new InvalidOperationException(SR.XmlWriterClosed);
        }
 
        public override string? XmlLang
        {
            get
            {
                return _nsMgr.XmlLang;
            }
        }
 
        public override XmlSpace XmlSpace
        {
            get
            {
                return _nsMgr.XmlSpace;
            }
        }
 
        public override WriteState WriteState
        {
            get
            {
                return _writeState;
            }
        }
 
        public override void WriteXmlnsAttribute(string? prefix, string ns)
        {
            if (IsClosed)
                ThrowClosed();
 
            ArgumentNullException.ThrowIfNull(ns);
 
            if (_writeState != WriteState.Element)
                throw new InvalidOperationException(SR.Format(SR.XmlInvalidWriteState, "WriteXmlnsAttribute", WriteState.ToString()));
 
            if (prefix == null)
            {
                prefix = _nsMgr.LookupPrefix(ns);
                if (prefix == null)
                {
                    GeneratePrefix(ns, null);
                }
            }
            else
            {
                _nsMgr.AddNamespaceIfNotDeclared(prefix, ns, null);
            }
        }
 
        public override void WriteXmlnsAttribute(string? prefix, XmlDictionaryString ns)
        {
            if (IsClosed)
                ThrowClosed();
 
            ArgumentNullException.ThrowIfNull(ns);
 
            if (_writeState != WriteState.Element)
                throw new InvalidOperationException(SR.Format(SR.XmlInvalidWriteState, "WriteXmlnsAttribute", WriteState.ToString()));
 
            if (prefix == null)
            {
                prefix = _nsMgr.LookupPrefix(ns.Value);
                if (prefix == null)
                {
                    GeneratePrefix(ns.Value, ns);
                }
            }
            else
            {
                _nsMgr.AddNamespaceIfNotDeclared(prefix, ns.Value, ns);
            }
        }
 
        private void StartAttribute([AllowNull] ref string prefix, string localName, string? ns, XmlDictionaryString? xNs)
        {
            if (IsClosed)
                ThrowClosed();
 
            if (_writeState == WriteState.Attribute)
                WriteEndAttribute();
 
            if (localName == null || (localName.Length == 0 && prefix != "xmlns"))
                throw new ArgumentNullException(nameof(localName));
 
            if (_writeState != WriteState.Element)
                throw new InvalidOperationException(SR.Format(SR.XmlInvalidWriteState, "WriteStartAttribute", WriteState.ToString()));
 
            if (prefix == null)
            {
                if (ns == xmlnsNamespace && localName != "xmlns")
                    prefix = "xmlns";
                else if (ns == xmlNamespace)
                    prefix = "xml";
                else
                    prefix = string.Empty;
            }
 
            // Normalize a (prefix,localName) of (null, "xmlns") to ("xmlns", string.Empty).
            if (prefix.Length == 0 && localName == "xmlns")
            {
                prefix = "xmlns";
                localName = string.Empty;
            }
 
            _isXmlnsAttribute = false;
            _isXmlAttribute = false;
            if (prefix == "xml")
            {
                if (ns != null && ns != xmlNamespace)
                    throw new ArgumentException(SR.Format(SR.XmlPrefixBoundToNamespace, "xml", xmlNamespace, ns), nameof(ns));
                _isXmlAttribute = true;
                _attributeValue = string.Empty;
                _attributeLocalName = localName;
            }
            else if (prefix == "xmlns")
            {
                if (ns != null && ns != xmlnsNamespace)
                    throw new ArgumentException(SR.Format(SR.XmlPrefixBoundToNamespace, "xmlns", xmlnsNamespace, ns), nameof(ns));
                _isXmlnsAttribute = true;
                _attributeValue = string.Empty;
                _attributeLocalName = localName;
            }
            else if (ns == null)
            {
                // A null namespace means the namespace of the given prefix.
                // An empty prefix on an attribute means no namespace (not the default namespace)
                if (prefix.Length != 0)
                {
                    ns = _nsMgr.LookupNamespace(prefix);
 
                    if (ns == null)
                        throw new ArgumentException(SR.Format(SR.XmlUndefinedPrefix, prefix), nameof(prefix));
                }
            }
            else if (ns.Length == 0)
            {
                // An empty namespace means no namespace; prefix must be empty
                if (prefix.Length != 0)
                    throw new ArgumentException(SR.XmlEmptyNamespaceRequiresNullPrefix, nameof(prefix));
            }
            else if (prefix.Length == 0)
            {
                // No prefix specified - try to find a prefix corresponding to the given namespace
                string? tempPrefix = _nsMgr.LookupAttributePrefix(ns);
 
                // If we didn't find anything with the right namespace, generate one.
                if (tempPrefix == null)
                {
                    // Watch for special values
                    if (ns.Length == xmlnsNamespace.Length && ns == xmlnsNamespace)
                        throw new ArgumentException(SR.Format(SR.XmlSpecificBindingNamespace, "xmlns", ns));
                    if (ns.Length == xmlNamespace.Length && ns == xmlNamespace)
                        throw new ArgumentException(SR.Format(SR.XmlSpecificBindingNamespace, "xml", ns));
 
                    tempPrefix = GeneratePrefix(ns, xNs);
                }
                prefix = tempPrefix;
            }
            else
            {
                _nsMgr.AddNamespaceIfNotDeclared(prefix, ns, xNs);
            }
            _writeState = WriteState.Attribute;
        }
 
        public override void WriteStartAttribute(string? prefix, string localName, string? namespaceUri)
        {
            StartAttribute(ref prefix, localName, namespaceUri, null);
            if (!_isXmlnsAttribute)
            {
                _writer.WriteStartAttribute(prefix, localName);
            }
        }
 
        public override void WriteStartAttribute(string? prefix, XmlDictionaryString localName, XmlDictionaryString? namespaceUri)
        {
            StartAttribute(ref prefix, localName?.Value!, namespaceUri?.Value, namespaceUri);
            if (!_isXmlnsAttribute)
            {
                _writer.WriteStartAttribute(prefix, localName!);
            }
        }
 
        public override void WriteEndAttribute()
        {
            if (IsClosed)
                ThrowClosed();
 
            if (_writeState != WriteState.Attribute)
                throw new InvalidOperationException(SR.Format(SR.XmlInvalidWriteState, "WriteEndAttribute", WriteState.ToString()));
 
            FlushBase64();
            try
            {
                if (_isXmlAttribute)
                {
                    if (_attributeLocalName == "lang")
                    {
                        _nsMgr.AddLangAttribute(_attributeValue!);
                    }
                    else if (_attributeLocalName == "space")
                    {
                        if (_attributeValue == "preserve")
                        {
                            _nsMgr.AddSpaceAttribute(XmlSpace.Preserve);
                        }
                        else if (_attributeValue == "default")
                        {
                            _nsMgr.AddSpaceAttribute(XmlSpace.Default);
                        }
                        else
                        {
                            throw new ArgumentException(SR.Format(SR.XmlInvalidXmlSpace, _attributeValue));
                        }
                    }
                    else
                    {
                        // XmlTextWriter specifically allows for other localNames
                    }
                    _isXmlAttribute = false;
                    _attributeLocalName = null;
                    _attributeValue = null;
                }
 
                if (_isXmlnsAttribute)
                {
                    _nsMgr.AddNamespaceIfNotDeclared(_attributeLocalName!, _attributeValue!, null);
                    _isXmlnsAttribute = false;
                    _attributeLocalName = null;
                    _attributeValue = null;
                }
                else
                {
                    _writer.WriteEndAttribute();
                }
            }
            finally
            {
                _writeState = WriteState.Element;
            }
        }
 
        protected override Task WriteEndAttributeAsync()
        {
            if (IsClosed)
                ThrowClosed();
 
            if (_writeState != WriteState.Attribute)
                throw new InvalidOperationException(SR.Format(SR.XmlInvalidWriteState, "WriteEndAttribute", WriteState.ToString()));
 
            return WriteEndAttributeAsyncImpl();
        }
 
        private async Task WriteEndAttributeAsyncImpl()
        {
            await FlushBase64Async().ConfigureAwait(false);
            try
            {
                if (_isXmlAttribute)
                {
                    if (_attributeLocalName == "lang")
                    {
                        _nsMgr.AddLangAttribute(_attributeValue!);
                    }
                    else if (_attributeLocalName == "space")
                    {
                        if (_attributeValue == "preserve")
                        {
                            _nsMgr.AddSpaceAttribute(XmlSpace.Preserve);
                        }
                        else if (_attributeValue == "default")
                        {
                            _nsMgr.AddSpaceAttribute(XmlSpace.Default);
                        }
                        else
                        {
                            throw new ArgumentException(SR.Format(SR.XmlInvalidXmlSpace, _attributeValue));
                        }
                    }
                    else
                    {
                        // XmlTextWriter specifically allows for other localNames
                    }
                    _isXmlAttribute = false;
                    _attributeLocalName = null;
                    _attributeValue = null;
                }
 
                if (_isXmlnsAttribute)
                {
                    _nsMgr.AddNamespaceIfNotDeclared(_attributeLocalName!, _attributeValue!, null);
                    _isXmlnsAttribute = false;
                    _attributeLocalName = null;
                    _attributeValue = null;
                }
                else
                {
                    await _writer.WriteEndAttributeAsync().ConfigureAwait(false);
                }
            }
            finally
            {
                _writeState = WriteState.Element;
            }
        }
 
        public override void WriteComment(string? text)
        {
            if (IsClosed)
                ThrowClosed();
 
            if (_writeState == WriteState.Attribute)
                throw new InvalidOperationException(SR.Format(SR.XmlInvalidWriteState, "WriteComment", WriteState.ToString()));
 
            if (text == null)
            {
                text = string.Empty;
            }
            else if (text.Contains("--") || text.EndsWith('-'))
            {
                throw new ArgumentException(SR.XmlInvalidCommentChars, nameof(text));
            }
 
            StartComment();
            FlushBase64();
            _writer.WriteComment(text);
            EndComment();
        }
 
        public override void WriteFullEndElement()
        {
            if (IsClosed)
                ThrowClosed();
 
            if (_writeState == WriteState.Attribute)
                WriteEndAttribute();
 
            if (_writeState != WriteState.Element && _writeState != WriteState.Content)
                throw new InvalidOperationException(SR.Format(SR.XmlInvalidWriteState, "WriteFullEndElement", WriteState.ToString()));
 
            AutoComplete(WriteState.Content);
            WriteEndElement();
        }
 
        public override void WriteCData(string? text)
        {
            if (IsClosed)
                ThrowClosed();
 
            if (_writeState == WriteState.Attribute)
                throw new InvalidOperationException(SR.Format(SR.XmlInvalidWriteState, "WriteCData", WriteState.ToString()));
 
            text ??= string.Empty;
 
            if (text.Length > 0)
            {
                StartContent();
                FlushBase64();
                _writer.WriteCData(text);
                EndContent();
            }
        }
 
        public override void WriteDocType(string name, string? pubid, string? sysid, string? subset)
        {
            throw new NotSupportedException(SR.Format(SR.XmlMethodNotSupported, "WriteDocType"));
        }
 
        private void StartElement(ref string? prefix, string localName, string? ns, XmlDictionaryString? xNs)
        {
            if (IsClosed)
                ThrowClosed();
 
            if (_documentState == DocumentState.Epilog)
                throw new InvalidOperationException(SR.XmlOnlyOneRoot);
            ArgumentNullException.ThrowIfNull(localName);
            if (localName.Length == 0)
                throw new ArgumentException(SR.InvalidLocalNameEmpty, nameof(localName));
            if (_writeState == WriteState.Attribute)
                throw new InvalidOperationException(SR.Format(SR.XmlInvalidWriteState, "WriteStartElement", WriteState.ToString()));
 
            FlushBase64();
            AutoComplete(WriteState.Element);
            Element element = EnterScope();
            if (ns == null)
            {
                prefix ??= string.Empty;
 
                ns = _nsMgr.LookupNamespace(prefix);
 
                if (ns == null)
                    throw new ArgumentException(SR.Format(SR.XmlUndefinedPrefix, prefix), nameof(prefix));
            }
            else if (prefix == null)
            {
                prefix = _nsMgr.LookupPrefix(ns);
 
                if (prefix == null)
                {
                    prefix = string.Empty;
                    _nsMgr.AddNamespace(string.Empty, ns, xNs);
                }
            }
            else
            {
                _nsMgr.AddNamespaceIfNotDeclared(prefix, ns, xNs);
            }
            element.Prefix = prefix;
            element.LocalName = localName;
        }
 
        private void PreStartElementAsyncCheck(string localName)
        {
            if (IsClosed)
                ThrowClosed();
 
            if (_documentState == DocumentState.Epilog)
                throw new InvalidOperationException(SR.XmlOnlyOneRoot);
            ArgumentException.ThrowIfNullOrEmpty(localName);
            if (_writeState == WriteState.Attribute)
                throw new InvalidOperationException(SR.Format(SR.XmlInvalidWriteState, "WriteStartElement", WriteState.ToString()));
        }
 
        private async Task StartElementAndWriteStartElementAsync(string? prefix, string localName, string? namespaceUri)
        {
            prefix = await StartElementAsync(prefix, localName, namespaceUri, null).ConfigureAwait(false);
            await _writer.WriteStartElementAsync(prefix, localName).ConfigureAwait(false);
        }
 
        private async Task<string> StartElementAsync(string? prefix, string localName, string? ns, XmlDictionaryString? xNs)
        {
            await FlushBase64Async().ConfigureAwait(false);
            await AutoCompleteAsync(WriteState.Element).ConfigureAwait(false);
            Element element = EnterScope();
            if (ns == null)
            {
                prefix ??= string.Empty;
 
                ns = _nsMgr.LookupNamespace(prefix);
 
                if (ns == null)
                    throw new ArgumentException(SR.Format(SR.XmlUndefinedPrefix, prefix), nameof(prefix));
            }
            else if (prefix == null)
            {
                prefix = _nsMgr.LookupPrefix(ns);
 
                if (prefix == null)
                {
                    prefix = string.Empty;
                    _nsMgr.AddNamespace(string.Empty, ns, xNs);
                }
            }
            else
            {
                _nsMgr.AddNamespaceIfNotDeclared(prefix, ns, xNs);
            }
            element.Prefix = prefix;
            element.LocalName = localName;
 
            return prefix;
        }
 
        public override void WriteStartElement(string? prefix, string localName, string? namespaceUri)
        {
            StartElement(ref prefix, localName, namespaceUri, null);
            _writer.WriteStartElement(prefix, localName);
        }
 
        public override Task WriteStartElementAsync(string? prefix, string localName, string? namespaceUri)
        {
            PreStartElementAsyncCheck(localName);
            return StartElementAndWriteStartElementAsync(prefix, localName, namespaceUri);
        }
 
        public override void WriteStartElement(string? prefix, XmlDictionaryString localName, XmlDictionaryString? namespaceUri)
        {
            StartElement(ref prefix, localName.Value, namespaceUri?.Value, namespaceUri);
            _writer.WriteStartElement(prefix, localName);
        }
 
        public override void WriteEndElement()
        {
            if (IsClosed)
                ThrowClosed();
 
            if (_depth == 0)
                throw new InvalidOperationException(SR.Format(SR.XmlInvalidDepth, "WriteEndElement", _depth.ToString(CultureInfo.InvariantCulture)));
 
            if (_writeState == WriteState.Attribute)
                WriteEndAttribute();
 
            FlushBase64();
            if (_writeState == WriteState.Element)
            {
                _nsMgr.DeclareNamespaces(_writer);
                _writer.WriteEndStartElement(true);
            }
            else
            {
                Element element = _elements![_depth];
                _writer.WriteEndElement(element.Prefix, element.LocalName!);
            }
 
            ExitScope();
            _writeState = WriteState.Content;
        }
 
        public override Task WriteEndElementAsync()
        {
            if (IsClosed)
                ThrowClosed();
 
            if (_depth == 0)
                throw new InvalidOperationException(SR.Format(SR.XmlInvalidDepth, "WriteEndElement", _depth.ToString(CultureInfo.InvariantCulture)));
 
            return WriteEndElementAsyncImpl();
        }
 
        private async Task WriteEndElementAsyncImpl()
        {
            if (_writeState == WriteState.Attribute)
                await WriteEndAttributeAsync().ConfigureAwait(false);
 
            FlushBase64();
            if (_writeState == WriteState.Element)
            {
                _nsMgr.DeclareNamespaces(_writer);
                await _writer.WriteEndStartElementAsync(true).ConfigureAwait(false);
            }
            else
            {
                Element element = _elements![_depth];
                await _writer.WriteEndElementAsync(element.Prefix, element.LocalName!).ConfigureAwait(false);
            }
 
            ExitScope();
            _writeState = WriteState.Content;
        }
 
        private Element EnterScope()
        {
            _nsMgr.EnterScope();
            _depth++;
            if (_elements == null)
            {
                _elements = new Element[4];
            }
            else if (_elements.Length == _depth)
            {
                Element[] newElementNodes = new Element[_depth * 2];
                Array.Copy(_elements, newElementNodes, _depth);
                _elements = newElementNodes;
            }
            Element element = _elements[_depth];
            if (element == null)
            {
                element = new Element();
                _elements[_depth] = element;
            }
            return element;
        }
 
        private void ExitScope()
        {
            _elements![_depth].Clear();
            _depth--;
            if (_depth == 0 && _documentState == DocumentState.Document)
                _documentState = DocumentState.Epilog;
            _nsMgr.ExitScope();
        }
 
        protected void FlushElement()
        {
            if (_writeState == WriteState.Element)
            {
                AutoComplete(WriteState.Content);
            }
        }
 
        private Task FlushElementAsync()
        {
            return _writeState == WriteState.Element ? AutoCompleteAsync(WriteState.Content) : Task.CompletedTask;
        }
 
        protected void StartComment()
        {
            FlushElement();
        }
 
        protected static void EndComment()
        {
        }
 
        protected void StartContent()
        {
            FlushElement();
            if (_depth == 0)
                throw new InvalidOperationException(SR.XmlIllegalOutsideRoot);
        }
 
        protected async Task StartContentAsync()
        {
            await FlushElementAsync().ConfigureAwait(false);
            if (_depth == 0)
                throw new InvalidOperationException(SR.XmlIllegalOutsideRoot);
        }
 
        protected void StartContent(char ch)
        {
            FlushElement();
            if (_depth == 0)
                VerifyWhitespace(ch);
        }
 
        protected void StartContent(string s)
        {
            FlushElement();
            if (_depth == 0)
                VerifyWhitespace(s);
        }
 
        protected void StartContent(char[] chars, int offset, int count)
        {
            FlushElement();
            if (_depth == 0)
                VerifyWhitespace(chars, offset, count);
        }
 
        private static void VerifyWhitespace(char ch)
        {
            if (!XmlConverter.IsWhitespace(ch))
                throw new InvalidOperationException(SR.XmlIllegalOutsideRoot);
        }
 
        private static void VerifyWhitespace(string s)
        {
            if (!XmlConverter.IsWhitespace(s))
                throw new InvalidOperationException(SR.XmlIllegalOutsideRoot);
        }
 
        private static void VerifyWhitespace(char[] chars, int offset, int count)
        {
            if (!XmlConverter.IsWhitespace(chars.AsSpan(offset, count)))
                throw new InvalidOperationException(SR.XmlIllegalOutsideRoot);
        }
 
        protected static void EndContent()
        {
        }
 
        private void AutoComplete(WriteState writeState)
        {
            if (_writeState == WriteState.Element)
            {
                EndStartElement();
            }
            _writeState = writeState;
        }
 
        private async Task AutoCompleteAsync(WriteState writeState)
        {
            if (_writeState == WriteState.Element)
            {
                await EndStartElementAsync().ConfigureAwait(false);
            }
            _writeState = writeState;
        }
 
        private void EndStartElement()
        {
            _nsMgr.DeclareNamespaces(_writer);
            _writer.WriteEndStartElement(false);
        }
 
        private Task EndStartElementAsync()
        {
            _nsMgr.DeclareNamespaces(_writer);
            return _writer.WriteEndStartElementAsync(false);
        }
 
        public override string? LookupPrefix(string ns)
        {
            ArgumentNullException.ThrowIfNull(ns);
 
            if (IsClosed)
                ThrowClosed();
 
            return _nsMgr.LookupPrefix(ns);
        }
 
        private string GetQualifiedNamePrefix(string namespaceUri, XmlDictionaryString? xNs)
        {
            string? prefix = _nsMgr.LookupPrefix(namespaceUri);
            if (prefix == null)
            {
                if (_writeState != WriteState.Attribute)
                    throw new ArgumentException(SR.Format(SR.XmlNamespaceNotFound, namespaceUri), nameof(namespaceUri));
 
                prefix = GeneratePrefix(namespaceUri, xNs);
            }
            return prefix;
        }
 
        public override void WriteQualifiedName(string localName, string? namespaceUri)
        {
            if (IsClosed)
                ThrowClosed();
            ArgumentException.ThrowIfNullOrEmpty(localName);
            namespaceUri ??= string.Empty;
            string prefix = GetQualifiedNamePrefix(namespaceUri, null);
            if (prefix.Length != 0)
            {
                WriteString(prefix);
                WriteString(":");
            }
            WriteString(localName);
        }
 
        public override void WriteQualifiedName(XmlDictionaryString localName, XmlDictionaryString? namespaceUri)
        {
            if (IsClosed)
                ThrowClosed();
            ArgumentNullException.ThrowIfNull(localName);
            if (localName.Value.Length == 0)
                throw new ArgumentException(SR.InvalidLocalNameEmpty, nameof(localName));
            namespaceUri ??= XmlDictionaryString.Empty;
            string prefix = GetQualifiedNamePrefix(namespaceUri.Value, namespaceUri);
 
            FlushBase64();
            if (_attributeValue != null)
                WriteAttributeText(string.Concat(prefix, ":", namespaceUri.Value));
 
            if (!_isXmlnsAttribute)
            {
                StartContent();
                _writer.WriteQualifiedName(prefix, localName);
                EndContent();
            }
        }
 
        public override void WriteStartDocument()
        {
            if (IsClosed)
                ThrowClosed();
 
            if (_writeState != WriteState.Start)
                throw new InvalidOperationException(SR.Format(SR.XmlInvalidWriteState, "WriteStartDocument", WriteState.ToString()));
 
            _writeState = WriteState.Prolog;
            _documentState = DocumentState.Document;
            _writer.WriteDeclaration();
        }
 
        public override void WriteStartDocument(bool standalone)
        {
            if (IsClosed)
                ThrowClosed();
 
            WriteStartDocument();
        }
 
 
        public override void WriteProcessingInstruction(string name, string? text)
        {
            if (IsClosed)
                ThrowClosed();
 
            if (name != "xml")
                throw new ArgumentException(SR.XmlProcessingInstructionNotSupported, nameof(name));
 
            if (_writeState != WriteState.Start)
                throw new InvalidOperationException(SR.XmlInvalidDeclaration);
 
            // The only thing the text can legitimately contain is version, encoding, and standalone.
            // We only support version 1.0, we can only write whatever encoding we were supplied,
            // and we don't support DTDs, so whatever values are supplied in the text argument are irrelevant.
            _writer.WriteDeclaration();
        }
 
        private void FinishDocument()
        {
            if (_writeState == WriteState.Attribute)
            {
                WriteEndAttribute();
            }
 
            while (_depth > 0)
            {
                WriteEndElement();
            }
        }
 
        public override void WriteEndDocument()
        {
            if (IsClosed)
                ThrowClosed();
 
            if (_writeState == WriteState.Start || _writeState == WriteState.Prolog)
                throw new InvalidOperationException(SR.XmlNoRootElement);
 
            FinishDocument();
            _writeState = WriteState.Start;
            _documentState = DocumentState.End;
        }
 
        protected int NamespaceBoundary
        {
            get
            {
                return _nsMgr.NamespaceBoundary;
            }
            set
            {
                _nsMgr.NamespaceBoundary = value;
            }
        }
 
        public override void WriteEntityRef(string name)
        {
            throw new NotSupportedException(SR.Format(SR.XmlMethodNotSupported, "WriteEntityRef"));
        }
 
        public override void WriteName(string name)
        {
            if (IsClosed)
                ThrowClosed();
 
            WriteString(name);
        }
 
        public override void WriteNmToken(string name)
        {
            throw new NotSupportedException(SR.Format(SR.XmlMethodNotSupported, "WriteNmToken"));
        }
 
        public override void WriteWhitespace(string? whitespace)
        {
            if (IsClosed)
                ThrowClosed();
 
            ArgumentNullException.ThrowIfNull(whitespace);
 
            if (whitespace.AsSpan().ContainsAnyExcept(" \t\r\n"))
            {
                throw new ArgumentException(SR.XmlOnlyWhitespace, nameof(whitespace));
            }
 
            WriteString(whitespace);
        }
 
        public override void WriteString(string? value)
        {
            if (IsClosed)
                ThrowClosed();
 
            value ??= string.Empty;
 
            if (value.Length > 0 || _inList)
            {
                FlushBase64();
 
                if (_attributeValue != null)
                    WriteAttributeText(value);
 
                if (!_isXmlnsAttribute)
                {
                    StartContent(value);
                    _writer.WriteEscapedText(value);
                    EndContent();
                }
            }
        }
 
        public override void WriteString(XmlDictionaryString? value)
        {
            if (IsClosed)
                ThrowClosed();
 
            ArgumentNullException.ThrowIfNull(value);
 
            if (value.Value.Length > 0)
            {
                FlushBase64();
 
                if (_attributeValue != null)
                    WriteAttributeText(value.Value);
 
                if (!_isXmlnsAttribute)
                {
                    StartContent(value.Value);
                    _writer.WriteEscapedText(value);
                    EndContent();
                }
            }
        }
 
        public override void WriteChars(char[] chars, int offset, int count)
        {
            if (IsClosed)
                ThrowClosed();
 
            ArgumentNullException.ThrowIfNull(chars);
 
            // Not checking upper bound because it will be caught by "count".  This is what XmlTextWriter does.
            ArgumentOutOfRangeException.ThrowIfNegative(offset);
            ArgumentOutOfRangeException.ThrowIfNegative(count);
            if (count > chars.Length - offset)
                throw new ArgumentOutOfRangeException(nameof(count), SR.Format(SR.SizeExceedsRemainingBufferSpace, chars.Length - offset));
 
            if (count > 0)
            {
                FlushBase64();
 
                if (_attributeValue != null)
                    WriteAttributeText(new string(chars, offset, count));
 
                if (!_isXmlnsAttribute)
                {
                    StartContent(chars, offset, count);
                    _writer.WriteEscapedText(chars, offset, count);
                    EndContent();
                }
            }
        }
 
        public override void WriteRaw(string value)
        {
            if (IsClosed)
                ThrowClosed();
 
            value ??= string.Empty;
 
            if (value.Length > 0)
            {
                FlushBase64();
 
                if (_attributeValue != null)
                    WriteAttributeText(value);
 
                if (!_isXmlnsAttribute)
                {
                    StartContent(value);
                    _writer.WriteText(value);
                    EndContent();
                }
            }
        }
 
        public override void WriteRaw(char[] chars, int offset, int count)
        {
            if (IsClosed)
                ThrowClosed();
 
            ArgumentNullException.ThrowIfNull(chars);
 
            // Not checking upper bound because it will be caught by "count".  This is what XmlTextWriter does.
            ArgumentOutOfRangeException.ThrowIfNegative(offset);
            ArgumentOutOfRangeException.ThrowIfNegative(count);
            if (count > chars.Length - offset)
                throw new ArgumentOutOfRangeException(nameof(count), SR.Format(SR.SizeExceedsRemainingBufferSpace, chars.Length - offset));
 
            if (count > 0)
            {
                FlushBase64();
 
                if (_attributeValue != null)
                    WriteAttributeText(new string(chars, offset, count));
 
                if (!_isXmlnsAttribute)
                {
                    StartContent(chars, offset, count);
                    _writer.WriteText(chars, offset, count);
                    EndContent();
                }
            }
        }
 
        public override void WriteCharEntity(char ch)
        {
            if (IsClosed)
                ThrowClosed();
 
            if (ch >= 0xd800 && ch <= 0xdfff)
                throw new ArgumentException(SR.XmlMissingLowSurrogate, nameof(ch));
 
            if (_attributeValue != null)
                WriteAttributeText(ch.ToString());
 
            if (!_isXmlnsAttribute)
            {
                StartContent(ch);
                FlushBase64();
                _writer.WriteCharEntity(ch);
                EndContent();
            }
        }
 
        public override void WriteSurrogateCharEntity(char lowChar, char highChar)
        {
            if (IsClosed)
                ThrowClosed();
 
            SurrogateChar ch = new SurrogateChar(lowChar, highChar);
 
            if (_attributeValue != null)
            {
                WriteAttributeText(new string([highChar, lowChar]));
            }
 
            if (!_isXmlnsAttribute)
            {
                StartContent();
                FlushBase64();
                _writer.WriteCharEntity(ch.Char);
                EndContent();
            }
        }
 
        public override void WriteValue(object value)
        {
            if (IsClosed)
                ThrowClosed();
 
            ArgumentNullException.ThrowIfNull(value);
 
            if (value is object[])
            {
                WriteValue((object[])value);
            }
            else if (value is Array)
            {
                WriteValue((Array)value);
            }
            else if (value is IStreamProvider)
            {
                WriteValue((IStreamProvider)value);
            }
            else
            {
                WritePrimitiveValue(value);
            }
        }
 
        protected void WritePrimitiveValue(object value)
        {
            if (IsClosed)
                ThrowClosed();
 
            ArgumentNullException.ThrowIfNull(value);
 
            if (value is ulong)
            {
                WriteValue((ulong)value);
            }
            else if (value is string)
            {
                WriteValue((string)value);
            }
            else if (value is int)
            {
                WriteValue((int)value);
            }
            else if (value is long)
            {
                WriteValue((long)value);
            }
            else if (value is bool)
            {
                WriteValue((bool)value);
            }
            else if (value is double)
            {
                WriteValue((double)value);
            }
            else if (value is DateTime)
            {
                WriteValue((DateTime)value);
            }
            else if (value is float)
            {
                WriteValue((float)value);
            }
            else if (value is decimal)
            {
                WriteValue((decimal)value);
            }
            else if (value is XmlDictionaryString)
            {
                WriteValue((XmlDictionaryString)value);
            }
            else if (value is UniqueId)
            {
                WriteValue((UniqueId)value);
            }
            else if (value is Guid)
            {
                WriteValue((Guid)value);
            }
            else if (value is TimeSpan)
            {
                WriteValue((TimeSpan)value);
            }
            else if (value.GetType().IsArray)
            {
                throw new ArgumentException(SR.XmlNestedArraysNotSupported, nameof(value));
            }
            else
            {
                base.WriteValue(value);
            }
        }
 
        public override void WriteValue(string? value)
        {
            if (IsClosed)
                ThrowClosed();
 
            WriteString(value);
        }
 
        public override void WriteValue(int value)
        {
            if (IsClosed)
                ThrowClosed();
 
            FlushBase64();
            if (_attributeValue != null)
                WriteAttributeText(XmlConverter.ToString(value));
 
            if (!_isXmlnsAttribute)
            {
                StartContent();
                _writer.WriteInt32Text(value);
                EndContent();
            }
        }
 
        public override void WriteValue(long value)
        {
            if (IsClosed)
                ThrowClosed();
 
            FlushBase64();
            if (_attributeValue != null)
                WriteAttributeText(XmlConverter.ToString(value));
 
            if (!_isXmlnsAttribute)
            {
                StartContent();
                _writer.WriteInt64Text(value);
                EndContent();
            }
        }
 
        private void WriteValue(ulong value)
        {
            if (IsClosed)
                ThrowClosed();
 
            FlushBase64();
            if (_attributeValue != null)
                WriteAttributeText(XmlConverter.ToString(value));
 
            if (!_isXmlnsAttribute)
            {
                StartContent();
                _writer.WriteUInt64Text(value);
                EndContent();
            }
        }
 
        public override void WriteValue(bool value)
        {
            if (IsClosed)
                ThrowClosed();
 
            FlushBase64();
            if (_attributeValue != null)
                WriteAttributeText(XmlConverter.ToString(value));
 
            if (!_isXmlnsAttribute)
            {
                StartContent();
                _writer.WriteBoolText(value);
                EndContent();
            }
        }
 
        public override void WriteValue(decimal value)
        {
            if (IsClosed)
                ThrowClosed();
 
            FlushBase64();
            if (_attributeValue != null)
                WriteAttributeText(XmlConverter.ToString(value));
 
            if (!_isXmlnsAttribute)
            {
                StartContent();
                _writer.WriteDecimalText(value);
                EndContent();
            }
        }
 
        public override void WriteValue(float value)
        {
            if (IsClosed)
                ThrowClosed();
 
            FlushBase64();
            if (_attributeValue != null)
                WriteAttributeText(XmlConverter.ToString(value));
 
            if (!_isXmlnsAttribute)
            {
                StartContent();
                _writer.WriteFloatText(value);
                EndContent();
            }
        }
 
        public override void WriteValue(double value)
        {
            if (IsClosed)
                ThrowClosed();
 
            FlushBase64();
            if (_attributeValue != null)
                WriteAttributeText(XmlConverter.ToString(value));
 
            if (!_isXmlnsAttribute)
            {
                StartContent();
                _writer.WriteDoubleText(value);
                EndContent();
            }
        }
 
        public override void WriteValue(XmlDictionaryString? value)
        {
            WriteString(value);
        }
 
        public override void WriteValue(DateTime value)
        {
            if (IsClosed)
                ThrowClosed();
 
            FlushBase64();
            if (_attributeValue != null)
                WriteAttributeText(XmlConverter.ToString(value));
 
            if (!_isXmlnsAttribute)
            {
                StartContent();
                _writer.WriteDateTimeText(value);
                EndContent();
            }
        }
 
        public override void WriteValue(UniqueId value)
        {
            if (IsClosed)
                ThrowClosed();
 
            ArgumentNullException.ThrowIfNull(value);
 
            FlushBase64();
            if (_attributeValue != null)
                WriteAttributeText(XmlConverter.ToString(value));
 
            if (!_isXmlnsAttribute)
            {
                StartContent();
                _writer.WriteUniqueIdText(value);
                EndContent();
            }
        }
 
        public override void WriteValue(Guid value)
        {
            if (IsClosed)
                ThrowClosed();
 
            FlushBase64();
            if (_attributeValue != null)
                WriteAttributeText(XmlConverter.ToString(value));
 
            if (!_isXmlnsAttribute)
            {
                StartContent();
                _writer.WriteGuidText(value);
                EndContent();
            }
        }
 
        public override void WriteValue(TimeSpan value)
        {
            if (IsClosed)
                ThrowClosed();
 
            FlushBase64();
            if (_attributeValue != null)
                WriteAttributeText(XmlConverter.ToString(value));
 
            if (!_isXmlnsAttribute)
            {
                StartContent();
                _writer.WriteTimeSpanText(value);
                EndContent();
            }
        }
 
        private static void EnsureBufferBounds(byte[] buffer, int offset, int count)
        {
            ArgumentNullException.ThrowIfNull(buffer);
 
            // Not checking upper bound because it will be caught by "count".  This is what XmlTextWriter does.
            ArgumentOutOfRangeException.ThrowIfNegative(offset);
            ArgumentOutOfRangeException.ThrowIfNegative(count);
            if (count > buffer.Length - offset)
                throw new ArgumentOutOfRangeException(nameof(count), SR.Format(SR.SizeExceedsRemainingBufferSpace, buffer.Length - offset));
        }
 
        public override void WriteBinHex(byte[] buffer, int offset, int count)
        {
            if (IsClosed)
                ThrowClosed();
 
            EnsureBufferBounds(buffer, offset, count);
 
            WriteRaw(DataContractSerializer.BinHexEncoding.GetString(buffer, offset, count));
        }
 
        public override void WriteBase64(byte[] buffer, int offset, int count)
        {
            if (IsClosed)
                ThrowClosed();
 
            EnsureBufferBounds(buffer, offset, count);
 
            if (count > 0)
            {
                if (_trailByteCount > 0)
                {
                    while (_trailByteCount < 3 && count > 0)
                    {
                        _trailBytes![_trailByteCount++] = buffer[offset++];
                        count--;
                    }
                }
 
                int totalByteCount = _trailByteCount + count;
                int actualByteCount = totalByteCount - (totalByteCount % 3);
 
                _trailBytes ??= new byte[3];
 
                if (actualByteCount >= 3)
                {
                    if (_attributeValue != null)
                    {
                        WriteAttributeText(DataContractSerializer.Base64Encoding.GetString(_trailBytes, 0, _trailByteCount));
                        WriteAttributeText(DataContractSerializer.Base64Encoding.GetString(buffer, offset, actualByteCount - _trailByteCount));
                    }
 
                    if (!_isXmlnsAttribute)
                    {
                        StartContent();
                        _writer.WriteBase64Text(_trailBytes, _trailByteCount, buffer, offset, actualByteCount - _trailByteCount);
                        EndContent();
                    }
                    _trailByteCount = (totalByteCount - actualByteCount);
 
                    if (_trailByteCount > 0)
                    {
                        int trailOffset = offset + count - _trailByteCount;
                        for (int i = 0; i < _trailByteCount; i++)
                            _trailBytes[i] = buffer[trailOffset++];
                    }
                }
                else
                {
                    Buffer.BlockCopy(buffer, offset, _trailBytes, _trailByteCount, count);
                    _trailByteCount += count;
                }
            }
        }
 
        public override Task WriteBase64Async(byte[] buffer, int offset, int count)
        {
            if (IsClosed)
                ThrowClosed();
 
            EnsureBufferBounds(buffer, offset, count);
 
            return WriteBase64AsyncImpl(buffer, offset, count);
        }
 
        private async Task WriteBase64AsyncImpl(byte[] buffer, int offset, int count)
        {
            if (count > 0)
            {
                if (_trailByteCount > 0)
                {
                    while (_trailByteCount < 3 && count > 0)
                    {
                        _trailBytes![_trailByteCount++] = buffer[offset++];
                        count--;
                    }
                }
 
                int totalByteCount = _trailByteCount + count;
                int actualByteCount = totalByteCount - (totalByteCount % 3);
 
                _trailBytes ??= new byte[3];
 
                if (actualByteCount >= 3)
                {
                    if (_attributeValue != null)
                    {
                        WriteAttributeText(DataContractSerializer.Base64Encoding.GetString(_trailBytes, 0, _trailByteCount));
                        WriteAttributeText(DataContractSerializer.Base64Encoding.GetString(buffer, offset, actualByteCount - _trailByteCount));
                    }
                    if (!_isXmlnsAttribute)
                    {
                        await StartContentAsync().ConfigureAwait(false);
                        await _writer.WriteBase64TextAsync(_trailBytes, _trailByteCount, buffer, offset, actualByteCount - _trailByteCount).ConfigureAwait(false);
                        EndContent();
                    }
                    _trailByteCount = (totalByteCount - actualByteCount);
                    if (_trailByteCount > 0)
                    {
                        int trailOffset = offset + count - _trailByteCount;
                        for (int i = 0; i < _trailByteCount; i++)
                            _trailBytes[i] = buffer[trailOffset++];
                    }
                }
                else
                {
                    Buffer.BlockCopy(buffer, offset, _trailBytes, _trailByteCount, count);
                    _trailByteCount += count;
                }
            }
        }
 
 
        public override bool CanCanonicalize
        {
            get
            {
                return true;
            }
        }
 
        protected bool Signing
        {
            get
            {
                return _writer == _signingWriter;
            }
        }
 
        public override void StartCanonicalization(Stream stream, bool includeComments, string[]? inclusivePrefixes)
        {
            if (IsClosed)
                ThrowClosed();
            if (Signing)
                throw new InvalidOperationException(SR.XmlCanonicalizationStarted);
            FlushElement();
            _signingWriter ??= CreateSigningNodeWriter();
            _signingWriter.SetOutput(_writer, stream, includeComments, inclusivePrefixes);
            _writer = _signingWriter;
            SignScope(_signingWriter.CanonicalWriter);
        }
 
        public override void EndCanonicalization()
        {
            if (IsClosed)
                ThrowClosed();
            if (!Signing)
                throw new InvalidOperationException(SR.XmlCanonicalizationNotStarted);
            _signingWriter!.Flush();
            _writer = _signingWriter.NodeWriter;
        }
 
        protected abstract XmlSigningNodeWriter CreateSigningNodeWriter();
 
        public virtual bool CanFragment
        {
            get
            {
                return true;
            }
        }
 
        public void StartFragment(Stream stream, bool generateSelfContainedTextFragment)
        {
            if (!CanFragment)
                throw new NotSupportedException();
 
            if (IsClosed)
                ThrowClosed();
 
            ArgumentNullException.ThrowIfNull(stream);
 
            if (_oldStream != null || _oldWriter != null)
                throw new InvalidOperationException();
            if (WriteState == WriteState.Attribute)
                throw new InvalidOperationException(SR.Format(SR.XmlInvalidWriteState, "StartFragment", WriteState.ToString()));
 
            FlushElement();
            _writer.Flush();
 
            _oldNamespaceBoundary = NamespaceBoundary;
 
            XmlStreamNodeWriter? fragmentWriter = null;
            if (generateSelfContainedTextFragment)
            {
                this.NamespaceBoundary = _depth + 1;
                _textFragmentWriter ??= new XmlUTF8NodeWriter();
                _textFragmentWriter.SetOutput(stream, false, Encoding.UTF8);
                fragmentWriter = _textFragmentWriter;
            }
 
            if (Signing)
            {
                if (fragmentWriter != null)
                {
                    _oldWriter = _signingWriter!.NodeWriter;
                    _signingWriter.NodeWriter = fragmentWriter;
                }
                else
                {
                    _oldStream = ((XmlStreamNodeWriter)_signingWriter!.NodeWriter).OutputStream;
                    ((XmlStreamNodeWriter)_signingWriter.NodeWriter).OutputStream = stream;
                }
            }
            else
            {
                if (fragmentWriter != null)
                {
                    _oldWriter = _writer;
                    _writer = fragmentWriter;
                }
                else
                {
                    _oldStream = _nodeWriter.OutputStream;
                    _nodeWriter.OutputStream = stream;
                }
            }
        }
 
        public void EndFragment()
        {
            if (IsClosed)
                ThrowClosed();
 
            if (_oldStream == null && _oldWriter == null)
                throw new InvalidOperationException();
            if (WriteState == WriteState.Attribute)
                throw new InvalidOperationException(SR.Format(SR.XmlInvalidWriteState, "EndFragment", WriteState.ToString()));
 
            FlushElement();
            _writer.Flush();
 
            if (Signing)
            {
                if (_oldWriter != null)
                    _signingWriter!.NodeWriter = _oldWriter;
                else
                    ((XmlStreamNodeWriter)_signingWriter!.NodeWriter).OutputStream = _oldStream!; // Checked above. _oldStream can't be null if _oldWriter is.
            }
            else
            {
                if (_oldWriter != null)
                    _writer = _oldWriter;
                else
                    _nodeWriter.OutputStream = _oldStream!; // Checked above. _oldStream can't be null if _oldWriter is.
            }
            NamespaceBoundary = _oldNamespaceBoundary;
            _oldWriter = null;
            _oldStream = null;
        }
 
        public void WriteFragment(byte[] buffer, int offset, int count)
        {
            if (!CanFragment)
                throw new NotSupportedException();
 
            if (IsClosed)
                ThrowClosed();
 
            EnsureBufferBounds(buffer, offset, count);
 
            if (WriteState == WriteState.Attribute)
                throw new InvalidOperationException(SR.Format(SR.XmlInvalidWriteState, "WriteFragment", WriteState.ToString()));
 
            if (_writer != _nodeWriter)
                throw new InvalidOperationException();
 
            FlushElement();
            FlushBase64();
            _nodeWriter.Flush();
            _nodeWriter.OutputStream.Write(buffer, offset, count);
        }
 
        private void FlushBase64()
        {
            if (_trailByteCount > 0)
            {
                FlushTrailBytes();
            }
        }
 
        private Task FlushBase64Async()
        {
            return _trailByteCount > 0 ? FlushTrailBytesAsync() : Task.CompletedTask;
        }
 
        private void FlushTrailBytes()
        {
            Debug.Assert(_trailBytes != null);
 
            if (_attributeValue != null)
                WriteAttributeText(DataContractSerializer.Base64Encoding.GetString(_trailBytes, 0, _trailByteCount));
 
            if (!_isXmlnsAttribute)
            {
                StartContent();
                _writer.WriteBase64Text(_trailBytes, _trailByteCount, _trailBytes, 0, 0);
                EndContent();
            }
            _trailByteCount = 0;
        }
 
        private async Task FlushTrailBytesAsync()
        {
            Debug.Assert(_trailBytes != null);
 
            if (_attributeValue != null)
                WriteAttributeText(DataContractSerializer.Base64Encoding.GetString(_trailBytes, 0, _trailByteCount));
 
            if (!_isXmlnsAttribute)
            {
                await StartContentAsync().ConfigureAwait(false);
                await _writer.WriteBase64TextAsync(_trailBytes, _trailByteCount, _trailBytes, 0, 0).ConfigureAwait(false);
                EndContent();
            }
            _trailByteCount = 0;
        }
 
        private void WriteValue(object[] array)
        {
            FlushBase64();
            StartContent();
            _writer.WriteStartListText();
            _inList = true;
            for (int i = 0; i < array.Length; i++)
            {
                if (i != 0)
                {
                    _writer.WriteListSeparator();
                }
                WritePrimitiveValue(array[i]);
            }
            _inList = false;
            _writer.WriteEndListText();
            EndContent();
        }
 
        private void WriteValue(Array array)
        {
            FlushBase64();
            StartContent();
            _writer.WriteStartListText();
            _inList = true;
            for (int i = 0; i < array.Length; i++)
            {
                if (i != 0)
                {
                    _writer.WriteListSeparator();
                }
                WritePrimitiveValue(array.GetValue(i)!); // possible bug to log?
            }
            _inList = false;
            _writer.WriteEndListText();
            EndContent();
        }
 
        protected void StartArray(int count)
        {
            FlushBase64();
            if (_documentState == DocumentState.Epilog)
                throw new InvalidOperationException(SR.XmlOnlyOneRoot);
            if (_documentState == DocumentState.Document && count > 1 && _depth == 0)
                throw new InvalidOperationException(SR.XmlOnlyOneRoot);
            if (_writeState == WriteState.Attribute)
                throw new InvalidOperationException(SR.Format(SR.XmlInvalidWriteState, "WriteStartElement", WriteState.ToString()));
            AutoComplete(WriteState.Content);
        }
 
        private string GeneratePrefix(string ns, XmlDictionaryString? xNs)
        {
            if (_writeState != WriteState.Element && _writeState != WriteState.Attribute)
                throw new InvalidOperationException(SR.Format(SR.XmlInvalidPrefixState, WriteState.ToString()));
 
            string? prefix = _nsMgr.AddNamespace(ns, xNs);
 
            if (prefix != null)
                return prefix;
 
            while (true)
            {
                int prefixId = _elements![_depth].PrefixId++;
                prefix = string.Create(CultureInfo.InvariantCulture, $"d{_depth}p{prefixId}");
 
                if (_nsMgr.LookupNamespace(prefix) == null)
                {
                    _nsMgr.AddNamespace(prefix, ns, xNs);
                    return prefix;
                }
            }
        }
 
        protected void SignScope(XmlCanonicalWriter signingWriter)
        {
            _nsMgr.Sign(signingWriter);
        }
 
        private void WriteAttributeText(string value)
        {
            if (_attributeValue!.Length == 0)
                _attributeValue = value;
            else
                _attributeValue += value;
        }
 
        private sealed class Element
        {
            private string? _prefix;
            private string? _localName;
            private int _prefixId;
 
            public string? Prefix
            {
                get
                {
                    return _prefix;
                }
                set
                {
                    _prefix = value;
                }
            }
 
            public string? LocalName
            {
                get
                {
                    return _localName;
                }
                set
                {
                    _localName = value;
                }
            }
 
            public int PrefixId
            {
                get
                {
                    return _prefixId;
                }
                set
                {
                    _prefixId = value;
                }
            }
 
            public void Clear()
            {
                _prefix = null;
                _localName = null;
                _prefixId = 0;
            }
        }
 
        private enum DocumentState : byte
        {
            None,       // Not inside StartDocument/EndDocument - Allows multiple root elements
            Document,   // Inside StartDocument/EndDocument
            Epilog,     // EndDocument must be called
            End         // Nothing further to write
        }
 
        private sealed class NamespaceManager
        {
            private Namespace[]? _namespaces;
            private Namespace? _lastNameSpace;
            private int _nsCount;
            private int _depth;
            private XmlAttribute[]? _attributes;
            private int _attributeCount;
            private XmlSpace _space;
            private string? _lang;
            private int _namespaceBoundary;
            private int _nsTop;
            private readonly Namespace _defaultNamespace;
 
            public NamespaceManager()
            {
                _defaultNamespace = new Namespace();
                _defaultNamespace.Depth = 0;
                _defaultNamespace.Prefix = string.Empty;
                _defaultNamespace.Uri = string.Empty;
                _defaultNamespace.UriDictionaryString = null;
            }
 
            public string? XmlLang
            {
                get
                {
                    return _lang;
                }
            }
 
            public XmlSpace XmlSpace
            {
                get
                {
                    return _space;
                }
            }
 
            public void Clear()
            {
                if (_namespaces == null)
                {
                    _namespaces = new Namespace[4];
                    _namespaces[0] = _defaultNamespace;
                }
                _nsCount = 1;
                _nsTop = 0;
                _depth = 0;
                _attributeCount = 0;
                _space = XmlSpace.None;
                _lang = null;
                _namespaceBoundary = 0;
                _lastNameSpace = null;
            }
 
            public int NamespaceBoundary
            {
                get
                {
                    return _namespaceBoundary;
                }
                set
                {
                    int i;
                    for (i = 0; i < _nsCount; i++)
                        if (_namespaces![i].Depth >= value)
                            break;
 
                    _nsTop = i;
                    _namespaceBoundary = value;
                    _lastNameSpace = null;
                }
            }
 
            public void Close()
            {
                if (_depth == 0)
                {
                    if (_namespaces != null && _namespaces.Length > 32)
                        _namespaces = null;
                    if (_attributes != null && _attributes.Length > 4)
                        _attributes = null;
                }
                else
                {
                    _namespaces = null;
                    _attributes = null;
                }
                _lang = null;
            }
 
            public void DeclareNamespaces(XmlNodeWriter writer)
            {
                int i = _nsCount;
                while (i > 0)
                {
                    Namespace nameSpace = _namespaces![i - 1];
                    if (nameSpace.Depth != _depth)
                        break;
                    i--;
                }
                while (i < _nsCount)
                {
                    Namespace nameSpace = _namespaces![i];
                    if (nameSpace.UriDictionaryString != null)
                        writer.WriteXmlnsAttribute(nameSpace.Prefix, nameSpace.UriDictionaryString);
                    else
                        writer.WriteXmlnsAttribute(nameSpace.Prefix, nameSpace.Uri!);
                    i++;
                }
            }
 
            public void EnterScope()
            {
                _depth++;
            }
 
            public void ExitScope()
            {
                while (_nsCount > 0)
                {
                    Namespace nameSpace = _namespaces![_nsCount - 1];
                    if (nameSpace.Depth != _depth)
                        break;
                    if (_lastNameSpace == nameSpace)
                        _lastNameSpace = null;
                    nameSpace.Clear();
                    _nsCount--;
                }
                while (_attributeCount > 0)
                {
                    XmlAttribute attribute = _attributes![_attributeCount - 1];
                    if (attribute.Depth != _depth)
                        break;
                    _space = attribute.XmlSpace;
                    _lang = attribute.XmlLang;
                    attribute.Clear();
                    _attributeCount--;
                }
                _depth--;
            }
 
            public void AddLangAttribute(string lang)
            {
                AddAttribute();
                _lang = lang;
            }
 
            public void AddSpaceAttribute(XmlSpace space)
            {
                AddAttribute();
                _space = space;
            }
 
            private void AddAttribute()
            {
                if (_attributes == null)
                {
                    _attributes = new XmlAttribute[1];
                }
                else if (_attributes.Length == _attributeCount)
                {
                    XmlAttribute[] newAttributes = new XmlAttribute[_attributeCount * 2];
                    Array.Copy(_attributes, newAttributes, _attributeCount);
                    _attributes = newAttributes;
                }
                XmlAttribute attribute = _attributes[_attributeCount];
                if (attribute == null)
                {
                    attribute = new XmlAttribute();
                    _attributes[_attributeCount] = attribute;
                }
                attribute.XmlLang = _lang;
                attribute.XmlSpace = _space;
                attribute.Depth = _depth;
                _attributeCount++;
            }
 
            public string? AddNamespace(string uri, XmlDictionaryString? uriDictionaryString)
            {
                if (uri.Length == 0)
                {
                    // Empty namespace can only be bound to the empty prefix
                    AddNamespaceIfNotDeclared(string.Empty, uri, uriDictionaryString);
                    return string.Empty;
                }
                else
                {
                    for (int i = 0; i < s_prefixes.Length; i++)
                    {
                        string prefix = s_prefixes[i];
                        bool declared = false;
                        for (int j = _nsCount - 1; j >= _nsTop; j--)
                        {
                            Namespace nameSpace = _namespaces![j];
                            if (nameSpace.Prefix == prefix)
                            {
                                declared = true;
                                break;
                            }
                        }
                        if (!declared)
                        {
                            AddNamespace(prefix, uri, uriDictionaryString);
                            return prefix;
                        }
                    }
                }
                return null;
            }
 
            public void AddNamespaceIfNotDeclared(string prefix, string uri, XmlDictionaryString? uriDictionaryString)
            {
                if (LookupNamespace(prefix) != uri)
                {
                    AddNamespace(prefix, uri, uriDictionaryString);
                }
            }
 
            public void AddNamespace(string prefix, string uri, XmlDictionaryString? uriDictionaryString)
            {
                if (prefix.Length >= 3)
                {
                    // Upper and lower case letter differ by a bit.
                    if ((prefix[0] & ~32) == 'X' && (prefix[1] & ~32) == 'M' && (prefix[2] & ~32) == 'L')
                    {
                        if (prefix == "xml" && uri == xmlNamespace)
                            return;
                        if (prefix == "xmlns" && uri == xmlnsNamespace)
                            return;
                        throw new ArgumentException(SR.XmlReservedPrefix, nameof(prefix));
                    }
                }
                Namespace nameSpace;
                for (int i = _nsCount - 1; i >= 0; i--)
                {
                    nameSpace = _namespaces![i];
                    if (nameSpace.Depth != _depth)
                        break;
                    if (nameSpace.Prefix == prefix)
                    {
                        if (nameSpace.Uri == uri)
                            return;
                        throw new ArgumentException(SR.Format(SR.XmlPrefixBoundToNamespace, prefix, nameSpace.Uri, uri), nameof(prefix));
                    }
                }
                if (prefix.Length != 0 && uri.Length == 0)
                    throw new ArgumentException(SR.XmlEmptyNamespaceRequiresNullPrefix, nameof(prefix));
                if (uri.Length == xmlnsNamespace.Length && uri == xmlnsNamespace)
                    throw new ArgumentException(SR.Format(SR.XmlSpecificBindingNamespace, "xmlns", uri));
                // The addressing namespace and the xmlNamespace are the same length, so add a quick check to try to disambiguate
                if (uri.Length == xmlNamespace.Length && uri[18] == 'X' && uri == xmlNamespace)
                    throw new ArgumentException(SR.Format(SR.XmlSpecificBindingNamespace, "xml", uri));
 
                if (_namespaces!.Length == _nsCount)
                {
                    Namespace[] newNamespaces = new Namespace[_nsCount * 2];
                    Array.Copy(_namespaces, newNamespaces, _nsCount);
                    _namespaces = newNamespaces;
                }
                nameSpace = _namespaces[_nsCount];
                if (nameSpace == null)
                {
                    nameSpace = new Namespace();
                    _namespaces[_nsCount] = nameSpace;
                }
                nameSpace.Depth = _depth;
                nameSpace.Prefix = prefix;
                nameSpace.Uri = uri;
                nameSpace.UriDictionaryString = uriDictionaryString;
                _nsCount++;
                _lastNameSpace = null;
            }
 
            public string? LookupPrefix(string ns)
            {
                if (_lastNameSpace != null && _lastNameSpace.Uri == ns)
                    return _lastNameSpace.Prefix;
                int nsCount = _nsCount;
                for (int i = nsCount - 1; i >= _nsTop; i--)
                {
                    Namespace nameSpace = _namespaces![i];
                    if (object.ReferenceEquals(nameSpace.Uri, ns))
                    {
                        string? prefix = nameSpace.Prefix;
                        // Make sure that the prefix refers to the namespace in scope
                        bool declared = false;
                        for (int j = i + 1; j < nsCount; j++)
                        {
                            if (_namespaces[j].Prefix == prefix)
                            {
                                declared = true;
                                break;
                            }
                        }
                        if (!declared)
                        {
                            _lastNameSpace = nameSpace;
                            return prefix;
                        }
                    }
                }
                for (int i = nsCount - 1; i >= _nsTop; i--)
                {
                    Namespace nameSpace = _namespaces![i];
                    if (nameSpace.Uri == ns)
                    {
                        string? prefix = nameSpace.Prefix;
                        // Make sure that the prefix refers to the namespace in scope
                        bool declared = false;
                        for (int j = i + 1; j < nsCount; j++)
                        {
                            if (_namespaces[j].Prefix == prefix)
                            {
                                declared = true;
                                break;
                            }
                        }
                        if (!declared)
                        {
                            _lastNameSpace = nameSpace;
                            return prefix;
                        }
                    }
                }
 
                if (ns.Length == 0)
                {
                    // Make sure the default binding is still valid
                    bool emptyPrefixUnassigned = true;
                    for (int i = nsCount - 1; i >= _nsTop; i--)
                    {
                        if (_namespaces![i].Prefix!.Length == 0)
                        {
                            emptyPrefixUnassigned = false;
                            break;
                        }
                    }
                    if (emptyPrefixUnassigned)
                        return string.Empty;
                }
 
                if (ns == xmlnsNamespace)
                    return "xmlns";
                if (ns == xmlNamespace)
                    return "xml";
                return null;
            }
 
            public string? LookupAttributePrefix(string ns)
            {
                if (_lastNameSpace != null && _lastNameSpace.Uri == ns && _lastNameSpace.Prefix!.Length != 0)
                    return _lastNameSpace.Prefix;
 
                int nsCount = _nsCount;
                for (int i = nsCount - 1; i >= _nsTop; i--)
                {
                    Namespace nameSpace = _namespaces![i];
 
                    if (object.ReferenceEquals(nameSpace.Uri, ns))
                    {
                        string prefix = nameSpace.Prefix!;
                        if (prefix.Length != 0)
                        {
                            // Make sure that the prefix refers to the namespace in scope
                            bool declared = false;
                            for (int j = i + 1; j < nsCount; j++)
                            {
                                if (_namespaces[j].Prefix == prefix)
                                {
                                    declared = true;
                                    break;
                                }
                            }
                            if (!declared)
                            {
                                _lastNameSpace = nameSpace;
                                return prefix;
                            }
                        }
                    }
                }
                for (int i = nsCount - 1; i >= _nsTop; i--)
                {
                    Namespace nameSpace = _namespaces![i];
                    if (nameSpace.Uri == ns)
                    {
                        string prefix = nameSpace.Prefix!;
                        if (prefix.Length != 0)
                        {
                            // Make sure that the prefix refers to the namespace in scope
                            bool declared = false;
                            for (int j = i + 1; j < nsCount; j++)
                            {
                                if (_namespaces[j].Prefix == prefix)
                                {
                                    declared = true;
                                    break;
                                }
                            }
                            if (!declared)
                            {
                                _lastNameSpace = nameSpace;
                                return prefix;
                            }
                        }
                    }
                }
                if (ns.Length == 0)
                    return string.Empty;
                return null;
            }
 
            public string? LookupNamespace(string prefix)
            {
                int nsCount = _nsCount;
                if (prefix.Length == 0)
                {
                    for (int i = nsCount - 1; i >= _nsTop; i--)
                    {
                        Namespace nameSpace = _namespaces![i];
                        if (nameSpace.Prefix!.Length == 0)
                            return nameSpace.Uri;
                    }
                    return string.Empty;
                }
                if (prefix.Length == 1)
                {
                    char prefixChar = prefix[0];
                    for (int i = nsCount - 1; i >= _nsTop; i--)
                    {
                        Namespace nameSpace = _namespaces![i];
                        if (nameSpace.PrefixChar == prefixChar)
                            return nameSpace.Uri;
                    }
                    return null;
                }
                for (int i = nsCount - 1; i >= _nsTop; i--)
                {
                    Namespace nameSpace = _namespaces![i];
                    if (nameSpace.Prefix == prefix)
                        return nameSpace.Uri;
                }
                if (prefix == "xmlns")
                    return xmlnsNamespace;
                if (prefix == "xml")
                    return xmlNamespace;
                return null;
            }
 
            public void Sign(XmlCanonicalWriter signingWriter)
            {
                int nsCount = _nsCount;
                Debug.Assert(nsCount >= 1 && _namespaces![0].Prefix!.Length == 0 && _namespaces[0].Uri!.Length == 0);
                for (int i = 1; i < nsCount; i++)
                {
                    Namespace nameSpace = _namespaces[i];
 
                    bool found = false;
                    for (int j = i + 1; j < nsCount && !found; j++)
                    {
                        found = (nameSpace.Prefix == _namespaces[j].Prefix);
                    }
 
                    if (!found)
                    {
                        signingWriter.WriteXmlnsAttribute(nameSpace.Prefix!, nameSpace.Uri!);
                    }
                }
            }
 
            private sealed class XmlAttribute
            {
                private XmlSpace _space;
                private string? _lang;
                private int _depth;
 
                public XmlAttribute()
                {
                }
 
                public int Depth
                {
                    get
                    {
                        return _depth;
                    }
                    set
                    {
                        _depth = value;
                    }
                }
 
                public string? XmlLang
                {
                    get
                    {
                        return _lang;
                    }
                    set
                    {
                        _lang = value;
                    }
                }
 
                public XmlSpace XmlSpace
                {
                    get
                    {
                        return _space;
                    }
                    set
                    {
                        _space = value;
                    }
                }
 
                public void Clear()
                {
                    _lang = null;
                }
            }
 
            private sealed class Namespace
            {
                private string? _prefix;
                private string? _ns;
                private XmlDictionaryString? _xNs;
                private int _depth;
                private char _prefixChar;
 
                public Namespace()
                {
                }
 
                public void Clear()
                {
                    _prefix = null;
                    _prefixChar = (char)0;
                    _ns = null;
                    _xNs = null;
                    _depth = 0;
                }
 
                public int Depth
                {
                    get
                    {
                        return _depth;
                    }
                    set
                    {
                        _depth = value;
                    }
                }
 
                public char PrefixChar
                {
                    get
                    {
                        return _prefixChar;
                    }
                }
 
                [DisallowNull]
                public string? Prefix
                {
                    get
                    {
                        return _prefix;
                    }
                    set
                    {
                        if (value.Length == 1)
                            _prefixChar = value[0];
                        else
                            _prefixChar = (char)0;
                        _prefix = value;
                    }
                }
 
                public string? Uri
                {
                    get
                    {
                        return _ns;
                    }
                    set
                    {
                        _ns = value;
                    }
                }
 
                public XmlDictionaryString? UriDictionaryString
                {
                    get
                    {
                        return _xNs;
                    }
                    set
                    {
                        _xNs = value;
                    }
                }
            }
        }
    }
}