为CommonMark.Net增加Table解析

最近发现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个阶段

  1. markdown -> blocks
    按markdown语法创建代码块(比如标题、段落、列表、代码块、引用等),并组成一个树形结构,每个节点有FirstChild和NextSibling的索引,代码中的document是根节点。
  2. blocks -> inlines
    每个block中的内容按字符处理,碰到特殊符号(比如#,*, )就将符号及跟随的一段内容解析成相应的inline对象(标题,粗体、(软)换行),并用stack的方式实现标签的闭合。
    Inline对象也组成一个树形结构,有FirstChild和NextSibling的索引(但似乎只用到了NextSibling?)
  3. 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;
        ....... ......
}

附上源码和dll:
dll下载
源码下载

ok,可以愉快的把md的笔记都粘来了~

参考文献:
原项目的github
用上CommonMark.NET,.NET平台终于有了好用的markdown引擎

原文地址:https://www.cnblogs.com/ace-lj/p/5363001.html