File: System\Xml\Core\XmlTextEncoder.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.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;
 
namespace System.Xml
{
    // XmlTextEncoder
    //
    // This class does special handling of text content for XML.  For example
    // it will replace special characters with entities whenever necessary.
    internal sealed class XmlTextEncoder
    {
        //
        // Fields
        //
        // output text writer
        private readonly TextWriter _textWriter;
 
        // true when writing out the content of attribute value
        private bool _inAttribute;
 
        // quote char of the attribute (when inAttribute)
        private char _quoteChar;
 
        // caching of attribute value
        private StringBuilder? _attrValue;
        private bool _cacheAttrValue;
 
        //
        // Constructor
        //
        internal XmlTextEncoder(TextWriter textWriter)
        {
            _textWriter = textWriter;
            _quoteChar = '"';
        }
 
        //
        // Internal methods and properties
        //
        internal char QuoteChar
        {
            set
            {
                _quoteChar = value;
            }
        }
 
        internal void StartAttribute(bool cacheAttrValue)
        {
            _inAttribute = true;
            _cacheAttrValue = cacheAttrValue;
            if (cacheAttrValue)
            {
                if (_attrValue == null)
                {
                    _attrValue = new StringBuilder();
                }
                else
                {
                    _attrValue.Length = 0;
                }
            }
        }
 
        internal void EndAttribute()
        {
            if (_cacheAttrValue)
            {
                Debug.Assert(_attrValue != null);
                _attrValue.Length = 0;
            }
 
            _inAttribute = false;
            _cacheAttrValue = false;
        }
 
        internal string AttributeValue
        {
            get
            {
                if (_cacheAttrValue)
                {
                    Debug.Assert(_attrValue != null);
                    return _attrValue.ToString();
                }
                else
                {
                    return string.Empty;
                }
            }
        }
 
        internal void WriteSurrogateChar(char lowChar, char highChar)
        {
            if (!XmlCharType.IsLowSurrogate(lowChar) ||
                 !XmlCharType.IsHighSurrogate(highChar))
            {
                throw XmlConvert.CreateInvalidSurrogatePairException(lowChar, highChar);
            }
 
            _textWriter.Write(highChar);
            _textWriter.Write(lowChar);
        }
 
        internal void Write(char[] array, int offset, int count)
        {
            ArgumentNullException.ThrowIfNull(array);
 
            ArgumentOutOfRangeException.ThrowIfNegative(offset);
            ArgumentOutOfRangeException.ThrowIfNegative(count);
            ArgumentOutOfRangeException.ThrowIfGreaterThan(count, array.Length - offset);
 
            if (_cacheAttrValue)
            {
                Debug.Assert(_attrValue != null);
                _attrValue.Append(array, offset, count);
            }
 
            int endPos = offset + count;
            int i = offset;
            char ch = (char)0;
            while (true)
            {
                int startPos = i;
                while (i < endPos && XmlCharType.IsAttributeValueChar(ch = array[i]))
                {
                    i++;
                }
 
                if (startPos < i)
                {
                    _textWriter.Write(array, startPos, i - startPos);
                }
                if (i == endPos)
                {
                    break;
                }
 
                switch (ch)
                {
                    case (char)0x9:
                        _textWriter.Write(ch);
                        break;
                    case (char)0xA:
                    case (char)0xD:
                        if (_inAttribute)
                        {
                            WriteCharEntityImpl(ch);
                        }
                        else
                        {
                            _textWriter.Write(ch);
                        }
                        break;
 
                    case '<':
                        WriteEntityRefImpl("lt");
                        break;
                    case '>':
                        WriteEntityRefImpl("gt");
                        break;
                    case '&':
                        WriteEntityRefImpl("amp");
                        break;
                    case '\'':
                        if (_inAttribute && _quoteChar == ch)
                        {
                            WriteEntityRefImpl("apos");
                        }
                        else
                        {
                            _textWriter.Write('\'');
                        }
                        break;
                    case '"':
                        if (_inAttribute && _quoteChar == ch)
                        {
                            WriteEntityRefImpl("quot");
                        }
                        else
                        {
                            _textWriter.Write('"');
                        }
                        break;
                    default:
                        if (XmlCharType.IsHighSurrogate(ch))
                        {
                            if (i + 1 < endPos)
                            {
                                WriteSurrogateChar(array[++i], ch);
                            }
                            else
                            {
                                throw new ArgumentException(SR.Xml_SurrogatePairSplit);
                            }
                        }
                        else if (XmlCharType.IsLowSurrogate(ch))
                        {
                            throw XmlConvert.CreateInvalidHighSurrogateCharException(ch);
                        }
                        else
                        {
                            Debug.Assert((ch < 0x20 && !XmlCharType.IsWhiteSpace(ch)) || (ch > 0xFFFD));
                            WriteCharEntityImpl(ch);
                        }
                        break;
                }
                i++;
            }
        }
 
