File: System\Xml\Core\XmlWriterSettings.cs
Web Access
Project: src\src\libraries\System.Private.Xml\src\System.Private.Xml.csproj (System.Private.Xml)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using System.Xml.Xsl.Runtime;
 
namespace System.Xml
{
    public enum XmlOutputMethod
    {
        Xml = 0,    // Use Xml 1.0 rules to serialize
        Html = 1,    // Use Html rules specified by Xslt specification to serialize
        Text = 2,    // Only serialize text blocks
        AutoDetect = 3,    // Choose between Xml and Html output methods at runtime (using Xslt rules to do so)
    }
 
    /// <summary>
    /// Three-state logic enumeration.
    /// </summary>
    internal enum TriState
    {
        Unknown = -1,
        False = 0,
        True = 1,
    };
 
    internal enum XmlStandalone
    {
        // Do not change the constants - XmlBinaryWriter depends in it
        Omit = 0,
        Yes = 1,
        No = 2,
    }
 
    // XmlWriterSettings class specifies basic features of an XmlWriter.
    public sealed class XmlWriterSettings
    {
        internal static readonly XmlWriterSettings s_defaultWriterSettings = new() { ReadOnly = true };
        private bool _useAsync;
        private Encoding _encoding;
        private bool _omitXmlDecl;
        private NewLineHandling _newLineHandling;
        private string _newLineChars;
        private string _indentChars;
        private bool _newLineOnAttributes;
        private bool _closeOutput;
        private NamespaceHandling _namespaceHandling;
        private ConformanceLevel _conformanceLevel;
        private bool _checkCharacters;
        private bool _writeEndDocumentOnClose;
        private bool _doNotEscapeUriAttributes;
        private bool _mergeCDataSections;
        private string? _mediaType;
        private string? _docTypeSystem;
        private string? _docTypePublic;
        private XmlStandalone _standalone;
        private bool _autoXmlDecl;
        public XmlWriterSettings()
        {
            Initialize();
        }
 
        public bool Async
        {
            get => _useAsync;
            set
            {
                CheckReadOnly();
                _useAsync = value;
            }
        }
 
        public Encoding Encoding
        {
            get => _encoding;
            [MemberNotNull(nameof(_encoding))]
            set
            {
                CheckReadOnly();
                _encoding = value;
            }
        }
 
        // True if an xml declaration should *not* be written.
        public bool OmitXmlDeclaration
        {
            get => _omitXmlDecl;
            set
            {
                CheckReadOnly();
                _omitXmlDecl = value;
            }
        }
 
        // See NewLineHandling enum for details.
        public NewLineHandling NewLineHandling
        {
            get => _newLineHandling;
            set
            {
                CheckReadOnly();
 
                if (unchecked((uint)value) > (uint)NewLineHandling.None)
                {
                    ThrowArgumentOutOfRangeException(nameof(value));
                }
 
                _newLineHandling = value;
            }
        }
 
        // Line terminator string. By default, this is a carriage return followed by a line feed ("\r\n").
        public string NewLineChars
        {
            get => _newLineChars;
            [MemberNotNull(nameof(_newLineChars))]
            set
            {
                CheckReadOnly();
                ArgumentNullException.ThrowIfNull(value);
                _newLineChars = value;
            }
        }
 
        // True if output should be indented using rules that are appropriate to the output rules (i.e. Xml, Html, etc).
        public bool Indent
        {
            get => IndentInternal == TriState.True;
            set
            {
                CheckReadOnly();
                IndentInternal = value ? TriState.True : TriState.False;
            }
        }
 
        // Characters to use when indenting. This is usually tab or some spaces, but can be anything.
        public string IndentChars
        {
            get => _indentChars;
            [MemberNotNull(nameof(_indentChars))]
            set
            {
                CheckReadOnly();
                ArgumentNullException.ThrowIfNull(value);
                _indentChars = value;
            }
        }
 
        // Whether or not indent attributes on new lines.
        public bool NewLineOnAttributes
        {
            get => _newLineOnAttributes;
            set
            {
                CheckReadOnly();
                _newLineOnAttributes = value;
            }
        }
 
        // Whether or not the XmlWriter should close the underlying stream or TextWriter when Close is called on the XmlWriter.
        public bool CloseOutput
        {
            get => _closeOutput;
            set
            {
                CheckReadOnly();
                _closeOutput = value;
            }
        }
 
