项目

Markdig 语法树抽象表示

如果解析成功,Markdown.Parse(...) 方法会返回源文本的抽象语法树(AST)。

这将是一个 MarkdownDocument 类型的对象,它进一步继承自更通用的块容器,并且是代表 Markdown 语法树中不同语义构造的更大类别的部分。

本文档将讨论 Markdig 中 AST 中的不同元素类型。

AST 的结构

在 Markdig 中,Markdown 语法树中的节点主要有两种类型:BlockInlineBlock 节点可以包含 Inline 节点,但反之则不然。Block 可以包含其他 Block,而 Inline 可以包含其他 Inline

AST 的根是 MarkdownDocument,它自己也是从容器块派生的,同时也包含了文档的行数和在文档中的起始位置信息。AST 的节点有指向父节点和子节点的链接,使得树形结构的双向遍历效率很高。

不同的语义构建由 BlockInline 类型的派生类表示,这两个类型本身都是 abstract 的。这些元素由 BlockParserInlineParser 派生类型生成,因此可以通过实现新的块或内联解析器以及新的块或内联类型,并在管道扩展中注册它们来添加新的构造。有关如何以这种方式扩展 Markdig 的详细信息,请参阅 扩展/解析器 文档。

AST 是通过静态方法 Markdown.Parse(...) 使用 MarkdownPipeline 中的块和内联解析器集合来组装的。有关更多详细信息,请参考 Markdig 解析概述 文档。

快速示例:后代 API

遍历抽象语法树最简单的方法是使用一组名为 Descendants 的扩展方法。存在多种重载,以便从树中的任何节点开始搜索 BlockInline 元素。

Descendants 方法的返回结果是 IEnumerable<MarkdownObject>IEnumerable<T>。它们内部使用 yield return 进行懒惰的边遍历。

深度优先遍历所有元素

MarkdownDocument result = Markdown.Parse(sourceText, pipeline);

// 按深度优先顺序遍历所有 MarkdownObject
foreach (var item in result.Descendants())
{
    Console.WriteLine(item.GetType());

    // 你可以使用模式匹配来隔离特定类型的元素,否则可以使用下一节中演示的过滤机制
    if (item is ListItemBlock listItem)
    {
        // ...
    }
}

过滤特定子类型

可以使用 Descendants<T>() 方法进行筛选,其中 T 需要从 MarkdownObject 派生。

MarkdownDocument result = Markdown.Parse(sourceText, pipeline);

// 遍历所有 ListItem 块
foreach (var item in result.Descendants<ListItemBlock>())
{
    // ...
}

// 遍历所有图像链接
foreach (var item in result.Descendants<LinkInline>().Where(x => x.IsImage))
{
    // ...
}

结合式层次结构

Descendants 方法可用于任何 MarkdownObject,而不仅仅是根节点,因此可以查询复杂的层次结构。

MarkdownDocument result = Markdown.Parse(sourceText, pipeline);

// 查找所有从 ListItem 块派生的 Emphasis 内联
var items = document.Descendants<ListItemBlock>()
    .Select(block => block.Descendants<EmphasisInline>());

// 查找直接父块为 ListItem 的所有 Emphasis 内联
var other = document.Descendants<EmphasisInline>()
    .Where(inline => inline.ParentBlock is ListItemBlock);

块元素

块元素都从 Block 派生,可以分为两种类型:

  1. ContainerBlock,这是一种包含其他块的块(MarkdownDocument 自身就从这个派生)
  2. LeafBlock,这是一个没有子块的块,但可能包含内联

Markdown 中的块元素指的是段落、标题、列表、代码等。大多数块可以包含内联,除了像代码块这样的东西。

块的属性

以下是对 Block 对象的属性进行详细说明。有关完整属性列表,请参阅生成的 API 文档(即将推出)。

块父节点

所有块都有一个指向父节点的引用(Parent),类型为 ContainerBlock?,允许高效地向上遍历抽象语法树。根节点(MarkdownDocument)的父节点为 null

解析器

所有块都有一个解析器引用(Parser),类型为 BlockParser?,它引用创建此块的解析器实例。

开启标志

块有一个布尔标志 IsOpen,在解析过程中设置为 true,然后在解析完成时关闭。块由 BlockParser 对象管理,BlockProcessor 实例在其逐行处理源代码的过程中维护所有当前打开的 Block 对象列表。IsOpen 标志指示 BlockProcessor 在继续处理下一行时应保留该块打开。如果 BlockParser 没有在每行上直接设置 IsOpen 标志,BlockProcessor 将认为该块已完全解析,将不再对其调用解析器。

可中断标志

块可中断或不可中断,由 IsBreakable 标志指定。如果一个块不可中断,它向解析器指示只要不可中断的子块仍然打开,其父容器的关闭条件就不适用。

内置的唯一例子是 FencedCodeBlock,如果作为某种容器块的子块存在,它将阻止容器在 FencedCodeBlock 关闭之前关闭,因为 FencedCodeBlock 中的任何字符都被视为有效的代码,而不是容器的关闭条件。

RemoveAfterProcessInlines

内联元素

Markdown 中的内联元素指的是修饰符(斜体、粗体、下划线等)、链接、URL、内联代码、图像等。

内联元素可以分为两种类型:

  1. Inline,其父节点始终为 ContainerInline
  2. ContainerInline,从 Inline 派生,包含其他内联。ContainerInline 还有一个 ParentBlock 属性,类型为 LeafBlock?

(内联元素或内联类型是否还有其他特别值得记录的内容?)

SourceSpan 结构

如果管道配置为 .UsePreciseSourceLocation(),那么 AST 中的所有元素都将包含它们在原始源位置的引用。这是通过 SourceSpan 类型完成的,这是一个 Markdig 自定义的 struct,提供了开始和结束位置。

所有从 MarkdownObject 派生的对象都包含 Span 属性,其类型为 SourceSpan

在本文档中