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;
}
}
}