        // Conformance
        // See ConformanceLevel enum for details.
        public ConformanceLevel ConformanceLevel
        {
            get => _conformanceLevel;
            set
            {
                CheckReadOnly();
 
                if (unchecked((uint)value) > (uint)ConformanceLevel.Document)
                {
                    ThrowArgumentOutOfRangeException(nameof(value));
                }
                _conformanceLevel = value;
            }
        }
 
        // Whether or not to check content characters that they are valid XML characters.
        public bool CheckCharacters
        {
            get => _checkCharacters;
            set
            {
                CheckReadOnly();
                _checkCharacters = value;
            }
        }
 
        // Whether or not to remove duplicate namespace declarations
        public NamespaceHandling NamespaceHandling
        {
            get => _namespaceHandling;
            set
            {
                CheckReadOnly();
                ArgumentOutOfRangeException.ThrowIfGreaterThan(unchecked((uint)value), (uint)NamespaceHandling.OmitDuplicates, nameof(value));
                _namespaceHandling = value;
            }
        }
 
        //Whether or not to auto complete end-element when close/dispose
        public bool WriteEndDocumentOnClose
        {
            get => _writeEndDocumentOnClose;
            set
            {
                CheckReadOnly();
                _writeEndDocumentOnClose = value;
            }
        }
 
        // Specifies the method (Html, Xml, etc.) that will be used to serialize the result tree.
        public XmlOutputMethod OutputMethod { get; internal set; }
 
        public void Reset()
        {
            CheckReadOnly();
            Initialize();
        }
 
        // Deep clone all settings (except read-only, which is always set to false).  The original and new objects
        // can now be set independently of each other.
        public XmlWriterSettings Clone()
        {
            XmlWriterSettings clonedSettings = (XmlWriterSettings)MemberwiseClone();
 
            // Deep clone shared settings that are not immutable
            clonedSettings.CDataSectionElements = new List<XmlQualifiedName>(CDataSectionElements);
            clonedSettings.ReadOnly = false;
 
            return clonedSettings;
        }
 
        // Set of XmlQualifiedNames that identify any elements that need to have text children wrapped in CData sections.
        internal List<XmlQualifiedName> CDataSectionElements { get; private set; } = new();
 
        // Used in Html writer to disable encoding of uri attributes
        public bool DoNotEscapeUriAttributes
        {
            get => _doNotEscapeUriAttributes;
            set
            {
                CheckReadOnly();
                _doNotEscapeUriAttributes = value;
            }
        }
 
        internal bool MergeCDataSections
        {
            get => _mergeCDataSections;
            set
            {
                CheckReadOnly();
                _mergeCDataSections = value;
            }
        }
 
        // Used in Html writer when writing Meta element.  Null denotes the default media type.
        internal string? MediaType
        {
            get => _mediaType;
            set
            {
                CheckReadOnly();
                _mediaType = value;
            }
        }
 
        // System Id in doc-type declaration.  Null denotes the absence of the system Id.
        internal string? DocTypeSystem
        {
            get => _docTypeSystem;
            set
            {
                CheckReadOnly();
                _docTypeSystem = value;
            }
        }
 
        // Public Id in doc-type declaration.  Null denotes the absence of the public Id.
        internal string? DocTypePublic
        {
            get => _docTypePublic;
            set
            {
                CheckReadOnly();
                _docTypePublic = value;
            }
        }
 
        // Yes for standalone="yes", No for standalone="no", and Omit for no standalone.
        internal XmlStandalone Standalone
        {
            get => _standalone;
            set
            {
                CheckReadOnly();
                _standalone = value;
            }
        }
 
        // True if an xml declaration should automatically be output (no need to call WriteStartDocument)
        internal bool AutoXmlDeclaration
        {
            get => _autoXmlDecl;
            set
            {
                CheckReadOnly();
                _autoXmlDecl = value;
            }
        }
 
        // If TriState.Unknown, then Indent property was not explicitly set.  In this case, the AutoDetect output
        // method will default to Indent=true for Html and Indent=false for Xml.
        internal TriState IndentInternal { get; set; }
        private bool IsQuerySpecific => CDataSectionElements.Count != 0 || _docTypePublic != null || _docTypeSystem != null || _standalone == XmlStandalone.Yes;
        internal XmlWriter CreateWriter(string outputFileName)
        {
            ArgumentNullException.ThrowIfNull(outputFileName);
 
            // need to clone the settigns so that we can set CloseOutput to true to make sure the stream gets closed in the end
            XmlWriterSettings newSettings = this;
            if (!newSettings.CloseOutput)
            {
                newSettings = newSettings.Clone();
                newSettings.CloseOutput = true;
            }
 
            FileStream? fs = null;
            try
            {
                // open file stream
                fs = new FileStream(outputFileName, FileMode.Create, FileAccess.Write, FileShare.Read, 0x1000, _useAsync);
 
                // create writer
                return newSettings.CreateWriter(fs);
            }
            catch
            {
                fs?.Dispose();
                throw;
            }
        }
 
