md4c:高性能 CommonMark 解析器,支持多扩展与跨平台嵌入

C Markdown parser. Fast. SAX-like interface. Compliant to CommonMark specification.

分支5Tags27
文件最后提交记录最后更新时间
2 个月前
1 个月前
3 天前
3 天前
3 天前
1 个月前
27 天前
2 个月前
1 个月前
1 个月前

MD4C 自述文件

MD4C 意为“C 语言版 Markdown”,这也正是本项目的核心所在。

什么是 Markdown

简而言之,Markdown 是编写此 README.md 文件所使用的标记语言。

若您对此不熟悉,以下资源可提供更多解释:

什么是 MD4C

MD4C 是一个用 C 语言实现的 Markdown 解析器,具有以下特性:

  • 合规性:MD4C 通常致力于符合最新版本的CommonMark 规范。目前,我们完全兼容 CommonMark 0.31。

  • 扩展:MD4C 支持一些普遍需求且被广泛接受的扩展。详见下文。

  • 性能:MD4C 速度极快

  • 简洁性:MD4C 解析器仅通过一个源文件和一个头文件实现。除标准 C 库外,无其他依赖。

  • 可嵌入性:MD4C 解析器易于在其他项目中复用,其 API 非常简单直观:实际上仅有一个函数 md_parse()

  • 推送模型:MD4C 解析整个文档,并调用应用程序提供的几个回调函数,以告知每个块的开始/结束、每个跨度的开始/结束以及任何文本内容。

  • 可移植性:MD4C 可在 Windows 和 POSIX 兼容的操作系统上构建和运行。(要使其在大多数其他平台上运行也应很简单,至少在平台提供 C 标准库,包括堆内存管理的情况下是如此。)

  • 编码:MD4C 默认期望输入文档采用 UTF-8 编码。但也可通过编译使其仅识别 ASCII 控制字符(即禁用所有 Unicode 特定代码),或者(在 Windows 上)期望 UTF-16 编码(即 Windows 上通常简称为“Unicode”的编码)。详见下文。

  • 宽松许可:MD4C 基于 MIT 许可证 发布。

使用 MD4C

解析 Markdown

如果您只需要解析 Markdown 文档,只需包含 md4c.h 并链接 MD4C 库(使用 -lmd4c);或者,由于解析器仅在单个 C 源文件中实现,您也可以直接将 md4c.[hc] 添加到您的代码库中。

提供的主要函数是 md_parse()。它接收 Markdown 语法的文本以及一个指向结构体的指针,该结构体提供了多个回调函数的指针。

md_parse() 处理输入时,它会调用这些回调函数(在进入或离开任何 Markdown 块级元素或内联元素时,以及在输出文档的任何文本内容时),使应用程序能够将其转换为另一种格式或将其渲染到屏幕上。

转换为 HTML

如果您需要将 Markdown 转换为 HTML,请包含 md4c-html.h 并链接 MD4C-HTML 库(使用 -lmd4c-html);或者,也可以将源文件 md4c.[hc]md4c-html.[hc]entity.[hc] 添加到您的代码库中。

要转换 Markdown 输入,请调用 md_html() 函数。它接收 Markdown 输入并调用提供的回调函数。该回调函数会接收 HTML 输出的块。典型的回调实现只是将这些块附加到缓冲区中或写入文件。

Markdown 扩展

默认行为是仅识别 CommonMark 规范 中定义的 Markdown 语法。

