File: src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Core\Utilities\StringEscapeEncoder.cs
Web Access
Project: src\src\CodeStyle\Core\Analyzers\Microsoft.CodeAnalysis.CodeStyle.csproj (Microsoft.CodeAnalysis.CodeStyle)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Text;
 
namespace Roslyn.Utilities;
 
internal static class StringEscapeEncoder
{
    public static string Escape(this string text, char escapePrefix, params char[] prohibitedCharacters)
    {
        StringBuilder? builder = null;
 
        var startIndex = 0;
        while (startIndex < text.Length)
        {
            var prefixIndex = text.IndexOf(escapePrefix, startIndex);
            var prohibitIndex = text.IndexOfAny(prohibitedCharacters, startIndex);
            var index = prefixIndex >= 0 && prohibitIndex >= 0 ? Math.Min(prefixIndex, prohibitIndex)
                    : prefixIndex >= 0 ? prefixIndex
                    : prohibitIndex >= 0 ? prohibitIndex
                    : -1;
 
            if (index < 0)
            {
                // append remaining text
                builder?.Append(text, startIndex, text.Length - startIndex);
 
                break;
            }
 
            builder ??= new StringBuilder();
 
            if (index > startIndex)
            {
                // everything between the start and the prohibited character
                builder.Append(text, startIndex, index - startIndex);
            }
 
            // add the escape prefix before the character that needs escaping
            builder.Append(escapePrefix);
 
            // add the prohibited character data as hex after the prefix
            builder.AppendFormat("{0:X2}", (int)text[index]);
 
            startIndex = index + 1;
        }
 
        if (builder != null)
        {
            return builder.ToString();
        }
        else
        {
            return text;
        }
    }
 
    public static string Unescape(this string text, char escapePrefix)
    {
        StringBuilder? builder = null;
        var startIndex = 0;
 
        while (startIndex < text.Length)
        {
            var index = text.IndexOf(escapePrefix, startIndex);
            if (index < 0)
            {
                // append remaining text
                builder?.Append(text, startIndex, text.Length - startIndex);
 
                break;
            }
 
            builder ??= new StringBuilder();
 
            // add everything up to the escape prefix
            builder.Append(text, startIndex, index - startIndex);
 
            // skip over the escape prefix and the following character that was escaped
            var hex = ParseHex(text, index + 1, 2);
            builder.Append((char)hex);
 
            startIndex = index + 3; // includes escape + 2 hex digits
        }
 
        if (builder != null)
        {
            return builder.ToString();
        }
        else
        {
            return text;
        }
    }
 
    private static int ParseHex(string text, int start, int length)
    {
        var value = 0;
 
        for (int i = start, end = start + length; i < end; i++)
        {
            var ch = text[i];
            if (!IsHexDigit(ch))
            {
                break;
            }
 
            value = (value << 4) + GetHexValue(ch);
        }
 
        return value;
    }
 
    private static bool IsHexDigit(char ch)
    {
        return ch is >= '0' and <= '9' or >= 'A' and <= 'F' or >= 'a' and <= 'f';
    }
 
    private static int GetHexValue(char ch)
    {
        if (ch is >= '0' and <= '9')
        {
            return ch - '0';
        }
        else if (ch is >= 'A' and <= 'F')
        {
            return (ch - 'A') + 10;
        }
        else if (ch is >= 'a' and <= 'f')
        {
            return (ch - 'a') + 10;
        }
        else
        {
            throw new InvalidOperationException();
        }
    }
}