最近发现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中的内容按字符处理,碰到特殊符号(比如#,*,\n)就将符号及跟随的一段内容解析成相应的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[] InitializeParsers(CommonMarkSettings settings){ var strikethroughTilde = 0 != (settings.AdditionalFeatures & CommonMarkAdditionalFeatures.StrikethroughTilde); var p = new Func [strikethroughTilde ? 127 : 125]; p['\n'] = 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("
" : " | "); writer.WriteConstant(tds[j]); writer.WriteConstant(i == 1 ? "" : ""); } writer.WriteConstant(" |
---|
附上源码和dll:
ok,可以愉快的把md的笔记都粘来了~
参考文献: