|
// 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();
}
}
}
|