IRC parsing, tokenization, and state handling in C#

move tag escape to its own file

+68 -64
+5 -64
IRCTokens/Line.cs
··· 1 - using System.Globalization; 2 - using System.Text; 3 - 4 1 // ReSharper disable ReplaceWithFieldKeyword 5 2 // ReSharper disable CommentTypo 6 3 ··· 9 6 /// <summary>Tools to represent, parse, and format IRC lines</summary> 10 7 public class Line : IEquatable<Line> 11 8 { 12 - private static readonly string[] TagUnescaped = ["\\", " ", ";", "\r", "\n"]; 13 - 14 - private static readonly string[] TagEscaped = [@"\\", "\\s", "\\:", "\\r", "\\n"]; 15 9 private Hostmask _hostmask; 16 10 17 11 public Line() ··· 48 42 if (part.Contains('=')) 49 43 { 50 44 split = part.Split(['='], 2); 51 - Tags[split[0]] = UnescapeTag(split[1]); 45 + Tags[split[0]] = TagEscape.Unescape(split[1]); 52 46 } 53 47 else 54 48 { ··· 96 90 public string Command { get; set; } 97 91 public List<string> Params { get; set; } 98 92 99 - public Hostmask Hostmask => _hostmask ??= new Hostmask(Source); 93 + public Hostmask Hostmask => _hostmask ??= new(Source); 100 94 101 95 public bool Equals(Line other) 102 96 { ··· 108 102 return Format() == other.Format(); 109 103 } 110 104 111 - /// <summary>Unescape ircv3 tag</summary> 112 - /// <param name="val">escaped string</param> 113 - /// <returns>unescaped string</returns> 114 - private static string UnescapeTag(string val) 115 - { 116 - var unescaped = new StringBuilder(); 117 - 118 - var graphemeIterator = StringInfo.GetTextElementEnumerator(val); 119 - graphemeIterator.Reset(); 120 - 121 - while (graphemeIterator.MoveNext()) 122 - { 123 - var current = graphemeIterator.GetTextElement(); 124 - 125 - if (current == @"\") 126 - { 127 - try 128 - { 129 - graphemeIterator.MoveNext(); 130 - var next = graphemeIterator.GetTextElement(); 131 - var pair = current + next; 132 - unescaped.Append(TagEscaped.Contains(pair) 133 - ? TagUnescaped[Array.IndexOf(TagEscaped, pair)] 134 - : next); 135 - } 136 - catch (InvalidOperationException) 137 - { 138 - // ignored 139 - } 140 - } 141 - else 142 - { 143 - unescaped.Append(current); 144 - } 145 - } 146 - 147 - return unescaped.ToString(); 148 - } 149 - 150 - /// <summary>Escape strings for use in ircv3 tags</summary> 151 - /// <param name="val">string to escape</param> 152 - /// <returns>escaped string</returns> 153 - private static string EscapeTag(string val) 154 - { 155 - StringBuilder sb = new(val); 156 - for (var i = 0; i < TagUnescaped.Length; ++i) 157 - { 158 - sb.Replace(TagUnescaped[i], TagEscaped[i]); 159 - } 160 - 161 - return sb.ToString(); 162 - } 163 - 164 105 public override string ToString() 165 106 { 166 - var vars = new List<string>(); 107 + List<string> vars = []; 167 108 168 109 if (Command != null) 169 110 { ··· 196 137 /// <returns>formatted irc line</returns> 197 138 public string Format() 198 139 { 199 - var outs = new List<string>(); 140 + List<string> outs = []; 200 141 201 142 if (Tags != null && Tags.Any()) 202 143 { 203 144 var tags = Tags.Keys 204 145 .OrderBy(k => k) 205 - .Select(key => string.IsNullOrWhiteSpace(Tags[key]) ? key : $"{key}={EscapeTag(Tags[key])}") 146 + .Select(key => string.IsNullOrWhiteSpace(Tags[key]) ? key : $"{key}={TagEscape.Escape(Tags[key])}") 206 147 .ToList(); 207 148 208 149 outs.Add($"@{string.Join(";", tags)}");
+63
IRCTokens/TagEscape.cs
··· 1 + using System.Globalization; 2 + using System.Text; 3 + 4 + namespace IRCTokens; 5 + 6 + public 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 + }