但是,通过适当的标志,可以调整行为以启用一些扩展:

  • 使用标志 MD_FLAG_COLLAPSEWHITESPACE,可将非平凡的空白字符折叠为单个空格。

  • 使用标志 MD_FLAG_TABLES,支持 GitHub 风格的表格。

  • 使用标志 MD_FLAG_TASKLISTS,支持 GitHub 风格的任务列表。

  • 使用标志 MD_FLAG_FOOTNOTES,支持脚注引用和定义(例如 [^note][^note]: Footnote text)。被引用的定义会按照首次引用的顺序在文档末尾输出。

  • 使用标志 MD_FLAG_STRIKETHROUGH,启用删除线内联元素(用波浪线标记的文本,例如 ~foo bar~)。

  • 使用标志 MD_FLAG_SPOILERS,启用剧透内联元素(用双竖线标记的文本,例如 ||hidden text||)。(请注意,HTML 渲染器会将它们输出为自定义标签 <x-spoiler>。)

  • 使用标志 MD_FLAG_SUPERSCRIPTS,启用上标内联元素(用脱字符标记的文本,例如 x^2^)。HTML 渲染器输出 <sup>

  • 使用标志 MD_FLAG_SUBSCRIPTS,启用下标内联元素(用单波浪线标记的文本,例如 H~2~O)。HTML 渲染器输出 <sub>。当与 MD_FLAG_STRIKETHROUGH 一起使用时,单波浪线渲染为下标,双波浪线 ~~text~~ 渲染为删除线。

  • 使用标志 MD_FLAG_HIGHLIGHT,启用高亮内联元素(用双等号标记的文本,例如 ==important==)。HTML 渲染器输出 <mark>

  • 使用标志 MD_FLAG_PERMISSIVEURLAUTOLINKS,支持宽松的 URL 自动链接(不包含在 <> 中)。

  • 使用标志 MD_FLAG_PERMISSIVEEMAILAUTOLINKS,支持宽松的电子邮件自动链接(不包含在 <> 中)。

  • 使用标志 MD_FLAG_PERMISSIVEWWWAUTOLINKS,支持未指定任何方案的宽松 WWW 自动链接(例如 www.example.com)。MD4C 会假定使用 http: 方案。

  • 使用标志 MD_FLAG_LATEXMATHSPANS,支持 LaTeX 数学内联元素($...$)和 LaTeX 显示数学块($$...$$)。(不过请注意,HTML 渲染器会将它们原样输出到自定义标签 <x-equation> 中。)

  • 使用标志 MD_FLAG_WIKILINKS,支持维基风格链接([[link label]][[target article|link label]])。(请注意,HTML 渲染器会将它们输出为自定义标签 <x-wikilink>。)

  • 使用标志 MD_FLAG_UNDERLINE,下划线(_)表示下划线,而非普通强调或 strong 强调。

  • 使用标志 MD_FLAG_ADMONITIONS,识别 GitHub 风格的提示框。

CommonMark 的少数特性(有些人认为是缺陷)可以通过以下标志禁用:

  • 使用标志 MD_FLAG_NOHTMLSPANSMD_FLAG_NOHTMLBLOCKS,分别禁用原始内联 HTML 或原始 HTML 块。

  • 使用标志 MD_FLAG_NOINDENTEDCODEBLOCKS,禁用缩进代码块。

输入/输出编码

CommonMark 规范声明,任何 Unicode 码点序列都是有效的 CommonMark 文档。

但仔细研究后会发现,在解析 Markdown 文档时,Unicode 仅在少数非常特定的情况下发挥作用:

  1. 在处理强调和强强调时检测单词边界,需要对 Unicode 字符进行一些分类(判断其是空白字符还是标点符号)。

  2. 为了(不区分大小写地)将链接引用标签与相应的链接引用定义进行匹配,会使用 Unicode 大小写折叠。

  3. 用于将 HTML 实体(例如 &amp;)和数字字符引用(例如 &#35;&#xcab;)转换为其 Unicode 等效字符。

    但请注意,MD4C 将此转换工作留给了渲染器/应用程序;因为渲染器理应确切知晓输出编码,以及是否确实需要执行此类转换。(例如,当渲染器输出 HTML 时,它可以保持实体不被转换,而将这项工作推迟到 Web 浏览器进行。)

MD4C 依托 CommonMark 的这一特性,其实现很大程度上与编码无关。MD4C 的大部分代码仅假设您选择的编码与 ASCII 兼容。也就是说,值低于 128 的码点与 ASCII 具有相同的数值。

MD4C 无法理解的任何输入都将被简单地视为文档文本的一部分,并原封不动地发送到渲染器的回调函数。

MD4C 必须理解 Unicode 的两种情况(单词边界检测和链接引用匹配)由以下预处理器宏(在构建 MD4C 时指定)按规定处理:

  • 如果定义了预处理器宏 MD4C_USE_UTF8,MD4C 会在单词边界检测以及链接标签的不区分大小写匹配中采用 UTF-8 编码。

    如果未显式使用这些宏中的任何一个,这将是默认行为。

  • 在 Windows 系统上,如果定义了预处理器宏 MD4C_USE_UTF16,MD4C 会使用 WCHAR 而非 char,并在上述情况下采用 UTF-16 编码。(UTF-16 通常被 Windows 开发人员简称为“Unicode”,也是 Win32API 通常使用的编码。)

    请注意,由于此宏也会影响 md4c.h 中的类型,因此在构建 MD4C 以及包含 md4c.h 时都必须定义该宏。

    另请注意,此特性仅在解析器(md4c.[hc])中受支持。HTML 渲染器不支持此特性,您需要编写自己的自定义渲染器才能使用此功能。

  • 如果定义了预处理器宏 MD4C_USE_ASCII,MD4C 仅假设输入为 ASCII 编码。

    这实际上意味着,非 ASCII 的空白字符或标点符号将不会被识别为相应的类别,并且链接引用匹配仅对 ASCII 字母([a-zA-Z])不区分大小写。

文档说明

解析器的 API 在 md4c.h 文件的注释中有相当完善的说明。 同样,markdown 转 html 的 API 在其头文件 md4c-html.h 中也有描述。

此外,还有一个项目维基,提供了更全面的文档。不过需要注意的是,该维基内容尚不完善,部分细节可能已有些过时。

常见问题

问:MD4C 与其他 Markdown 解析器相比有何优势?

答: 其他一些实现将 Markdown 解析器和 HTML 生成器合并为一个纠缠的代码,隐藏在仅允许从 Markdown 转换为 HTML 的接口后面。如果您想以任何其他方式处理输入,它们往往无法使用。

其次,大多数解析器(如果不是全部的话;至少在 C/C++ 语言范围内)都是完全类似 DOM 的解析器:它们构建整个 Markdown 文档的抽象语法树(AST)表示。这需要时间,并且会导致更大的内存占用。

只要您需要 AST,构建它是完全没问题的。如果不需要,使用 MD4C 很可能会在速度上快得多,内存消耗也少得多。

最后但同样重要的是,一些 Markdown 解析器的实现方式比较 naive。当输入精心设计的模式时,它们可能会表现出二次(甚至更糟)的解析时间。MD4C 只需几分之一秒就能解析的内容,用它们可能需要漫长的几分钟甚至几小时。因此,当使用这种 naive 解析器处理来自不受信任来源的输入时,拒绝服务攻击的可能性就成为了一个真正的危险。

我们投入了大量精力,以确保无论 MD4C 解析器处理何种复杂输入,都能提供线性或接近线性的解析时间。(如果您遇到导致亚线性解析时间的输入模式,请务必将其作为错误报告。)

问:MD4C 是否执行任何输入验证?

答: 不执行。而且我们对此感到自豪。😃

CommonMark 规范指出,任何 Unicode 字符序列都是有效的 Markdown 文档。(实际上,这或多或少总是意味着 UTF-8 编码。)

换句话说,根据规范,无论某些 Markdown 语法结构是否以某种方式损坏,都无关紧要。如果它损坏了,就不会被识别,解析器应该将其视为纯文本。

MD4C 更进一步:它将任何字节序列都视为有效输入,完全遵循 GIGO(输入垃圾,输出垃圾)原则。也就是说,任何格式不正确的 UTF-8 字节序列都会作为文本的一部分传播到相应的回调函数。

如果您需要验证输入是否为格式良好的 UTF-8 文档等,就必须自己进行验证。最简单的方法是在将整个文档传递给 MD4C 解析器之前对其进行验证。

问:MD4C 不支持以零结尾的字符串。为什么?

答: 有两个原因:正确性和性能。

Markdown 文档可以合法地包含 U+0000 字符(在 UTF-8 中编码为零字节),为了符合 CommonMark 规范,我们需要能够处理此类输入。

至于性能,如果在将每个文本块传递给应用程序回调之前添加零终止符,我们就必须在内部将每个这样的字符串复制到临时缓冲区中。这意味着我们实际上会一点一点地对整个文档内容进行额外的复制。

为避免这种性能下降,只要有可能,解析器就会直接将指向输入文档中正确位置的指针以及回调函数预期处理的文本长度传递给回调函数。

许可协议

MD4C 采用 MIT 许可协议,详情参见文件 LICENSE.md

相关项目链接

其他语言的移植和绑定:

使用 MD4C 的软件:

  • imgui_mdDear ImGui 的 Markdown 渲染器。

  • LibreOffice: 一款开源办公套件。

  • lnav: 一款终端日志文件查看器,也能渲染 Markdown。

  • MarkDown Monolith Assembler: 一款用于构建基于浏览器的书籍的命令行工具。

  • Mdview MTX: GTK Markdown 查看器和命令行转换器。

  • QOwnNotes: 一款支持 Markdown 的纯文本文件记事本和待办事项管理器,并集成了 ownCloud / Nextcloud。

  • Qt: 跨平台 C++ GUI 框架。

  • react-native-enriched-markdown: 一款由 MD4C 驱动的 React Native Markdown 渲染器。

  • Textosaurus: 基于 Qt 和 Scintilla 的跨平台文本编辑器。

  • 8th: 跨平台连接式编程语言。

项目介绍

C语言编写的Markdown解析器。快速高效。采用类似SAX的接口。符合CommonMark规范。【此简介由AI生成】

定制我的领域
181.39 K205访问 GitHub