        internal XmlWriter CreateWriter(Stream output)
        {
            ArgumentNullException.ThrowIfNull(output);
 
            XmlWriter writer;
 
            // create raw writer
            Debug.Assert(Encoding.UTF8.WebName == "utf-8");
            if (Encoding.WebName == "utf-8")
            { // Encoding.CodePage is not supported in Silverlight
                // create raw UTF-8 writer
                switch (OutputMethod)
                {
                    case XmlOutputMethod.Xml:
                        writer = Indent ? new XmlUtf8RawTextWriterIndent(output, this) : new XmlUtf8RawTextWriter(output, this);
                        break;
                    case XmlOutputMethod.Html:
                        writer = Indent ? new HtmlUtf8RawTextWriterIndent(output, this) : new HtmlUtf8RawTextWriter(output, this);
                        break;
                    case XmlOutputMethod.Text:
                        writer = new TextUtf8RawTextWriter(output, this);
                        break;
                    case XmlOutputMethod.AutoDetect:
                        writer = new XmlAutoDetectWriter(output, this);
                        break;
                    default:
                        Debug.Fail("Invalid XmlOutputMethod setting.");
                        return null!;
                }
            }
            else
            {
                // Otherwise, create a general-purpose writer than can do any encoding
                switch (OutputMethod)
                {
                    case XmlOutputMethod.Xml:
                        writer = Indent ? new XmlEncodedRawTextWriterIndent(output, this) : new XmlEncodedRawTextWriter(output, this);
                        break;
                    case XmlOutputMethod.Html:
                        writer = Indent ? new HtmlEncodedRawTextWriterIndent(output, this) : new HtmlEncodedRawTextWriter(output, this);
                        break;
                    case XmlOutputMethod.Text:
                        writer = new TextEncodedRawTextWriter(output, this);
                        break;
                    case XmlOutputMethod.AutoDetect:
                        writer = new XmlAutoDetectWriter(output, this);
                        break;
                    default:
                        Debug.Fail("Invalid XmlOutputMethod setting.");
                        return null!;
                }
            }
 
            // Wrap with Xslt/XQuery specific writer if needed;
            // XmlOutputMethod.AutoDetect writer does this lazily when it creates the underlying Xml or Html writer.
            if (OutputMethod != XmlOutputMethod.AutoDetect)
            {
                if (IsQuerySpecific)
                {
                    // Create QueryOutputWriter if CData sections or DocType need to be tracked
                    writer = new QueryOutputWriter((XmlRawWriter)writer, this);
                }
            }
 
            // wrap with well-formed writer
            writer = new XmlWellFormedWriter(writer, this);
 
            if (_useAsync)
            {
                writer = new XmlAsyncCheckWriter(writer);
            }
 
            return writer;
        }
 
        internal XmlWriter CreateWriter(TextWriter output)
        {
            ArgumentNullException.ThrowIfNull(output);
 
            XmlWriter writer;
 
            // create raw writer
            switch (OutputMethod)
            {
                case XmlOutputMethod.Xml:
                    writer = Indent ? new XmlEncodedRawTextWriterIndent(output, this) : new XmlEncodedRawTextWriter(output, this);
                    break;
                case XmlOutputMethod.Html:
                    writer = Indent ? new HtmlEncodedRawTextWriterIndent(output, this) : new HtmlEncodedRawTextWriter(output, this);
                    break;
                case XmlOutputMethod.Text:
                    writer = new TextEncodedRawTextWriter(output, this);
                    break;
                case XmlOutputMethod.AutoDetect:
                    writer = new XmlAutoDetectWriter(output, this);
                    break;
                default:
                    Debug.Fail("Invalid XmlOutputMethod setting.");
                    return null!;
            }
 
            // XmlOutputMethod.AutoDetect writer does this lazily when it creates the underlying Xml or Html writer.
            if (OutputMethod != XmlOutputMethod.AutoDetect)
            {
                if (IsQuerySpecific)
                {
                    // Create QueryOutputWriter if CData sections or DocType need to be tracked
                    writer = new QueryOutputWriter((XmlRawWriter)writer, this);
                }
            }
 
            // wrap with well-formed writer
            writer = new XmlWellFormedWriter(writer, this);
 
            if (_useAsync)
            {
                writer = new XmlAsyncCheckWriter(writer);
            }
            return writer;
        }
 
