Markdown解析器-Showdown

由于工作中需要将 markdown 格式的文件转换成 html,因此抽空研究了下业界的工具Showdown,Showdown 是一个可以将标准 markdown 规范解析生成基础 html 的工具,使用起来很简单,通过命令:showdown makehtml -i <input> -o <output>即可完成转化,详细的介绍请参考官方文档 Showdown,在此就不过多赘述了。

Extensions 与 Listeners

通过研究发现,当需要解析一些”自定义”的 markdown 规范时,Showdown 提供了清晰良好的代码格式,方便我们扩展,包括extensions, listener的方式,可以进行注册,监听,来看 Showdown 的入口文件,里面有这样三行代码:

1
2
3
4
5
6
7
8
9
10
//src/cli/makehtml.cmd.js
function run() {

var converter = new showdown.Converter(argv);
...
input = fs.readFileSync(argv.i, enc);
...
output = converter.makeHtml(input);

}

可以分析出,converter通过接受参数,来配置各种extensionslistener,然后直接从源文件中读取,经过 makeHtml 一转换就变成 html 了

官方也提供了几个现有的 extensions, 如 table-extension prettify-extension 等,这些现有的extensions都是对 markdown 的元素进行了自定义的解析和渲染,如果是基于标准的 markdown 语法,使用这种方式进行自定义渲染就非常方便了

关于 Listeners 可以参考下面的工作原理章节

工作原理

关于工作原理,可以用一句话来概括:Showdown使用正则表达式解析 markdown 语法,然后直接渲染成 html,注意这里面有两个关键词:

  • 正则表达式解析

    来看一个 Parser 的源码片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 斜体和黑体的解析器:italicsAndBold
showdown.subParser('italicsAndBold', function (text, options, globals) {
'use strict';

text = globals.converter._dispatch('italicsAndBold.before', text, options, globals);

if (options.literalMidWordUnderscores) {
//underscores
// Since we are consuming a \s character, we need to add it
text = text.replace(/(^|\s|>|\b)__(?=\S)([\s\S]+?)__(?=\b|<|\s|$)/gm, '$1<strong>$2</strong>');
text = text.replace(/(^|\s|>|\b)_(?=\S)([\s\S]+?)_(?=\b|<|\s|$)/gm, '$1<em>$2</em>');
//asterisks
text = text.replace(/(\*\*)(?=\S)([^\r]*?\S[*]*)\1/g, '<strong>$2</strong>');
text = text.replace(/(\*)(?=\S)([^\r]*?\S)\1/g, '<em>$2</em>');

} else {
// <strong> must go first:
text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g, '<strong>$2</strong>');
text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g, '<em>$2</em>');
}

text = globals.converter._dispatch('italicsAndBold.after', text, options, globals);
return text;
});

每个 Parser 的开始和结束,都抛出了相应的事件供监听,这就是 listener 扩展的机制,每次处理的结果都是返回 text 文本,可以看到在解析器中,对输入的文本进行正则表达式的匹配并直接替换成了相应的 html 元素

  • 直接渲染

    所谓直接渲染,就是上面代码中的replace,这样的处理为后续的需求埋下隐患:

    1. 如果某个 markdown 片段需要作为整体的渲染,则无法满足

    2. 如果对渲染之后的 html 进行样式更改,加入css,则无法满足

      可以看出,replace 替换的仅仅是 html 基本元素样式,且没有 id,class 等信息,因此极大程度上约束了生成的 html 样式,并不利于满足一些实际需求,然而 Showdown 的代码架构,将各个 markdown 元素分别定义了单独的解析器,这就为我们提供了一个思路,可以根据自定义的 markdown 的格式通过自定义的 parser 解析出来就好了,例如:

      1
      2
      3
      4
      5
      6
      ### start 修订记录 ###
      1.0
      date: 11.3
      2.0
      date: 12.9
      ### end 修订记录 ###

解析器可以以 start 和 end 作为判断,整体将修订记录渲染成想要的格式,因此,对 Showdown 工程有以下重构想法:

解析和渲染解耦,不通过 replace 直接完成, 而是通过 taffydb 将解析出来的数据进行存储(参考jsdoc),这样做的好处是:规则制定者可以自定义 markdown 语法片段并通过解析器解析出来,然后存放到数据库中,不需要关心接下来如何渲染,前端工程师可以直接从数据库中取出相应的片段信息,配合css,进行定制化渲染。借助于中间数据库的解耦,使得最后生成出来的html可以加入css样式,从而满足一切定制化需求,即 解析 - 数据 -渲染

总结

以上是在结合工作需求的基础上研究 Showdown 工具的一些想法,还是软件工程里的一句老话:从需求出发的改动才附有意义

如需转载,请注明出处