IRC parsing, tokenization, and state handling in C#
1using System.Globalization;
2using System.Text;
3
4namespace IRCTokens;
5
6public static class TagEscape
7{
8 private static readonly string[] TagUnescaped = ["\\", " ", ";", "\r", "\n"];
9 private static readonly string[] TagEscaped = [@"\\", "\\s", "\\:", "\\r", "\\n"];
10
11 /// <summary>Unescape ircv3 tag</summary>
12 /// <param name="val">escaped string</param>
13 /// <returns>unescaped string</returns>
14 public static string Unescape(string val)
15 {
16 StringBuilder unescaped = new();
17
18 var graphemeIterator = StringInfo.GetTextElementEnumerator(val);
19 graphemeIterator.Reset();
20
21 while (graphemeIterator.MoveNext())
22 {
23 var current = graphemeIterator.GetTextElement();
24
25 if (current == @"\")
26 {
27 try
28 {
29 graphemeIterator.MoveNext();
30 var next = graphemeIterator.GetTextElement();
31 var pair = current + next;
32 unescaped.Append(TagEscaped.Contains(pair)
33 ? TagUnescaped[Array.IndexOf(TagEscaped, pair)]
34 : next);
35 }
36 catch (InvalidOperationException)
37 {
38 // ignored
39 }
40 }
41 else
42 {
43 unescaped.Append(current);
44 }
45 }
46
47 return unescaped.ToString();
48 }
49
50 /// <summary>Escape strings for use in ircv3 tags</summary>
51 /// <param name="val">string to escape</param>
52 /// <returns>escaped string</returns>
53 public static string Escape(string val)
54 {
55 StringBuilder sb = new(val);
56 for (var i = 0; i < TagUnescaped.Length; ++i)
57 {
58 sb.Replace(TagUnescaped[i], TagEscaped[i]);
59 }
60
61 return sb.ToString();
62 }
63}