|
1 | 1 | using System.ComponentModel.DataAnnotations; |
2 | 2 | using System.Diagnostics.CodeAnalysis; |
3 | | -using System.Text.Json.Nodes; |
4 | 3 | using System.Text.Json.Serialization; |
5 | | -using System.Text.RegularExpressions; |
6 | 4 | using StabilityMatrix.Core.Models.Api.Comfy; |
7 | 5 |
|
8 | 6 | namespace StabilityMatrix.Core.Models; |
9 | 7 |
|
10 | 8 | [JsonSerializable(typeof(GenerationParameters))] |
11 | | -public partial record GenerationParameters |
| 9 | +public record GenerationParameters |
12 | 10 | { |
13 | 11 | public string? PositivePrompt { get; set; } |
14 | 12 | public string? NegativePrompt { get; set; } |
@@ -124,36 +122,139 @@ public static GenerationParameters Parse(string text) |
124 | 122 | /// fields are separated by commas and key-value pairs are separated by colons. |
125 | 123 | /// i.e. "key1: value1, key2: value2" |
126 | 124 | /// </summary> |
127 | | - internal static Dictionary<string, string> ParseLine(string fields) |
| 125 | + internal static Dictionary<string, string> ParseLine(string line) |
128 | 126 | { |
129 | 127 | var dict = new Dictionary<string, string>(); |
130 | 128 |
|
131 | | - // Values main contain commas or colons |
132 | | - foreach (var match in ParametersFieldsRegex().Matches(fields).Cast<Match>()) |
| 129 | + var quoteStack = new Stack<char>(); |
| 130 | + // the Range for the key |
| 131 | + Range? currentKeyRange = null; |
| 132 | + // the start of the key or value |
| 133 | + Index currentStart = 0; |
| 134 | + |
| 135 | + for (var i = 0; i < line.Length; i++) |
133 | 136 | { |
134 | | - if (!match.Success) |
135 | | - continue; |
| 137 | + var c = line[i]; |
136 | 138 |
|
137 | | - var key = match.Groups[1].Value.Trim(); |
138 | | - var value = UnquoteValue(match.Groups[2].Value.Trim()); |
| 139 | + switch (c) |
| 140 | + { |
| 141 | + case '"': |
| 142 | + // if we are in a " quote, pop the stack |
| 143 | + if (quoteStack.Count > 0 && quoteStack.Peek() == '"') |
| 144 | + { |
| 145 | + quoteStack.Pop(); |
| 146 | + } |
| 147 | + else |
| 148 | + { |
| 149 | + // start of a new quoted section |
| 150 | + quoteStack.Push(c); |
| 151 | + } |
| 152 | + break; |
139 | 153 |
|
140 | | - dict.Add(key, value); |
141 | | - } |
| 154 | + case '[': |
| 155 | + case '{': |
| 156 | + case '(': |
| 157 | + case '<': |
| 158 | + quoteStack.Push(c); |
| 159 | + break; |
142 | 160 |
|
143 | | - return dict; |
144 | | - } |
| 161 | + case ']': |
| 162 | + if (quoteStack.Count > 0 && quoteStack.Peek() == '[') |
| 163 | + { |
| 164 | + quoteStack.Pop(); |
| 165 | + } |
| 166 | + break; |
| 167 | + case '}': |
| 168 | + if (quoteStack.Count > 0 && quoteStack.Peek() == '{') |
| 169 | + { |
| 170 | + quoteStack.Pop(); |
| 171 | + } |
| 172 | + break; |
| 173 | + case ')': |
| 174 | + if (quoteStack.Count > 0 && quoteStack.Peek() == '(') |
| 175 | + { |
| 176 | + quoteStack.Pop(); |
| 177 | + } |
| 178 | + break; |
| 179 | + case '>': |
| 180 | + if (quoteStack.Count > 0 && quoteStack.Peek() == '<') |
| 181 | + { |
| 182 | + quoteStack.Pop(); |
| 183 | + } |
| 184 | + break; |
145 | 185 |
|
146 | | - /// <summary> |
147 | | - /// Unquotes a quoted value field if required |
148 | | - /// </summary> |
149 | | - private static string UnquoteValue(string quotedField) |
150 | | - { |
151 | | - if (!(quotedField.StartsWith('"') && quotedField.EndsWith('"'))) |
| 186 | + case ':': |
| 187 | + // : marks the end of the key |
| 188 | + |
| 189 | + // if we already have a key, ignore this colon as it is part of the value |
| 190 | + // if we are not in a quote, we have a key |
| 191 | + if (!currentKeyRange.HasValue && quoteStack.Count == 0) |
| 192 | + { |
| 193 | + currentKeyRange = new Range(currentStart, i); |
| 194 | + currentStart = i + 1; |
| 195 | + } |
| 196 | + break; |
| 197 | + |
| 198 | + case ',': |
| 199 | + // , marks the end of a key-value pair |
| 200 | + // if we are not in a quote, we have a value |
| 201 | + if (quoteStack.Count != 0) |
| 202 | + { |
| 203 | + break; |
| 204 | + } |
| 205 | + |
| 206 | + if (!currentKeyRange.HasValue) |
| 207 | + { |
| 208 | + // unexpected comma, reset and start from current position |
| 209 | + currentStart = i + 1; |
| 210 | + break; |
| 211 | + } |
| 212 | + |
| 213 | + try |
| 214 | + { |
| 215 | + // extract the key and value |
| 216 | + var key = new string(line.AsSpan()[currentKeyRange!.Value].Trim()); |
| 217 | + var value = new string(line.AsSpan()[currentStart..i].Trim()); |
| 218 | + |
| 219 | + // check duplicates and prefer the first occurrence |
| 220 | + if (!string.IsNullOrWhiteSpace(key) && !dict.ContainsKey(key)) |
| 221 | + { |
| 222 | + dict[key] = value; |
| 223 | + } |
| 224 | + } |
| 225 | + catch (Exception) |
| 226 | + { |
| 227 | + // ignore individual key-value pair errors |
| 228 | + } |
| 229 | + |
| 230 | + currentKeyRange = null; |
| 231 | + currentStart = i + 1; |
| 232 | + break; |
| 233 | + default: |
| 234 | + break; |
| 235 | + } // end of switch |
| 236 | + } // end of for |
| 237 | + |
| 238 | + // if we have a key-value pair at the end of the string |
| 239 | + if (currentKeyRange.HasValue) |
152 | 240 | { |
153 | | - return quotedField; |
| 241 | + try |
| 242 | + { |
| 243 | + var key = new string(line.AsSpan()[currentKeyRange!.Value].Trim()); |
| 244 | + var value = new string(line.AsSpan()[currentStart..].Trim()); |
| 245 | + |
| 246 | + if (!string.IsNullOrWhiteSpace(key) && !dict.ContainsKey(key)) |
| 247 | + { |
| 248 | + dict[key] = value; |
| 249 | + } |
| 250 | + } |
| 251 | + catch (Exception) |
| 252 | + { |
| 253 | + // ignore individual key-value pair errors |
| 254 | + } |
154 | 255 | } |
155 | 256 |
|
156 | | - return JsonNode.Parse(quotedField)?.GetValue<string>() ?? ""; |
| 257 | + return dict; |
157 | 258 | } |
158 | 259 |
|
159 | 260 | /// <summary> |
@@ -213,7 +314,4 @@ public static GenerationParameters GetSample() |
213 | 314 | Sampler = "DPM++ 2M Karras" |
214 | 315 | }; |
215 | 316 | } |
216 | | - |
217 | | - [GeneratedRegex("""\s*([\w ]+):\s*("(?:\\.|[^\\"])+"|[^,]*)(?:,|$)""")] |
218 | | - private static partial Regex ParametersFieldsRegex(); |
219 | 317 | } |
0 commit comments