        internal void WriteSurrogateCharEntity(char lowChar, char highChar)
        {
            if (!XmlCharType.IsLowSurrogate(lowChar) ||
                 !XmlCharType.IsHighSurrogate(highChar))
            {
                throw XmlConvert.CreateInvalidSurrogatePairException(lowChar, highChar);
            }
            int surrogateChar = XmlCharType.CombineSurrogateChar(lowChar, highChar);
 
            if (_cacheAttrValue)
            {
                Debug.Assert(_attrValue != null);
                _attrValue.Append(highChar);
                _attrValue.Append(lowChar);
            }
 
            _textWriter.Write("&#x");
            _textWriter.Write(surrogateChar.ToString("X", NumberFormatInfo.InvariantInfo));
            _textWriter.Write(';');
        }
 
        internal void Write(ReadOnlySpan<char> text)
        {
            if (text.IsEmpty)
            {
                return;
            }
 
            if (_cacheAttrValue)
            {
                Debug.Assert(_attrValue != null);
                _attrValue.Append(text);
            }
 
            // scan through the string to see if there are any characters to be escaped
            int len = text.Length;
            int i = 0;
            int startPos = 0;
            char ch = (char)0;
            while (true)
            {
                while (i < len && XmlCharType.IsAttributeValueChar(ch = text[i]))
                {
                    i++;
                }
 
                if (i == len)
                {
                    // reached the end of the string -> write it whole out
                    _textWriter.Write(text);
                    return;
                }
                if (_inAttribute)
                {
                    if (ch == 0x9)
                    {
                        i++;
                        continue;
                    }
                }
                else
                {
                    if (ch == 0x9 || ch == 0xA || ch == 0xD || ch == '"' || ch == '\'')
                    {
                        i++;
                        continue;
                    }
                }
                // some character that needs to be escaped is found:
                break;
            }
 
            while (true)
            {
                if (startPos < i)
                {
                    _textWriter.Write(text.Slice(startPos, i - startPos));
                }
 
                if (i == len)
                {
                    break;
                }
 
                switch (ch)
                {
                    case (char)0x9:
                        _textWriter.Write(ch);
                        break;
                    case (char)0xA:
                    case (char)0xD:
                        if (_inAttribute)
                        {
                            WriteCharEntityImpl(ch);
                        }
                        else
                        {
                            _textWriter.Write(ch);
                        }
                        break;
                    case '<':
                        WriteEntityRefImpl("lt");
                        break;
                    case '>':
                        WriteEntityRefImpl("gt");
                        break;
                    case '&':
                        WriteEntityRefImpl("amp");
                        break;
                    case '\'':
                        if (_inAttribute && _quoteChar == ch)
                        {
                            WriteEntityRefImpl("apos");
                        }
                        else
                        {
                            _textWriter.Write('\'');
                        }
                        break;
                    case '"':
                        if (_inAttribute && _quoteChar == ch)
                        {
                            WriteEntityRefImpl("quot");
                        }
                        else
                        {
                            _textWriter.Write('"');
                        }
                        break;
                    default:
                        if (XmlCharType.IsHighSurrogate(ch))
                        {
                            if (i + 1 < len)
                            {
                                WriteSurrogateChar(text[++i], ch);
                            }
                            else
                            {
                                throw XmlConvert.CreateInvalidSurrogatePairException(text[i], ch);
                            }
                        }
                        else if (XmlCharType.IsLowSurrogate(ch))
                        {
                            throw XmlConvert.CreateInvalidHighSurrogateCharException(ch);
                        }
                        else
                        {
                            Debug.Assert((ch < 0x20 && !XmlCharType.IsWhiteSpace(ch)) || (ch > 0xFFFD));
                            WriteCharEntityImpl(ch);
                        }
                        break;
                }
                i++;
                startPos = i;
                while (i < len && XmlCharType.IsAttributeValueChar(ch = text[i]))
                {
                    i++;
                }
            }
        }
 
