最近发现markdown真的好方便,所以一直都在为知客户端上写总结(富文本编辑器虽然功能强大,但总是把文章弄得乱糟糟的)。
接下来就打算为自己的博客加上markdown解析的功能,然而.net平台下并没有特别合适的类库,一些前端框架倒是可以用,但是想要二次加工就不是很方便了(比如想生成一个带锚点的目录)。最后看到博客园官博提到CommonMark.NET,这个是一个markdown的标准解析引擎,但对于标准外的语法没有支持(比如Table),So,我们自己来加吧....
PS:实现的还是有些粗糙,照源码的思路似乎应该为Table在Block层面上解析,而这里直接使用Inline的方式(就是碰到竖线就认为是Table开始了,而Block应该对整个代码块进行判断,是否是一个Table),但姑且这样用了,有时间再完善。
源码逻辑
先来了解下现有的解析逻辑:
public static void Convert(TextReader source, TextWriter target, CommonMarkSettings settings = null)
{
if (settings == null)
settings = CommonMarkSettings.Default;
var document = ProcessStage1(source, settings);
ProcessStage2(document, settings);
ProcessStage3(document, target, settings);
}
处理分为3个阶段
- markdown -> blocks
按markdown语法创建代码块(比如标题、段落、列表、代码块、引用等),并组成一个树形结构,每个节点有FirstChild和NextSibling的索引,代码中的document是根节点。 - blocks -> inlines
每个block中的内容按字符处理,碰到特殊符号(比如#,*, )就将符号及跟随的一段内容解析成相应的inline对象(标题,粗体、(软)换行),并用stack的方式实现标签的闭合。
Inline对象也组成一个树形结构,有FirstChild和NextSibling的索引(但似乎只用到了NextSibling?) - blocks/inlines -> html
遍历所有Block中的Inline对象,输出相应的html代码
Table解析实现
Block解析部分比较复杂,这里采用Inline解析时把第一个竖线到最后一个竖线封装一个Table类型的Inline(人为控制一个Block只有一个Table吧),再实现Table类型的渲染方法即可。
为Inline增加一个类型Table:
public enum InlineTag : byte
{
String = 0,
SoftBreak,
LineBreak,
Code,
RawHtml,
Emphasis,
Strong,
Link,
Image,
Strikethrough,
Table //新增的Table类型
}
为‘|’符号增加处理方法:
internal static Func<Subject, Inline>[] InitializeParsers(CommonMarkSettings settings)
{
var strikethroughTilde = 0 != (settings.AdditionalFeatures & CommonMarkAdditionalFeatures.StrikethroughTilde);
var p = new Func<Subject, Inline>[strikethroughTilde ? 127 : 125];
p['
'] = handle_newline;
p['`'] = handle_backticks;
p['\'] = handle_backslash;
p['&'] = HandleEntity;
p['<'] = handle_pointy_brace;
p['_'] = HandleEmphasis;
p['*'] = HandleEmphasis;
p['['] = HandleLeftSquareBracket;
p[']'] = HandleRightSquareBracket;
p['!'] = HandleExclamation;
p['|'] = HandleTable; //增加'|'的解析方法,把|到|封装为一个Table类型的Inline
if (strikethroughTilde)
p['~'] = HandleTilde;
return p;
}
private static Inline HandleTable(Subject subj)
{
//这里偷懒了,可以加个校验,确定是个table,否则单个'|'也会被解析
var start = subj.Buffer.IndexOf('|');
var last = subj.Buffer.LastIndexOf('|');
var inlTab = new Inline(InlineTag.Table, subj.Buffer.Substring(start, last - start + 1));
subj.Position = last + 1;
return inlTab;
}
Table类型Inline的渲染,InlinesToHtml:
switch (inline.Tag)
{
case InlineTag.Table:
//tab
writer.WriteConstant("<table class="table table-bordered table-striped">");
var lines = inline.LiteralContentValue.ToString().Split(new char[] { '
' }, System.StringSplitOptions.RemoveEmptyEntries);
int i = 0;
foreach (var line in lines)
{
i++;
if (line.Split(new char[] { '|', '-', ' ', '
' }, System.StringSplitOptions.RemoveEmptyEntries).Length == 0)
{
continue;
}
writer.WriteConstant("<tr>");
var tds = line.Split(new char[] { '|' });
for (int j = 1; j < tds.Length - 1; j++)
{
writer.WriteConstant(i == 1 ? "<th>" : "<td>");
writer.WriteConstant(tds[j]);
writer.WriteConstant(i == 1 ? "</th>" : "</td>");
}
writer.WriteConstant("</tr>");
}
writer.WriteConstant("</table>");
break;
....... ......
}
ok,可以愉快的把md的笔记都粘来了~