private static Range ParseSpan( string token, ushort startIndexInclusive, ushort endIndexExclusive, CharColors defaultColors, int defaultDelay) { token = token.Remove(token.Length - 1).Substring(SpanStart.Length); Dictionary <string, List <string> > result = new Dictionary <string, List <string> >(); string[] pairs = token.Split(Space, StringSplitOptions.RemoveEmptyEntries); foreach (string pair in pairs) { string[] components = pair.Split('='); string key = components[0]; if (!result.TryGetValue(key, out List <string> values)) { values = new List <string>(); result[key] = values; } if (components.Length == 2) { string value = components[1]; if (value.Length > 1) { if (value[0] == '"' && value[value.Length - 1] == '"') { value = value.Substring(1, value.Length - 2); } } values.Add(value); } } ConsoleColor fg = defaultColors.Foreground; ConsoleColor bg = defaultColors.Background; int dtime = defaultDelay; foreach (KeyValuePair <string, List <string> > kvp in result) { switch (kvp.Key) { case "fg": if (!Enum.TryParse <ConsoleColor>(kvp.Value[0], out fg)) { throw new InvalidOperationException( $"Unrecognized color '{kvp.Value[0]}'"); } break; case "bg": if (!Enum.TryParse <ConsoleColor>(kvp.Value[0], out bg)) { throw new InvalidOperationException( $"Unrecognized color '{kvp.Value[0]}'"); } break; case "dtime": dtime = int.Parse(kvp.Value[0]); break; default: throw new InvalidOperationException( $"Unrecognized key '{kvp.Key}'"); } } return(new Range( startIndexInclusive, endIndexExclusive, new CharColors(fg, bg), dtime)); }
public FormattedString( string value, CharColors defaultColors, int defaultDelay = 0) { List <Range> ranges = new List <Range>(); if (value.Length == 0) { goto end; } int startIndex = 0; while (true) { // 1. Find the next '<span ' // a. If not found, goto exit int startOfNextSpan = value.IndexOf("<span ", startIndex); if (startOfNextSpan < 0) { goto exit; } // 2. Find the next > // a. If not found, goto exit int endOfNextSpan = value.IndexOf('>', startOfNextSpan); if (endOfNextSpan < 0) { goto exit; } int lengthOfNextSpan = endOfNextSpan - startOfNextSpan + 1; // 3. Find the next </span> // b. If not found, goto exit const string closeOfSpan = "</span>"; int closeOfNextSpan = value.IndexOf(closeOfSpan, endOfNextSpan); int closeOfNextSpanLength = closeOfSpan.Length; if (closeOfNextSpan < 0) { goto exit; } // 4. Copy the '<span ...>' string copy = value.Substring(startOfNextSpan, lengthOfNextSpan); // 5. Remove the '</span>' value = value.Remove(closeOfNextSpan, closeOfNextSpanLength); // 6. Remove the '<span ...>' value = value.Remove(startOfNextSpan, lengthOfNextSpan); // 7. Parse the copied '<span ...>', // a. Don't forget to correct for the removal of the '<span ...>', or else the indices will be wrong! Range range = ParseSpan( copy, (ushort)startOfNextSpan, // the start is unchanged after the removal (ushort)(closeOfNextSpan - lengthOfNextSpan), defaultColors, defaultDelay); // 8. Add the Range to the list ranges.Add(range); // 9. Goto 1 startIndex = range.EndIndexExclusive; } exit: LinkedList <Range> linkedList = new LinkedList <Range>(ranges); if (ranges.Count == 0) { linkedList.AddFirst( new Range( 0, (ushort)value.Length, defaultColors, defaultDelay)); } else if (linkedList.First.Value.StartIndexInclusive != 0) { linkedList.AddFirst( new Range( 0, linkedList.First.Value.StartIndexInclusive, defaultColors, defaultDelay)); } if (ranges.Count != 0) { // We already explcitly ensured the first value reaches the next span. Now, we just need to check: // * Current range's start == next range's end // * If there is no next range, next range's end must equal value's length. Otherwise, add a new range. LinkedListNode <Range>?current = linkedList.First; while (current != null) { LinkedListNode <Range> next = current.Next; if (next == null && current.Value.EndIndexExclusive != value.Length) { linkedList.AddAfter( current, new Range( current.Value.EndIndexExclusive, (ushort)value.Length, defaultColors, defaultDelay)); current = null; } else if (next != null && current.Value.EndIndexExclusive != next.Value.StartIndexInclusive) { linkedList.AddAfter( current, new Range( current.Value.EndIndexExclusive, next.Value.StartIndexInclusive, defaultColors, defaultDelay)); current = current.Next; } else { current = current.Next; } } } ranges = linkedList.ToList(); end: this.Value = value; this.Ranges = new Ranges(ranges); this.ContainsDelays = this.Ranges.Any(x => x.Delay > 0); }