        internal void WriteRawWithSurrogateChecking(string text)
        {
            if (text == null)
            {
                return;
            }
 
            if (_cacheAttrValue)
            {
                Debug.Assert(_attrValue != null);
                _attrValue.Append(text);
            }
 
            int len = text.Length;
            int i = 0;
            char ch = (char)0;
 
            while (true)
            {
                while (i < len && (XmlCharType.IsCharData((ch = text[i])) || ch < 0x20))
                {
                    i++;
                }
                if (i == len)
                {
                    break;
                }
                if (XmlCharType.IsHighSurrogate(ch))
                {
                    if (i + 1 < len)
                    {
                        char lowChar = text[i + 1];
                        if (XmlCharType.IsLowSurrogate(lowChar))
                        {
                            i += 2;
                            continue;
                        }
                        else
                        {
                            throw XmlConvert.CreateInvalidSurrogatePairException(lowChar, ch);
                        }
                    }
                    throw new ArgumentException(SR.Xml_InvalidSurrogateMissingLowChar);
                }
                else if (XmlCharType.IsLowSurrogate(ch))
                {
                    throw XmlConvert.CreateInvalidHighSurrogateCharException(ch);
                }
                else
                {
                    i++;
                }
            }
 
            _textWriter.Write(text);
            return;
        }
 
        internal void WriteRaw(char[] array, int offset, int count)
        {
            ArgumentNullException.ThrowIfNull(array);
 
            ArgumentOutOfRangeException.ThrowIfNegative(count);
            ArgumentOutOfRangeException.ThrowIfNegative(offset);
            ArgumentOutOfRangeException.ThrowIfGreaterThan(count, array.Length - offset);
 
            if (_cacheAttrValue)
            {
                Debug.Assert(_attrValue != null);
                _attrValue.Append(array, offset, count);
            }
 
            _textWriter.Write(array, offset, count);
        }
 
 
 
        internal void WriteCharEntity(char ch)
        {
            if (XmlCharType.IsSurrogate(ch))
            {
                throw new ArgumentException(SR.Xml_InvalidSurrogateMissingLowChar);
            }
 
            string strVal = ((int)ch).ToString("X", NumberFormatInfo.InvariantInfo);
            if (_cacheAttrValue)
            {
                Debug.Assert(_attrValue != null);
                _attrValue.Append("&#x");
                _attrValue.Append(strVal);
                _attrValue.Append(';');
            }
 
            WriteCharEntityImpl(strVal);
        }
 
        internal void WriteEntityRef(string name)
        {
            if (_cacheAttrValue)
            {
                Debug.Assert(_attrValue != null);
                _attrValue.Append('&');
                _attrValue.Append(name);
                _attrValue.Append(';');
            }
 
            WriteEntityRefImpl(name);
        }
 
        //
        // Private implementation methods
        //
 
        private void WriteCharEntityImpl(char ch)
        {
            WriteCharEntityImpl(((int)ch).ToString("X", NumberFormatInfo.InvariantInfo));
        }
 
        private void WriteCharEntityImpl(string strVal)
        {
            _textWriter.Write("&#x");
            _textWriter.Write(strVal);
            _textWriter.Write(';');
        }
 
        private void WriteEntityRefImpl(string name)
        {
            _textWriter.Write('&');
            _textWriter.Write(name);
            _textWriter.Write(';');
        }
    }
}