C# Syntax Highlighter 2.0
As anybody who looked at the source code for the previous post might have noticed that I have indeed succeeded in cleaning up my syntax highlighter to produce the UI in a more pleasant manner. Iâve also included stylings for more language-specific elements (such as namespaces, classes, and operators). The line-handler is also much simpler and much more efficient. And finally, the spaces and the tabs (i.e., the presentation) is no longer present in the actual code (which is how it should be; at any rate, a list of ten spaces doesnât even show up, so weâre better off getting rid of it).
The Code:
using System;using System.Text;using System.Text.RegularExpressions;namespace SG.Net.Utilities.SyntaxHighlighter{public class CsharpSyntaxHighlighter{#region "Data and Data Access"private bool useRegions = false;private bool useLineNumbers = true;public bool UseRegions{get { return useRegions; }set { useRegions = value; }}public bool UseLineNumbers{get { return useLineNumbers; }set { useLineNumbers = value; }}#endregionpublic string Highlight(string code){Regex all = new Regex(MakePatterns(), RegexOptions.Singleline);code = all.Replace(code, new MatchEvaluator(HandleMatch));//Handle LinesStringBuilder line = new StringBuilder();//Matches line with tabsline.Append(@"(^ +.*?$)|");//Matches line with multiple spaces at the startline.Append(@"(^ {4,}.*?$)|");//Matches all other linesline.Append(@"(^.*?$)");Regex lines = new Regex(line.ToString(), RegexOptions.Multiline);code = lines.Replace(code, new MatchEvaluator(HandleLine));//Break multi-line comments properlyRegex mlcToLines = new Regex(@"/*.*?*/", RegexOptions.Singleline);code = mlcToLines.Replace(code, new MatchEvaluator(HandleMLC));//Break hard strings properlyRegex hardStrToLines = new Regex(@"@".*?(?<!\)"", RegexOptions.Singleline);code = hardStrToLines.Replace(code, new MatchEvaluator(HandleHardStrings));//Trim all extra white spacecode = Regex.Replace(code, " {2,}", String.Empty);return " <ol class = "code"> " + code + " </ol> ";}#region "Helper Methods"private string MakePatterns(){StringBuilder patterns = new StringBuilder();//Regular expression for single-quote stringspatterns.Append(@"(â[^ ]*?(?<!\)â)|");//Regular expression for double-quote stringspatterns.Append(@"((?<!@)"[^ ]*?(?<!\)")|");//Regular expression for hard stringspatterns.Append(@"(@".*?(?<!\)")|");//Regular expression for single-line commentspatterns.Append(@"(/(?!//)/[^ ]*)|");//Regular expression for formal documentation commentspatterns.Append(@"(///[^ ]*)|");//Regular expression for multi-line commentspatterns.Append(@"(/*.*?*/)|");//Regular expression for language-specific syntax//such as keywords, operators, namespaces, classes,//and functions.patterns.Append(GetSpecialSyntax());return patterns.ToString();}private string GetSpecialSyntax(){StringBuilder specialSyntax = new StringBuilder();specialSyntax.Append(GetOperators() + "|");specialSyntax.Append(GetKeywords() + "|");specialSyntax.Append(GetNamespaces() + "|");specialSyntax.Append(GetClasses() + "|");specialSyntax.Append(GetPreprocessorDirectives());return specialSyntax.ToString();}private string GetOperators(){StringBuilder ops = new StringBuilder(@"/s+(!|!=|%|%=|&|&&|&=|(|)|*|*=|+|++|+=|-|â|-=|->|.|/|/=|:|<|<<|<<=|<=|=|==|>|>=|>>|>>=|?|[|]|^|^=|{||||=||||}|~)/s+");ops.Replace(" ", "");ops.Replace(" ", "");ops.Replace(" ", "");ops.Replace(" ", "");ops.Replace("/s", " ");return ops.ToString();}private string GetKeywords(){StringBuilder kwds = new StringBuilder(@"b(A list of all the keywords, omitted for brevity)b");kwds.Replace(" ", "");kwds.Replace(" ", "");kwds.Replace(" ", "");kwds.Replace(" ", "");return kwds.ToString();}private string GetNamespaces(){StringBuilder nsps = new StringBuilder(@"b.?(A list of all the namespaces, omitted for brevity).?;?b");nsps.Replace(" ", "");nsps.Replace(" ", "");nsps.Replace(" ", "");nsps.Replace(" ", "");return nsps.ToString();}private string GetClasses(){StringBuilder classes = new StringBuilder(@"b.?(A list of all the classes, ommitted for brevity)(?)?b");classes.Replace(" ", "");classes.Replace(" ", "");classes.Replace(" ", "");classes.Replace(" ", "");return classes.ToString();}//Doesnât work properly. For example, this matches//#region in blah#region. But for some reason, /b//isnât working for this one.private string GetPreprocessorDirectives(){StringBuilder preproc = new StringBuilder(@"(#if|#else|#elif|#endif|#define|#undef|#warning|#error|#line|#region|#endregion)");preproc.Replace(" ", "");preproc.Replace(" ", "");preproc.Replace(" ", "");preproc.Replace(" ", "");preproc.Replace("/s", " ");return preproc.ToString();}#endregion#region "Match Handlers"private string HandleMatch(Match m){//Stringsif (m.Groups[1].Success || m.Groups[2].Success || m.Groups[3].Success){return "<span class = "str">" + m.Value + "</span>";}//Single-line commentselse if (m.Groups[4].Success){return "<span class = "slc">" + m.Value + "</span>";}//Formal documentation commentselse if (m.Groups[5].Success){return "<span class = "fdc">" + m.Value + "</span>";}//Multi-Line Commentselse if (m.Groups[6].Success){return "<span class = "mlc">" + m.Value + "</span>";}//Operatorselse if (m.Groups[7].Success){return "<span class = "op">" + m.Value + "</span>";}//Keywordselse if (m.Groups[8].Success){return "<span class = "kwd">" + m.Value + "</span>";}//Namespaceselse if (m.Groups[9].Success){return "<span class = "nsp">" + m.Value + "</span>";}//Classeselse if (m.Groups[10].Success){return "<span class = "cls">" + m.Value + "</span>";}//Preprocessor directiveselse if (m.Groups[11].Success){return "<span class = "preproc">" + m.Value + "</span>";}elsereturn m.Value;}private string HandleLine(Match m){//If the line is empty, add an to make it show upif (m.Value.Trim() == String.Empty)return "<li> </li> ";//Line with tabselse if (m.Groups[1].Success){//For each tab, weâre going to add 25px of left paddingint numberOfTabs = Regex.Match(m.Value, @"^ +").Length;string line = "<li style={0}padding-left:{1}px;{0}>{2}</li> ";return String.Format(line, """, numberOfTabs * 25, m.Value.TrimEnd(â â, â â));}//Line with multiple spaceselse if (m.Groups[2].Success){//Weâre going to add 25px of left padding for every 4 spacesint numberOfSpaces = Regex.Match(m.Value, @"^ {4,}").Length;numberOfSpaces /= 4;string line = "<li style={0}padding-left:{1}px;{0}>{2}</li> ";return String.Format(line, """, numberOfSpaces * 25, m.Value.TrimEnd(â â, â â));}//All other lineselse{return "<li>" + m.Value.TrimEnd(â â, â â) + "</li> ";}}private string HandleMLC(Match m){StringBuilder value = new StringBuilder(m.Value);value.Replace("<li>", "<li><span class = "mlc">");value.Replace("px;">", "px;"><span class = "mlc">");value.Replace("</li>", "<span></li>");return value.ToString();}private string HandleHardStrings(Match m){StringBuilder value = new StringBuilder(m.Value);value.Replace("<li>", "<li><span class = "str">");value.Replace("px;">", "px;"><span class = "str">");value.Replace("</li>", "<span></li>");return value.ToString();}#endregion}}