Advertisement
Guest User

Untitled

a guest
Jul 18th, 2019
90
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.99 KB | None | 0 0
  1. /// <summary>
  2. /// A basic implementation of a pretty-printer or syntax highlighter for C# soure code.
  3. /// </summary>
  4. public class SourceColorer
  5. {
  6. private string _commentCssClass;
  7. private string _keywordCssClass;
  8. private string _quotesCssClass;
  9. private string _typeCssClass;
  10. private bool _addStyleDefinition;
  11. private HashSet<string> _keywords;
  12. private bool _addPreTags;
  13.  
  14. /// <summary>
  15. /// Gets the list of reserved words/keywords.
  16. /// </summary>
  17. public HashSet<string> Keywords
  18. {
  19. get { return _keywords; }
  20. }
  21.  
  22. /// <summary>
  23. /// Gets or sets the CSS class used for comments. The default is 'comment'.
  24. /// </summary>
  25. public string CommentCssClass
  26. {
  27. get { return _commentCssClass; }
  28. set { _commentCssClass = value; }
  29. }
  30.  
  31. /// <summary>
  32. /// Gets or sets the CSS class used for keywords. The default is 'keyword'.
  33. /// </summary>
  34. public string KeywordCssClass
  35. {
  36. get { return _keywordCssClass; }
  37. set { _keywordCssClass = value; }
  38. }
  39.  
  40. /// <summary>
  41. /// Gets or sets the CSS class used for string quotes. The default is 'quotes'.
  42. /// </summary>
  43. public string QuotesCssClass
  44. {
  45. get { return _quotesCssClass; }
  46. set { _quotesCssClass = value; }
  47. }
  48.  
  49. /// <summary>
  50. /// Gets or sets the CSS class used for types. The default is 'type'.
  51. /// </summary>
  52. public string TypeCssClass
  53. {
  54. get { return _typeCssClass; }
  55. set { _typeCssClass = value; }
  56. }
  57.  
  58. /// <summary>
  59. /// Whether to add the CSS style definition to the top of the highlighted code.
  60. /// </summary>
  61. public bool AddStyleDefinition
  62. {
  63. get { return _addStyleDefinition; }
  64. set { _addStyleDefinition = value; }
  65. }
  66.  
  67. /// <summary>
  68. /// Whether to insert opening and closing pre tags around the highlighted code.
  69. /// </summary>
  70. public bool AddPreTags
  71. {
  72. get { return _addPreTags; }
  73. set { _addPreTags = value; }
  74. }
  75.  
  76. /// <summary>
  77. /// Initializes a new instance of the <see cref="SourceColorer"/> class.
  78. /// </summary>
  79. public SourceColorer()
  80. {
  81. _addStyleDefinition = true;
  82. _commentCssClass = "comment";
  83. _keywordCssClass = "keyword";
  84. _quotesCssClass = "quotes";
  85. _typeCssClass = "type";
  86. _keywords = new HashSet<string>()
  87. {
  88. "static", "using", "true", "false","new",
  89. "namespace", "void", "private", "public",
  90. "bool", "string", "return", "class","internal",
  91. "const", "readonly", "int", "double","lock",
  92. "float", "if", "else", "foreach", "for","var",
  93. "get","set","byte\\[\\]","char\\[\\]","int\\[\\]","string\\[\\]" // dumb array matching. Escaped as [] is regex syntax
  94. };
  95. }
  96.  
  97. /// <summary>
  98. /// Highlights the specified source code and returns it as stylised HTML.
  99. /// </summary>
  100. /// <param name="source">The source code.</param>
  101. /// <returns></returns>
  102. public string Highlight(string source)
  103. {
  104. StringBuilder builder = new StringBuilder();
  105. if (AddStyleDefinition)
  106. {
  107. builder.Append("<style>");
  108. builder.AppendFormat(".{0} {{ color: #0000FF }} ", KeywordCssClass);
  109. builder.AppendFormat(".{0} {{ color: #2B91AF }} ", TypeCssClass);
  110. builder.AppendFormat(".{0} {{ color: green }} ", CommentCssClass);
  111. builder.AppendFormat(".{0} {{ color: maroon }} ", QuotesCssClass);
  112. builder.Append("</style>");
  113. }
  114.  
  115. if (AddPreTags)
  116. builder.Append("<pre>");
  117.  
  118. builder.Append(HighlightSource(source));
  119.  
  120. if (AddPreTags)
  121. builder.Append("</pre>");
  122.  
  123. return builder.ToString();
  124. }
  125.  
  126. /// <summary>
  127. /// Occurs when the source code is highlighted, after any style (CSS) definitions are added.
  128. /// </summary>
  129. /// <param name="content">The source code content.</param>
  130. /// <returns>The highlighted source code.</returns>
  131. protected virtual string HighlightSource(string content)
  132. {
  133. if (string.IsNullOrEmpty(CommentCssClass))
  134. throw new InvalidOperationException("The CommentCssClass should not be null or empty");
  135. if (string.IsNullOrEmpty(KeywordCssClass))
  136. throw new InvalidOperationException("The KeywordCssClass should not be null or empty");
  137. if (string.IsNullOrEmpty(QuotesCssClass))
  138. throw new InvalidOperationException("The CommentCssClass should not be null or empty");
  139. if (string.IsNullOrEmpty(TypeCssClass))
  140. throw new InvalidOperationException("The TypeCssClass should not be null or empty");
  141.  
  142. // Some fairly secure token placeholders
  143. const string COMMENTS_TOKEN = "`````";
  144. const string MULTILINECOMMENTS_TOKEN = "~~~~~";
  145. const string QUOTES_TOKEN = "¬¬¬¬¬";
  146.  
  147. // Remove /* */ quotes, taken from ostermiller.org
  148. Regex regex = new Regex(@"/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/", RegexOptions.Singleline);
  149. List<string> multiLineComments = new List<string>();
  150. if (regex.IsMatch(content))
  151. {
  152. foreach (Match item in regex.Matches(content))
  153. {
  154. if (!multiLineComments.Contains(item.Value))
  155. multiLineComments.Add(item.Value);
  156. }
  157. }
  158.  
  159. for (int i = 0; i < multiLineComments.Count; i++)
  160. {
  161. content = content.ReplaceToken(multiLineComments[i], MULTILINECOMMENTS_TOKEN, i);
  162. }
  163.  
  164. // Remove the quotes first, so they don't get highlighted
  165. List<string> quotes = new List<string>();
  166. bool onEscape = false;
  167. bool onComment1 = false;
  168. bool onComment2 = false;
  169. bool inQuotes = false;
  170. int start = -1;
  171. for (int i = 0; i < content.Length; i++)
  172. {
  173. if (content[i] == '/' && !inQuotes && !onComment1)
  174. onComment1 = true;
  175. else if (content[i] == '/' && !inQuotes && onComment1)
  176. onComment2 = true;
  177. else if (content[i] == '"' && !onEscape && !onComment2)
  178. {
  179. inQuotes = true; // stops cases of: var s = "// I'm a comment";
  180. if (start > -1)
  181. {
  182. string quote = content.Substring(start, i - start + 1);
  183. if (!quotes.Contains(quote))
  184. quotes.Add(quote);
  185. start = -1;
  186. inQuotes = false;
  187. }
  188. else
  189. {
  190. start = i;
  191. }
  192. }
  193. else if (content[i] == '\\' || content[i] == '\'')
  194. onEscape = true;
  195. else if (content[i] == '\n')
  196. {
  197. onComment1 = false;
  198. onComment2 = false;
  199. }
  200. else
  201. {
  202. onEscape = false;
  203. }
  204. }
  205.  
  206. for (int i = 0; i < quotes.Count; i++)
  207. {
  208. content = content.ReplaceToken(quotes[i], QUOTES_TOKEN, i);
  209. }
  210.  
  211. // Remove the comments next, so they don't get highlighted
  212. regex = new Regex("(/{2,3}.+)\n", RegexOptions.Multiline);
  213. List<string> comments = new List<string>();
  214. if (regex.IsMatch(content))
  215. {
  216. foreach (Match item in regex.Matches(content))
  217. {
  218. if (!comments.Contains(item.Value + "\n"))
  219. comments.Add(item.Value);
  220. }
  221. }
  222.  
  223. for (int i = 0; i < comments.Count; i++)
  224. {
  225. content = content.ReplaceToken(comments[i], COMMENTS_TOKEN, i);
  226. }
  227.  
  228. // Highlight single quotes
  229. content = Regex.Replace(content, "('.{1,2}')", "<span class=\"quote\">$1</span>", RegexOptions.Singleline);
  230.  
  231. // Highlight class names based on the logic: {space OR start of line OR >}{1 capital){alphanumeric}{space}
  232. regex = new Regex(@"((?:\s|^)[A-Z]\w+(?:\s))", RegexOptions.Singleline);
  233. List<string> highlightedClasses = new List<string>();
  234. if (regex.IsMatch(content))
  235. {
  236. foreach (Match item in regex.Matches(content))
  237. {
  238. string val = item.Groups[1].Value;
  239. if (!highlightedClasses.Contains(val))
  240. highlightedClasses.Add(val);
  241. }
  242. }
  243.  
  244. for (int i = 0; i < highlightedClasses.Count; i++)
  245. {
  246. content = content.ReplaceWithCss(highlightedClasses[i], TypeCssClass);
  247. }
  248.  
  249. // Pass 2. Doing it in N passes due to my inferior regex knowledge of back/forwardtracking.
  250. // This does {space or [}{1 capital){alphanumeric}{]}
  251. regex = new Regex(@"(?:\s|\[)([A-Z]\w+)(?:\])", RegexOptions.Singleline);
  252. highlightedClasses = new List<string>();
  253. if (regex.IsMatch(content))
  254. {
  255. foreach (Match item in regex.Matches(content))
  256. {
  257. string val = item.Groups[1].Value;
  258. if (!highlightedClasses.Contains(val))
  259. highlightedClasses.Add(val);
  260. }
  261. }
  262.  
  263. for (int i = 0; i < highlightedClasses.Count; i++)
  264. {
  265. content = content.ReplaceWithCss(highlightedClasses[i], TypeCssClass);
  266. }
  267.  
  268. // Pass 3. Generics
  269. regex = new Regex(@"(?:\s|\[|\()([A-Z]\w+(?:<|<))", RegexOptions.Singleline);
  270. highlightedClasses = new List<string>();
  271. if (regex.IsMatch(content))
  272. {
  273. foreach (Match item in regex.Matches(content))
  274. {
  275. string val = item.Groups[1].Value;
  276. if (!highlightedClasses.Contains(val))
  277. highlightedClasses.Add(val);
  278. }
  279. }
  280.  
  281. for (int i = 0; i < highlightedClasses.Count; i++)
  282. {
  283. string val = highlightedClasses[i];
  284. val = val.Replace("<", "").Replace("<", "");
  285. content = content.ReplaceWithCss(highlightedClasses[i], val, "<", TypeCssClass);
  286. }
  287.  
  288. // Pass 4. new keyword with a type
  289. regex = new Regex(@"new\s+([A-Z]\w+)(?:\()", RegexOptions.Singleline);
  290. highlightedClasses = new List<string>();
  291. if (regex.IsMatch(content))
  292. {
  293. foreach (Match item in regex.Matches(content))
  294. {
  295. string val = item.Groups[1].Value;
  296. if (!highlightedClasses.Contains(val))
  297. highlightedClasses.Add(val);
  298. }
  299. }
  300.  
  301. // Replace the highlighted classes
  302. for (int i = 0; i < highlightedClasses.Count; i++)
  303. {
  304. content = content.ReplaceWithCss(highlightedClasses[i], TypeCssClass);
  305. }
  306.  
  307. // Highlight keywords
  308. foreach (string keyword in _keywords)
  309. {
  310. Regex regexKeyword = new Regex("(" + keyword + @")(>|>|\s|\n|;|<)", RegexOptions.Singleline);
  311. content = regexKeyword.Replace(content, "<span class=\"keyword\">$1</span>$2");
  312. }
  313.  
  314. // Shove the multiline comments back in
  315. for (int i = 0; i < multiLineComments.Count; i++)
  316. {
  317. content = content.ReplaceTokenWithCss(multiLineComments[i], MULTILINECOMMENTS_TOKEN, i, CommentCssClass);
  318. }
  319.  
  320. // Shove the quotes back in
  321. for (int i = 0; i < quotes.Count; i++)
  322. {
  323. content = content.ReplaceTokenWithCss(quotes[i], QUOTES_TOKEN, i, QuotesCssClass);
  324. }
  325.  
  326. // Shove the single line comments back in
  327. for (int i = 0; i < comments.Count; i++)
  328. {
  329. string comment = comments[i];
  330. // Add quotes back in
  331. for (int n = 0; n < quotes.Count; n++)
  332. {
  333. comment = comment.Replace(string.Format("{0}{1}{0}", QUOTES_TOKEN, n), quotes[n]);
  334. }
  335. content = content.ReplaceTokenWithCss(comment, COMMENTS_TOKEN, i, CommentCssClass);
  336. }
  337. return content;
  338. }
  339. }
  340.  
  341. public static class MoreExtensions
  342. {
  343. public static string ReplaceWithCss(this string content, string source, string cssClass)
  344. {
  345. return content.Replace(source, string.Format("<span class=\"{0}\">{1}</span>", cssClass, source));
  346. }
  347.  
  348. public static string ReplaceWithCss(this string content, string source, string replacement, string cssClass)
  349. {
  350. return content.Replace(source, string.Format("<span class=\"{0}\">{1}</span>", cssClass, replacement));
  351. }
  352.  
  353. public static string ReplaceWithCss(this string content, string source, string replacement, string suffix, string cssClass)
  354. {
  355. return content.Replace(source, string.Format("<span class=\"{0}\">{1}</span>{2}", cssClass, replacement, suffix));
  356. }
  357.  
  358. public static string ReplaceTokenWithCss(this string content, string source, string token, int counter, string cssClass)
  359. {
  360. string formattedToken = String.Format("{0}{1}{0}", token, counter);
  361. return content.Replace(formattedToken, string.Format("<span class=\"{0}\">{1}</span>", cssClass, source));
  362. }
  363.  
  364. public static string ReplaceToken(this string content, string source, string token, int counter)
  365. {
  366. string formattedToken = String.Format("{0}{1}{0}", token, counter);
  367. return content.Replace(source, formattedToken);
  368. }
  369. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement