项目

Markdig 扩展和解析器

Markdig 的实现方式被设计得非常可扩展,即使是最基本的行为也可以改变和扩展。Markdig 扩展的基础机制是 IMarkdownExtension 接口,任何实现了该接口的类都可以注册到管道构建器中,从而直接修改最终进入处理流程的 BlockParserInlineParser 对象集合。

本文档将讨论 IMarkdownExtension 接口、BlockParser 抽象基类和 InlineParser 抽象基类,它们共同构成了扩展 Markdig 解析器的基石。

创建扩展

扩展可以从非常简单到非常复杂不等。

例如,一个简单的扩展可能只是找到管道中的某个解析器,并修改其设置。SoftlineBreakAsHardlineExtension 就是一个例子,它会定位 LineBreakInlineParser 并修改其中的一个布尔标志。

另一方面,复杂的扩展可能会添加整个新的块和内联类型元组,以及相关的解析器和渲染器,并且需要按照与其他已配置扩展的特定顺序添加到管道中。FootnoteExtensionPipeTableExtension 是更复杂的扩展的例子。

对于不需要考虑顺序的扩展,扩展本身的实现就足够了,可以直接使用管道构建器的泛型 Use<TExtension>() 方法将其添加到管道中。对于需要考虑顺序的扩展,最好在 MarkdownPipelineBuilder 上创建一个扩展方法来执行注册。请参阅以下两节以获取更多信息。

扩展的实现

IMarkdownExtension.cs链接)接口定义了必须实现的两个方法。

第一个方法只接受管道构建器作为参数,当调用管道构建器的 Build() 方法时会被调用。这个方法应设置对解析器或解析器集合的任何修改。这些解析器随后将由主解析算法用于处理源文本。

void Setup(MarkdownPipelineBuilder pipeline);

第二个方法接受管道本身和一个渲染器,用于设置渲染组件,以便将扩展关联的特殊 MarkdownObject 类型转换为输出。这与解析无关,但对渲染是必需的。

void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer);

然后,可以使用 Use<TExtension>() 方法将扩展注册到管道构建器。下面是一个简化示例:

public class MySpecialBlockParser : BlockParser 
{
    // ...
}

public class MyExtension : IMarkdownExtension 
{
    void Setup(MarkdownPipelineBuilder pipeline)
    {
        pipeline.BlockParsers.AddIfNotAlready<MySpecialBlockParser>();
    }

    void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer) { }
}
var builder = new MarkdownPipelineBuilder()
    .Use<MyExtension>();

管道构建器扩展方法

对于需要特定顺序并/或需要执行多个操作以注册到构建器的扩展,建议创建扩展方法。

public static class MyExtensionMethods 
{
    public static MarkdownPipelineBuilder UseMyExtension(this MarkdownPipelineBuilder pipeline)
    {
        // 在这里直接访问或修改 pipeline.Extensions,可以搜索其他扩展,插入前后,移除其他扩展,或修改其设置。

        // ...

        return pipeline;
    }
}

简单扩展示例

一个不添加新解析器的简单扩展示例,而是创建一个新的恐怖强调标签,标记为三重百分号。这个示例基于 CitationExtension.cs

/// <summary> 
/// 适用于形式为 %%%text%%% 的文本的扩展
/// </summary>
public class BlinkExtension : IMarkdownExtension
{
    // 当管道构建器的 `Build()` 方法被调用时,这个设置方法会被运行。由于这是一个简单的、自包含的扩展,我们不会添加任何新内容,而是找到管道中已经存在的解析器,并为其添加一些设置。
    public void Setup(MarkdownPipelineBuilder pipeline)
    {
        // 我们检查管道构建器的内联解析器集合,看看是否能找到类型为 EmphasisInlineParser 的解析器。这是名义上处理粗体和斜体强调的解析器,但我们知道它的文档中指出它可以添加新的字符。
        var parser = pipeline.InlineParsers.FindExact<EmphasisInlineParser>();

        // 如果找到解析器并且它还没有注册 `%` 字符,我们添加一个描述符,表示连续出现三个 `%` 符号。这是针对 EmphasisInlineParser 的特例,只是为了示例。
        if (parser is not null && !parser.HasEmphasisChar('%'))
        {
            parser.EmphasisDescriptors.Add(new EmphasisDescriptor('%', 3, 3, false));
        }
    }

    // 这个方法在管道渲染之前被调用,这与解析是分开的操作。这个实现只是为了示例目的,我们将特定于 EmphasisInlineRenderer 的委托串联起来,使得在源代码中放置带有 `%%%` 注释的 span 位置插入无法容忍的标签到 HTML 输出中。
    public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
    {
        if (renderer is not HtmlRenderer) return;

        var emphasisRenderer = renderer.ObjectRenderers.FindExact<EmphasisInlineRenderer>();
        if (emphasisRenderer is null) return;

        var previousTag = emphasisRenderer.GetTag;
        emphasisRenderer.GetTag = inline =>
            (inline.DelimiterCount == 3 && inline.DelimiterChar == '%' ? "blink" : null)
            ?? previousTag(inline);
    }
}
在本文档中