Skip to content

Commit

Permalink
TextExtensions.Escape and Unescape
Browse files Browse the repository at this point in the history
  • Loading branch information
Nixill committed Jan 28, 2025
1 parent 10b38f9 commit 827a351
Showing 1 changed file with 93 additions and 0 deletions.
93 changes: 93 additions & 0 deletions CSharp.Nixill/src/Utils/Extensions/TextExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text;
using Nixill.Collections;

namespace Nixill.Utils.Extensions;

Expand Down Expand Up @@ -71,6 +72,42 @@ public static IEnumerable<string> CharLimitedJoin<T>(this IEnumerable<T> seq, st
}
}

/// <summary>
/// Escapes given characters from a string.
/// </summary>
/// <param name="input">The input string.</param>
/// <param name="escapeChar">
/// The escape character. Escapes itself by doubling.
/// </param>
/// <param name="sequences">
/// Map of characters that should be escaped to escape sequences. Does
/// not include the escape character itself in any sequence.
/// </param>
/// <returns>The escaped string.</returns>
public static string Escape(this IEnumerable<char> input, char escapeChar, IDictionary<char, string> sequences)
=> EscapeEnumerable(input, escapeChar, sequences).FormString();

static IEnumerable<char> EscapeEnumerable(IEnumerable<char> input, char escapeChar, IDictionary<char, string> sequences)
{
foreach (char chr in input)
{
if (sequences.ContainsKey(chr))
{
yield return escapeChar;
foreach (char chr2 in sequences[chr]) yield return chr2;
}
else if (chr == escapeChar)
{
yield return escapeChar;
yield return escapeChar;
}
else
{
yield return chr;
}
}
}

/// <summary>
/// Creates a string from a char enumerable.
/// </summary>
Expand All @@ -89,4 +126,60 @@ public static IEnumerable<string> CharLimitedJoin<T>(this IEnumerable<T> seq, st
/// <returns>The string.</returns>
public static string StringJoin<T>(this IEnumerable<T> objects, string with)
=> string.Join(with, objects);

/// <summary>
/// Parses escaped characters from a string.
/// </summary>
/// <param name="input">The input string.</param>
/// <param name="escapeChar">
/// The escape character. Double this is treated as itself.
/// </param>
/// <param name="sequences">
/// Map of escape sequences (without the escape character itself) to
/// the haracters they represent.
/// </param>
/// <returns>The parsed string.</returns>
public static string Unescape(this string input, char escapeChar, IDictionary<string, char> sequences)
=> UnescapeEnumerable(input, escapeChar, new RecursiveDictionary<char, char>(sequences.Select(kvp => new KeyValuePair<IEnumerable<char>, char>(kvp.Key, kvp.Value)))).FormString();

public static string Unescape(this string input, char escapeChar, IDictionary<IEnumerable<char>, char> sequences)
=> UnescapeEnumerable(input, escapeChar, (sequences as RecursiveDictionary<char, char>) ?? new RecursiveDictionary<char, char>(sequences)).FormString();

static IEnumerable<char> UnescapeEnumerable(IEnumerable<char> input, char escapeChar, RecursiveDictionary<char, char> sequences)
{
IEnumerator<char> enm = input.GetEnumerator();

while (enm.MoveNext())
{
char chr = enm.Current;

if (chr == escapeChar)
{
IRecursiveDictionary<char, char> view = sequences;
while (true)
{
if (view.HasEmptyKeyValue)
{
yield return view[""];
break;
}

if (!enm.MoveNext()) yield break;

chr = enm.Current;

if (chr == escapeChar && object.ReferenceEquals(view, sequences))
{
yield return escapeChar;
break;
}

if (!view.ContainsPrefix([chr])) break;

view = view.GetPrefix([chr]);
}
}
else yield return chr;
}
}
}

0 comments on commit 827a351

Please sign in to comment.