        internal XmlWriter CreateWriter(XmlWriter output)
        {
            ArgumentNullException.ThrowIfNull(output);
 
            return AddConformanceWrapper(output);
        }
 
 
        internal bool ReadOnly { get; set; }
        private void CheckReadOnly([CallerMemberName] string? propertyName = null)
        {
            if (ReadOnly)
            {
                throw new XmlException(SR.Xml_ReadOnlyProperty, $"{GetType().Name}.{propertyName}");
            }
        }
 
        [MemberNotNull(nameof(_encoding))]
        [MemberNotNull(nameof(_newLineChars))]
        [MemberNotNull(nameof(_indentChars))]
        private void Initialize()
        {
            _encoding = Encoding.UTF8;
            _omitXmlDecl = false;
            _newLineHandling = NewLineHandling.Replace;
            _newLineChars = Environment.NewLine; // "\r\n" on Windows, "\n" on Unix
            IndentInternal = TriState.Unknown;
            _indentChars = "  ";
            _newLineOnAttributes = false;
            _closeOutput = false;
            _namespaceHandling = NamespaceHandling.Default;
            _conformanceLevel = ConformanceLevel.Document;
            _checkCharacters = true;
            _writeEndDocumentOnClose = true;
            OutputMethod = XmlOutputMethod.Xml;
            CDataSectionElements.Clear();
            _mergeCDataSections = false;
            _mediaType = null;
            _docTypeSystem = null;
            _docTypePublic = null;
            _standalone = XmlStandalone.Omit;
            _doNotEscapeUriAttributes = false;
            _useAsync = false;
            ReadOnly = false;
        }
 
        private XmlWriter AddConformanceWrapper(XmlWriter baseWriter)
        {
            ConformanceLevel confLevel = ConformanceLevel.Auto;
            XmlWriterSettings? baseWriterSettings = baseWriter.Settings;
            bool checkValues = false;
            bool checkNames = false;
            bool replaceNewLines = false;
            bool needWrap = false;
 
            if (baseWriterSettings == null)
            {
                // assume the V1 writer already do all conformance checking;
                // wrap only if NewLineHandling == Replace or CheckCharacters is true
                if (_newLineHandling == NewLineHandling.Replace)
                {
                    replaceNewLines = true;
                    needWrap = true;
                }
                if (_checkCharacters)
                {
                    checkValues = true;
                    needWrap = true;
                }
            }
            else
            {
                if (_conformanceLevel != baseWriterSettings.ConformanceLevel)
                {
                    confLevel = ConformanceLevel;
                    needWrap = true;
                }
                if (_checkCharacters && !baseWriterSettings.CheckCharacters)
                {
                    checkValues = true;
                    checkNames = confLevel == ConformanceLevel.Auto;
                    needWrap = true;
                }
                if (_newLineHandling == NewLineHandling.Replace &&
                     baseWriterSettings.NewLineHandling == NewLineHandling.None)
                {
                    replaceNewLines = true;
                    needWrap = true;
                }
            }
 
            XmlWriter writer = baseWriter;
 
            if (needWrap)
            {
                if (confLevel != ConformanceLevel.Auto)
                {
                    writer = new XmlWellFormedWriter(writer, this);
                }
                if (checkValues || replaceNewLines)
                {
                    writer = new XmlCharCheckingWriter(writer, checkValues, checkNames, replaceNewLines, this.NewLineChars);
                }
            }
 
            if (this.IsQuerySpecific && (baseWriterSettings == null || !baseWriterSettings.IsQuerySpecific))
            {
                // Create QueryOutputWriterV1 if CData sections or DocType need to be tracked
                writer = new QueryOutputWriterV1(writer, this);
            }
 
            return writer;
        }
 
