satgo1546’s ocean

Any sufficiently primitive magic is indistinguishable from technology.

Markdown往事

本文最初发表在Ŝalenzo.RTFD.io

上回书说到静态网站生成器的先驱者Blosxom,这回则来讲讲现代静态网站生成器的标配写作格式——Markdown。

现在众所周知的Markdown的最初形态是一个名为Markdown.pl的Perl脚本,其1.0版由John Gruber于2004年8月28日编写完成。三个月后发布的1.0.1版时至今日仍然可以在Daring Fireball网站上下载到。

原版Markdown有不少细节问题,比如不会自动给裸的地址加链接,必须用<>括住链接地址才会变成链接。下划线在单词中也有效,单词中的下划线经常在标识符中出现,却会转换成斜体。(即使是现在,*也经常闹出问题,比如x=1*2*3会把2转换成斜体,变成“x=123”。)段中的回车会被全部吃掉,变成空格,而唯一的添加换行的办法是在行末加两个及以上的空格,很容易丢失。两个连续的引用块会接在一起,用空行也无法分离。行内链接的标题可以用单引号,但引用型链接不行。原版Markdown的语法页面甚至指出了这个bug:

:Markdown.pl 1.0.1版本中有一已知问题,链接标题无法用单引号表示。

NOTE: There is a known bug in Markdown.pl 1.0.1 which prevents single quotes from being used to delimit link titles.

鉴于这个页面最终更新日定格在了2004年,大抵是永远也修不好了。

不为人知的是,Markdown 1.0.1发布后的几年间,开发工作仍有所继续。2007年5月9日,John Gruber在一个犄角旮旯的邮件列表里偷偷摸摸发布过Markdown 1.0.2b8版,修复了一些问题,此后该软件便再无音讯。虽然这些β版本同样存放在daringfireball.net的服务器上,但从来没有在网页上公之于众。开发Markdown.pl过程中使用的测试套件MarkdownTest同样也没有发布在网站上。

与此同时,因Markdown好用而启用,从而认识到Markdown的一些问题的社区纷纷分叉出自己的一套书写规则。虽非一家人,在自动链接、词中下划线、硬换行上还是达成了共识。然而,各种不同的实现也带来了各自的小问题。

Fletcher T. Penney的MultiMarkdown直至第2版都直接基于Markdown.pl修改而来,最主要的是增加了HTML以外的多格式输出功能,包括XHTML、OPML、LaTeX(然后生成PDF和信封)、RTF。对Markdown语法的增添包括元数据、基于AsciiMath的数学公式、图片自定义属性、脚注,还可选择缩进表示诗歌而非代码块等等。salenzo.neocities.org采用的就是这一版,不过进行了一些修改,比如使公式内容不是经由AsciiMath变换到MathML,而是作为TeX代码送入另一个用于CGI但也能命令行调用的程序mimeTeX。如果客户端启用了JavaScript,则会用显示效果更好的jsMath替换。

但是,越来越多的问题浮现出来:原版脚本到处都是正则表达式,对源文本实行多遍替换,压根没有正经地解析。为了保留一部分文本不替换,先行用文本的MD5替换掉,随后再替换回来,导致当源文本中存在MD5字符串时就炸了。Reddit因为这个安全漏洞遭到过XSS注入。于是MultiMarkdown的第3版改为基于John MacFarlane用C写的peg-markdown来做,此后又重写过数次。

Michel Fortin编写了PHP Markdown并维护至今。起初只是把原版Perl脚本翻译成PHP,毕竟PHP也搭载了PCRE正则表达式引擎。然而还是遭不住原脚本过于自由的写法,最后不得不认真重写整个脚本。重写也方便了PHP Markdown Extra模块添加更多功能。由PHP Markdown Extra提供支持的新语法有表格、定义列表、脚注、```~~~代码块、自定义元素属性等,前两项新功能又被MultiMarkdown所吸收。

对于语法上边界情况的处理,原版Markdown文档的态度很模糊。比如,对于嵌套列表应缩进几个字,以及标题和正文、正文和引文之间的空行是否必需,全篇是只字未提。John Gruber特别喜欢在邮件列表里留下三言两语,对一部分问题表达过意见,而对网站上的语法文档则如文物一般保护着。

缺乏唯一标准,致使实现者使出十八般武艺:Michel Fortin创建了MDTest项目,旨在给出一套完整的测试用例;各个实现不得不通过互相比较来确定正确性,这便是babelmark,第一版也由同一人创建。

Discourse创始人之一的Jeff Atwood多次表达了对Markdown创始人缺乏领导力的不满,同时建议修改原版语法中前面提到的诸多不合理的细节问题,而John Gruber认为不必再改,无动于衷。2014年,Jeff Atwood和Pandoc的作者John MacFarlane等人最终联合建立了CommonMark标准(因为John Gruber反对在标准名里带上“Markdown”)。虽然CommonMark也还有若干边界情况未决,例如对制表符的处理方式,但大部分Markdown语言的混沌总算是得以统一。

可惜的是,CommonMark标准没有允许裸地址自动变成链接,也没有允许随意硬换行,只是除了行末加两个空格,还可以行末加\。“自动链接”功能不自动的原因是识别链接地址存在困难,特别是在英文文本中,简单的正则表达式容易把句末标点一起当成链接地址。虽然可以在Markdown转换完成后再匹配链接文本并添加链接,但地址中的一些下划线又被识别成斜体了(悲)。

2018年4月17日,John MacFarlane在CommonMark论坛发了篇吃桃文Beyond Markdown,幻想了一种没有Markdown各种遗留问题的标记语言。作为最早的基于PEG的Markdown解析器的作者,以上问题全部都没有解决,而是改进了原本难以高效分析的部分。现行的Markdown中,***[x]的含义会被超远处的标记影响,HTML块解析规则非常复杂,列表的缩进和分段规则也由于制表符和缩进代码块的存在而违反直觉,这些问题将被修复。常规文本中极少出现强调半个单词的情况,所以加繁了词中强调的语法,以简化解析的同时帮助内容作者正确使用。另外,增强了输出到多种格式的表达能力,添加了给元素标额外属性的功能。

2022年7月31日,John MacFarlane发布了名为Djot的下一代Markdown的第一个版本0.1.0。得益于设计简单的语法,处理速度得到了大幅提升。除了四年前的想法,还增加了智能引号、荧光笔、上下标、删改标记、emoji,调整了现行的标题、表格、公式、脚注链接的语法,而且词中强调又回来了,原因似乎是不引入巨大的Unicode数据表就无法定义“词中”是什么。