Markdig 扩展和解析器
Markdig 的实现方式被设计得非常可扩展,即使是最基本的行为也可以改变和扩展。Markdig 扩展的基础机制是 IMarkdownExtension
接口,任何实现了该接口的类都可以注册到管道构建器中,从而直接修改最终进入处理流程的 BlockParser
和 InlineParser
对象集合。
本文档将讨论 IMarkdownExtension
接口、BlockParser
抽象基类和 InlineParser
抽象基类,它们共同构成了扩展 Markdig 解析器的基石。
创建扩展
扩展可以从非常简单到非常复杂不等。
例如,一个简单的扩展可能只是找到管道中的某个解析器,并修改其设置。SoftlineBreakAsHardlineExtension
就是一个例子,它会定位 LineBreakInlineParser
并修改其中的一个布尔标志。
另一方面,复杂的扩展可能会添加整个新的块和内联类型元组,以及相关的解析器和渲染器,并且需要按照与其他已配置扩展的特定顺序添加到管道中。FootnoteExtension
和 PipeTableExtension
是更复杂的扩展的例子。
对于不需要考虑顺序的扩展,扩展本身的实现就足够了,可以直接使用管道构建器的泛型 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);
}
}