        /// <summary>
        /// Serialize the object to BinaryWriter.
        /// </summary>
        internal void GetObjectData(XmlQueryDataWriter writer)
        {
            // Encoding encoding;
            // NOTE: For Encoding we serialize only CodePage, and ignore EncoderFallback/DecoderFallback.
            // It suffices for XSLT purposes, but not in the general case.
            Debug.Assert(Encoding.Equals(Encoding.GetEncoding(Encoding.CodePage)), "Cannot serialize encoding correctly");
            writer.Write(Encoding.CodePage);
            // bool omitXmlDecl;
            writer.Write(OmitXmlDeclaration);
            // NewLineHandling newLineHandling;
            writer.Write((sbyte)NewLineHandling);
            // string newLineChars;
            writer.WriteStringQ(NewLineChars);
            // TriState indent;
            writer.Write((sbyte)IndentInternal);
            // string indentChars;
            writer.WriteStringQ(IndentChars);
            // bool newLineOnAttributes;
            writer.Write(NewLineOnAttributes);
            // bool closeOutput;
            writer.Write(CloseOutput);
            // ConformanceLevel conformanceLevel;
            writer.Write((sbyte)ConformanceLevel);
            // bool checkCharacters;
            writer.Write(CheckCharacters);
            // XmlOutputMethod outputMethod;
            writer.Write((sbyte)OutputMethod);
            // List<XmlQualifiedName> cdataSections;
            writer.Write(CDataSectionElements.Count);
            foreach (XmlQualifiedName qName in CDataSectionElements)
            {
                writer.Write(qName.Name);
                writer.Write(qName.Namespace);
            }
            // bool mergeCDataSections;
            writer.Write(_mergeCDataSections);
            // string mediaType;
            writer.WriteStringQ(_mediaType);
            // string docTypeSystem;
            writer.WriteStringQ(_docTypeSystem);
            // string docTypePublic;
            writer.WriteStringQ(_docTypePublic);
            // XmlStandalone standalone;
            writer.Write((sbyte)_standalone);
            // bool autoXmlDecl;
            writer.Write(_autoXmlDecl);
            // bool isReadOnly;
            writer.Write(ReadOnly);
        }
 
        /// <summary>
        /// Deserialize the object from BinaryReader.
        /// </summary>
        internal XmlWriterSettings(XmlQueryDataReader reader)
        {
            // Encoding encoding;
            Encoding = Encoding.GetEncoding(reader.ReadInt32());
            // bool omitXmlDecl;
            OmitXmlDeclaration = reader.ReadBoolean();
            // NewLineHandling newLineHandling;
            NewLineHandling = (NewLineHandling)reader.ReadSByte(0, (sbyte)NewLineHandling.None);
            // string newLineChars;
            NewLineChars = reader.ReadStringQ()!;
            // TriState indent;
            IndentInternal = (TriState)reader.ReadSByte((sbyte)TriState.Unknown, (sbyte)TriState.True);
            // string indentChars;
            IndentChars = reader.ReadStringQ()!;
            // bool newLineOnAttributes;
            NewLineOnAttributes = reader.ReadBoolean();
            // bool closeOutput;
            CloseOutput = reader.ReadBoolean();
            // ConformanceLevel conformanceLevel;
            ConformanceLevel = (ConformanceLevel)reader.ReadSByte(0, (sbyte)ConformanceLevel.Document);
            // bool checkCharacters;
            CheckCharacters = reader.ReadBoolean();
            // XmlOutputMethod outputMethod;
            OutputMethod = (XmlOutputMethod)reader.ReadSByte(0, (sbyte)XmlOutputMethod.AutoDetect);
            // List<XmlQualifiedName> cdataSections;
            int length = reader.ReadInt32();
            CDataSectionElements = new List<XmlQualifiedName>(length);
            for (int idx = 0; idx < length; idx++)
            {
                CDataSectionElements.Add(new XmlQualifiedName(reader.ReadString(), reader.ReadString()));
            }
            // bool mergeCDataSections;
            _mergeCDataSections = reader.ReadBoolean();
            // string mediaType;
            _mediaType = reader.ReadStringQ();
            // string docTypeSystem;
            _docTypeSystem = reader.ReadStringQ();
            // string docTypePublic;
            _docTypePublic = reader.ReadStringQ();
            // XmlStandalone standalone;
            Standalone = (XmlStandalone)reader.ReadSByte(0, (sbyte)XmlStandalone.No);
            // bool autoXmlDecl;
            _autoXmlDecl = reader.ReadBoolean();
            // bool isReadOnly;
            ReadOnly = reader.ReadBoolean();
        }
 
        [DoesNotReturn]
        [StackTraceHidden]
        private static void ThrowArgumentOutOfRangeException(string paramName)
        {
            throw new ArgumentOutOfRangeException(paramName);
        }
    }
}