using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Documents; using System.Text.RegularExpressions; using System.Windows.Media; namespace MUDClientEssentials { public struct MUDTextRun { public string Content; public int ForegroundColor; public int BackgroundColor; } //scans incoming text for ANSI control sequences to provide a stream of styled text for end-user viewing class ANSIColorParser { //regular expression to fit ANSI control sequences public readonly string ansiControlRegEx = (char)27 + @"\[" + "[^@-~]*" + "[@-~]"; //current settings (with defaults) private int foregroundColor = 7; private int backgroundColor = 0; private bool brightColors = true; //scans incoming text for ANSI control sequences, parses them, and returns a list of styled text runs public List<MUDTextRun> Translate(string text) { //start with an empty list of runs List<MUDTextRun> returnRuns = new List<MUDTextRun>(); //argument validation if(string.IsNullOrEmpty(text)) return returnRuns; //in simplest case, the generated run will represent the entire string int runStartIndex = 0; int runEndIndex = text.Length - 1; //find all the control sequences MatchCollection matches = Regex.Matches(text, this.ansiControlRegEx); //for each control sequence foreach (Match match in matches) { string matchValue = match.Value; //identify the operation by grabbing the last character char operationToken = matchValue[matchValue.Length - 1]; //identify the arguments by splitting on semicolon, then converting to integers string argsString = matchValue.Substring(2, matchValue.Length - 3); string[] argsArray = argsString.Split(';'); List<int> arguments = new List<int>(); foreach (string argument in argsArray) { try { arguments.Add(int.Parse(argument)); } //if we can't convert to an integer, it's a badly formed parameter and we'll ignore it catch (FormatException) { } catch (OverflowException) { } } //now apply whatever changes are necessary according to the operation and parameters //'m' is "select graphics rendition", and indicates a style change //we need a new run for each continuous string of same-style characters //so then, ending the current run and starting a new one if (operationToken.Equals('m')) { //figure out start index, end index, and text of the completed run (remember runStartIndex initialized to zero) //last run ends just before we encountered the ANSI control sequence runEndIndex = match.Index - 1; string runText = text.Substring(runStartIndex, runEndIndex - runStartIndex + 1); //build a run out of the current color settings and text up until this point MUDTextRun newRun; newRun.Content = runText; newRun.ForegroundColor = this.foregroundColor; newRun.BackgroundColor = this.backgroundColor; returnRuns.Add(newRun); //note the new start index of the next run, which will start after the end of the control sequence runStartIndex = match.Index + match.Length; //assume the next run will continue until the end of the string, until proven otherwise runEndIndex = text.Length - 1; //parameters will determine the style of the next run, and there may be several parameters foreach (int param in arguments) { //reset to defaults if (param == 0) { this.brightColors = true; this.foregroundColor = 15; this.backgroundColor = 0; } //bright colors on if (param == 1) { this.brightColors = true; } //bright colors off else if (param == 22) this.brightColors = false; //set foreground color else if (param >= 30 && param <= 37) { this.foregroundColor = param - 30; if (this.brightColors) this.foregroundColor += 8; } //set background color else if (param >= 40 && param <= 47) { this.backgroundColor = param - 40; if (this.brightColors) this.backgroundColor += 8; } //default background color else if (param == 49) { this.backgroundColor = 0; if (this.brightColors) this.backgroundColor += 8; } //default foreground color else if (param == 39) { this.foregroundColor = 7; if (this.brightColors) this.foregroundColor += 8; } } } //if the ansi control sequence has an unsupported operation code, show it in the UI highlighted in a bright color else { MUDTextRun controlSequenceRun; controlSequenceRun.Content = matchValue; controlSequenceRun.BackgroundColor = 14; controlSequenceRun.ForegroundColor = 0; returnRuns.Add(controlSequenceRun); } } //now that we're done with the last control sequence, build a run from any remaining ("trailing") text //if there were no control sequences at all, the "trailing text" would be the entire string int trailingTextStartIndex = 0; //if there were control sequences, the trailing text would start after the last control sequence ended if (matches.Count > 0) { //get the last control sequence Match lastMatch = matches[matches.Count - 1]; //figure out where it ends trailingTextStartIndex = lastMatch.Index + lastMatch.Length; } //if there's any trailing text, build a run from that text using current style if (trailingTextStartIndex < text.Length - 1) { MUDTextRun trailingRun; trailingRun.Content = text.Substring(trailingTextStartIndex); trailingRun.ForegroundColor = this.foregroundColor; trailingRun.BackgroundColor = this.backgroundColor; returnRuns.Add(trailingRun); } return returnRuns; } } }