专栏C#代码高亮

B站专栏是没有自带代码高亮功能的,通过拷贝特定的html到专栏编辑器可以实现代码高亮。
可惜支持的颜色数量很少,只有专栏自带的文字颜色和prism.js中的几种颜色:

代码高亮效果如下:
using System;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using C = System.Console;
using static System.Console;
namespace MyNamespace {
class Program {
[Flags]
enum RGB { R = 1, G = 2, B = 4 }
[StructLayout(LayoutKind.Sequential)]
struct Point : IEquatable<Point> {
public static readonly Point Empty = default;
public int X;
public int Y;
public Point(int x, int y) => (X, Y) = (x, y);
public void Deconstruct(out int x, out int y)
=> (x, y) = (X, Y);
bool IEquatable<Point>.Equals(Point other) {
return X == other.X && Y == other.Y;
}
public override string ToString()
=> this switch {
(0, 0) => "原点",
{ X: 1, Y: 0 } => "(1, 0)",
Point{ X: 0, Y: 1 } => "(0, 1)",
_ => $"({X}, {Y})"
};
public static implicit operator Point((int X, int Y) p)
=> new Point(p.X, p.Y);
}
delegate void PrintHandler(object obj);
/// <summary>
/// 参考<see cref="Console.WriteLine(object)"/>
/// </summary>
static readonly PrintHandler Print = Console.WriteLine;
unsafe static void Main(string[] args) {
PrintEnumNames<RGB>();
Span<Point> points = stackalloc[] {
new Point(1, 1),
new Point{ X = 2, Y = 3 },
Point.Empty,
};
PrintSpan(points);
WriteLine(sizeof(Point));
WriteLine(typeof(Point));
WriteLine(nameof(Point));
using var mem = new System.IO.MemoryStream();
int i = 0;
while (true) {
if (i >= 256) break;
else {
mem.WriteByte((byte)i);
i++;
}
}
using (var mem2 = new System.IO.MemoryStream()) {
for (i = 0; i <= 0xff; i++) {
mem2.WriteByte((Byte)i);
}
}
try {
throw new Exception();
} catch (Exception e) when (true) {
switch (e) {
case ArgumentException{ ParamName: nameof(args) }:
Console.WriteLine("hello");
break;
default: throw e;
}
} finally {
}
}
static void PrintSpan<T>(Span<T> structs) where T : unmanaged {
foreach (ref readonly var s in structs) {
Print(s);
}
unsafe {
fixed (T* p = structs) {
for (int i = 0; i < structs.Length; i++) {
Print(p[i]);
}
}
}
}
/// <summary>
/// 输出所有枚举成员名称
/// </summary>
/// <typeparam name="TEnum">枚举类型</typeparam>
static void PrintEnumNames<TEnum>() where TEnum : Enum {
foreach (var name in Enum.GetNames(typeof(TEnum))) {
C.WriteLine(name);
}
}
}
unsafe public static class FastMath {
const int LogTableLevel = 13; // 64KB
static readonly double* LogTable = (double*)
Marshal.AllocHGlobal((1 << LogTableLevel) * sizeof(double));
static FastMath() {
const int N = 1 << LogTableLevel;
for (int i = 0; i < N; i++) {
LogTable[i] = Math.Log(1 + (double)i / N, 2);
}
}
/// <summary>
/// 快速log2
/// </summary>
/// <param name="x"><paramref name="x"/>必须大于0</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double Log2(double x) {
const int N = 1 << LogTableLevel;
ulong t = *(ulong*)&x;
int exp = (int)(t >> 52) - 0x3ff;
return LogTable[(t >> (52 - LogTableLevel)) & (N - 1)] + exp;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.RegularExpressions;
namespace Calculator {
using Expr = Expression;
using ParamMap = Dictionary<string, ParameterExpression>;
class Program {
/// <summary>
/// 表达式解析器
/// </summary>
/// <remarks>
/// define
/// : param* ':' addSub
/// ;
/// param
/// : Id
/// ;
/// addSub
/// : mulDiv (('+'|'-') mulDiv)*
/// ;
/// mulDiv
/// : pow (('*'|'/') pow)*
/// ;
/// pow
/// : unary ('^' pow)?
/// ;
/// unary
/// : ('+'|'-') unary
/// | atom
/// ;
/// atom
/// : Id
/// | Number
/// ;
/// </remarks>
class Parser {
public class ParseException : Exception {
public ParseException(int index, string message)
: base($"位置({index})解析错误,{message}") { }
}
static readonly MethodInfo powMethod = typeof(Math).GetMethod("Pow");
/// <summary>
/// 匹配Id或Number的正则表达式
/// </summary>
static readonly Regex idOrNumRegex
= new Regex(@"^((?<id>[a-zA-Z_][a-zA-Z_\d]*)|(\d*\.\d+|\d+\.?)(e[+\-]?\d+)?)",
RegexOptions.Compiled | RegexOptions.ExplicitCapture);
readonly ParamMap @params = new ParamMap();
readonly string expr;
int i = 0;
Parser(string expr) {
this.expr = expr;
ParseMulDiv = GenerateBinaryParser(ParsePow,
("*", (x, y) => Expr.Multiply(x, y)),
("/", (x, y) => Expr.Divide(x, y)));
ParseAddSub = GenerateBinaryParser(ParseMulDiv,
("+", (x, y) => Expr.Add(x, y)),
("-", (x, y) => Expr.Subtract(x, y)));
}
/// <summary>
/// 跳过空白字符
/// </summary>
void SkipWhiteSpace() {
while (i < expr.Length && char.IsWhiteSpace(expr[i])) i++;
}
/// <summary>
/// 当前匹配位置之后如果是<paramref name="s"/>则吃掉
/// </summary>
bool Eat(string s) {
SkipWhiteSpace();
if (i + s.Length <= expr.Length && expr.AsSpan(i, s.Length).SequenceEqual(s)) {
i += s.Length;
return true;
}
return false;
}
/// <summary>
/// 生成左结合的二元表达式解析器
/// </summary>
/// <param name="nextParser">下一级解析器</param>
/// <param name="ops"></param>
Func<Expr> GenerateBinaryParser(Func<Expr> nextParser,
params (string Op, Func<Expr, Expr, Expr> Creator)[] ops) {
return () => {
var left = nextParser();
int index;
while ((index = Array.FindIndex(ops, p => Eat(p.Op))) >= 0) {
left = ops[index].Creator(left, nextParser());
}
return left;
};
}
/// <summary>
/// param
/// </summary>
ParameterExpression ParseParam() {
SkipWhiteSpace();
var group = idOrNumRegex.Match(expr, i, expr.Length - i).Groups["id"];
if (group.Success) {
i += group.Length;
return Expr.Parameter(typeof(double), group.Value);
}
return null;
}
/// <summary>
/// define
/// </summary>
LambdaExpression ParseDefine() {
while (ParseParam() is ParameterExpression param) {
@params.Add(param.Name, param);
}
if (!Eat(":")) throw new ParseException(i, "期望: ':'");
return Expr.Lambda(ParseAddSub(), @params.Values);
}
/// <summary>
/// addSub
/// </summary>
readonly Func<Expr> ParseAddSub;
/// <summary>
/// mulDiv
/// </summary>
readonly Func<Expr> ParseMulDiv;
/// <summary>
/// pow
/// </summary>
Expr ParsePow() {
var left = ParseUnary();
return Eat("^") ? Expr.Call(powMethod, left, ParsePow()) : left;
}
/// <summary>
/// unary
/// </summary>
Expr ParseUnary() {
if (Eat("-")) return Expr.Negate(ParseUnary());
if (Eat("+")) return ParseUnary();
return ParseAtom();
}
/// <summary>
/// atom
/// </summary>
Expr ParseAtom() {
SkipWhiteSpace();
var m = idOrNumRegex.Match(expr, i, expr.Length - i);
if (m.Success) {
i += m.Length;
if (m.Groups["id"].Success) {
if (@params.TryGetValue(m.Groups["id"].Value, out var param)) {
return param;
}
throw new ParseException(i, $"未定义的参数:{m.Groups["id"].Value}");
} else {
return Expr.Constant(double.Parse(m.Value), typeof(double));
}
} else if (Eat("(")) {
var result = ParseAddSub();
if (!Eat(")")) throw new ParseException(i, "期望: ')'");
return result;
}
throw new ParseException(i, "期望: '(' 或 <参数> 或 <数字>");
}
public static LambdaExpression Parse(string expr) {
var parser = new Parser(expr);
var result = parser.ParseDefine();
parser.SkipWhiteSpace();
if (parser.i != expr.Length)
throw new ParseException(parser.i, "未能完全解析表达式");
return result;
}
}
delegate double CalcHandler(params double[] args);
static CalcHandler Lambda(string expr) {
var lambda = Parser.Parse(expr);
var paramCount = lambda.Parameters.Count;
var paramsParam = Expr.Parameter(typeof(double[]));
var invoke = Expr.Invoke(lambda, Enumerable.Range(0, paramCount)
.Select(i => Expr.ArrayIndex(paramsParam, Expr.Constant(i))));
return Expr.Lambda<CalcHandler>(invoke, paramsParam).Compile();
}
static TDelegate Lambda<TDelegate>(string expr) where TDelegate : Delegate
=> (TDelegate)Parser.Parse(expr).Compile();
static void Main(string[] args) {
static void Print(double value) => Console.WriteLine(value);
Print(Lambda(": 233")()); // 233
Print(Lambda("x y: x + y")(22, 33)); // 55
Print(Lambda("a b c: -a - b - c")(1, 2, 3)); // -6
Print(Lambda("x1 y1 x2 y2: ((x1 - x2) ^ 2 + (y1 - y2) ^ 2) ^ 0.5")(0, 0, 3, 4)); // 5
Print(Lambda("x1 y1 x2 y2: (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2)")(0, 0, 3, 4)); // 25
Print(Lambda<Func<double>>(": 2 ^ 3 ^ 2")()); // 512
}
}
}
具体操作
首先到github上clone这个项目:https://github.com/ibukisaar/BilibiliCSharpHighlight
注意:该项目使用VS2019和.NET Core 3.0
使用如下代码生成html并保存到文件。
string code = File.ReadAllText("test.cs");
var highlight = new BilibiliCSharpHighlight(
code, // 代码
showError: true, // 如果代码有错误则提示
withReferences: null // 添加dll引用
);
var html = highlight.ToHtml();
// html保存到 Z:\highlight\csharp.html
File.WriteAllText(@"Z:\highlight\csharp.html",
$@"<html>
<head>
<link rel=""stylesheet"" type=""text/css"" href=""./prism.css"" />
</head>
<body>
<figure class=""code-box"">
<pre class="" language-csharp"" data-lang=""application/csharp@cs@CSharp""
><code class="" language-csharp"">{html}</code></pre>
</figure>
</body>
</html>
");
将css目录中的prism.css放到和csharp.html相同的目录。

使用浏览器预览csharp.html内容。

最后从浏览器选中代码片段并复制,粘贴到专栏编辑器即可。


