Markdown 结构分解

关于 Markdown

Markdown 在 《FirstWeb》中也有介绍过,相信很多读者知道或者已经在使用 Markdown 了。包括《全端》系列下的课程也都是以 Markdown 格式进行书写,并最终汇总为 PDF 。
Markdown 是 John Gruber 发明的,在 2004 发布了 1.0.1 版本 (Perl 写的),https://daringfireball.net/projects/markdown/ 可以看到更多的信息。
关于 Markdown,更多的内容,此处就不多做介绍了。

结构分解

Markdown 是一种书写的语法,尔后要渲染为 HTML,再配以 CSS,以形成最终的视觉效果。
Markdown 解析器 有很多,而且有不同的编程语言实现,解析器的主要作用是将 Markdown 文本转化为 HTML。但解析分解 是不一样的,解析的过程必然会进行分解,但由于解析器的最终目的是获得 HTML 结果,因此大概率是不会去存储中间结构分解的结果。

结构分解 重要吗?如果只是对单篇文章解析,那么它不重要。如果是对文章里的一个段落,那么它就非常重要。
有了 结构 的存在,你新增了一个词、段落,就能知道当前 Markdown 哪些结构发生了变化,并且只要重新渲染有变动的局部 就可以了。局部渲染 会大幅提高性能,以前我在 MarkEditor 中采用的方式是书写内容变更,然后重新解析 Markdown,再更新 WebView,它的性能问题会随着文本的内容增多而遇到瓶颈,局部渲染就可完全避免这个问题,文本再多,性能的瓶颈起码不会出现在 Markdown 的解析上面,而且 WebView 也可以高效地局部更新 Dom 元素。
使用纯 Python 完成的 Markdown 解析器,是不足以承担『工业』级别的使用场景 (只要产品提供给用户用,都算得上工业级别了),如果解析器的核心是 C 写的,那么性能可以十几倍、近百倍的倍增。即使这样的性能改善,也挡不住叠加后产生的局限性,比如 MarkEditor 的场景中:如果用户一更新内容,马上就渲染 Markdown,C 的核心模块性能虽强但 CPU 该高的还是高,会直接影响主体 GUI 程序的响应速度,为了避免卡顿一般会有个定期的delay (1 秒、2 秒都好) 再异步再处理;还有就是 WebView 的渲染性能,它的渲染速度肯定比 Markdown 解析要慢上很多。

作业

问题来了: 我们如何使用 Python 去分解一个 Markdown 的文本结构呢?
第一步应该去 Github 上寻找,你会发现很多用 Python 实现的 Markdown 解析器。然后,进入这些 repo 看源代码,或者选择一个,下载到本地,使用 PyCharm 查看,会便捷很多。

现在,尝试完成这个作业:

  1. 完成一个 Markdown 的结构分解的逻辑;
  2. 分解的是 block 级别的,像加粗这种属于 inline 性质并非 block,不用去管;
  3. 最后的结果应该是一个 list,每个元素的格式是 ([start_position, end_position], 'blockquote'])([start_position, block_length], 'blockquote']),block 的类型名称自己确定,比如 blockquotelistcode_block .etc。

一些建议:

  1. 你会用到 class (类),不然会太辛苦。
  2. 你最终也会提供一个 function,供外部快速调用,不然太不友好。
  3. 不要低头瞎写,可以多参考别人现成的代码,但要留意下 repo 对应的 License (开源协议)。

坑我?

完成上文提到的任务,或许几个小时,或许一天,时间长一些的,或许两天、三天,甚至更长,当然不要超过一个礼拜,这就过分了。你也会发现,自己需要处于重度思考,才能解决这个问题,甚至,思考的时间要远远超过写代码的时间。
而且,你在 Github 上找到的,用作参考用的 Markdown 解析器,基本都是基于 re (正则表达式) 来完成。由于涉及到的规则比较多,一条正则表达式来区分某个 Markdown 的 block 会显得非常复杂;所以这个过程中,你还会不断去翻阅、查找正则表达式相关的内容。这个正则表达式,基本规则看起来不复杂,但叠加起来之后,这个难度简直不堪重负。

我们现在回顾一下,《给小朋友的加减公式》中有一个获得加法等式的函数,叫 get_additions,以及另外一个 get_additions_2,两个函数的作用是一样的,但是实现的思路是完全不同的。get_additions 的逻辑更倾向于自然逻辑,而 get_additions_2 则不然;说到这里,是否有些启发


如果没有启发,就暂做停留,再想想?


简单的规则,叠加的时候是挤在一起,而不是有机的、结构化的,那么它就会变成一种不恰当的复杂。


我们继续,之所以说 get_additions自然逻辑,是因为如果让你手工排加法算式的时候,也是跟 get_additions 一样的思路。
你现在找一个 Markdown 的文档,里面尽量覆盖多数的 Markdown 语法,随意定睛于某处,然后判断出它当前所处的位置应该隶属于哪个 block,以及这个 block 是什么类型的?如果是分析全篇的结果,又会怎么处理呢?
考虑下自己的思维模式,再看下大量借助正则表达式实现的 作业,两者的思维模式是接近的吗?或者换个角度看,有没有从自己的人类的思维模式出发去完成代码?是不是先入为主,接受了 Github 上找到各种参考资料的实现方式呢?

明知要走一段歪路,没有从中阻止,反而作为作业怂恿之,简直坑人
并不算『歪路』,起码对正则表达式的理解会更进一步,能达到真正可用的状态,以后需要使用正则的时候,想必花些时间,总是能解决问题的。
即使除去『学习并使用正则表达式』的好处之外,之前的 作业 也算不得 『歪路』。
因为,这是两条道路的分叉口。一条是更倾向于人类的自然思维,一条是更倾向于机器的思维。两者并不互斥,但一定程度上擅长其中一条,另外一条路就会疏离。


所以,我们换一个更自然的逻辑去分解 Markdown 结构,简单来说,就是: 逐行分解

仍然有一些建议:

  1. 你会用到 class (类),不然会太辛苦。
  2. 你最终也会提供一个 function,供外部快速调用,不然太不友好。
  3. 思考的时间肯定会远多于写代码的时间。
  4. 逐行遍历的过程中,某一行是一个 block 的结束,那么继续下一行解析;但也有可能它是一个 block 的开始,而且只有它的存在,才能判定 上一个 block 的结束,那么可能需要改变当下处于 默认继承状态 的值 (表示进入新的 block type 了),并且需要重新解析当前行,而不是继续下一行
  5. 第 4 条建议可以不遵循,因为,它取决于你的实现逻辑。
  6. 不要担心性能问题,不要想当然 逐行 是低效的,有时,它的性能反倒更高。
  7. 现在,似乎没有可以参考的源码了,自己动手吧!

你的想法?

然后,又是一天,甚至两天、更长的时间,你完成了上文提及新的实现方式。Markdown 已经按照 block 进行结构分解了,那么 block 逐个再转为 HTML,它的难度就很低了。换句话说,你可以完成一个全新的 Markdown 解析器

如果你从头到尾经历了两份『作业』,那么现在有些想法我们可以讨论了:1,Markdown 是一个糟糕的语法;2,否定 Markdown 是一个荒唐的想法。两句话,特别是第一句话,是非常容易引来论战的,但是没有解释的必要,你肯定也形成自己的一些想法 (雏形),身体力行,是故为体悟
没有所谓的权威,我们可以跳出一些条条框框,虽然会有些阻力,但是不去管就好。比如你要跟别人讨论 『 Markdown 是不是糟糕的语法』,是没有什么价值的,不讨论就好了。
因为没有所谓的权威,所以,我说的这段话,也可能是错的。

写在最后

本篇内容跟 《给小朋友的加减公式》虽然是相辅相成的,但是难度系数完全不同,处在不同的维度上。
本篇很难,看不到一行示例代码,要依靠自己全部完成。

《技》这部分的内容并不是像课程前面部分一般,由浅到深,从头到尾存在过渡性的关联。
《技》除了有些是 How to 式一步一步介绍之外,更多的是期望带来启发,即使这个过程需要自己独立前行,耗上一个礼拜甚至更长的时间。
因为,只有这样,回头望时,才会真的会感觉到,我会了
这是捷径,它是开放式的,却也是辛苦的一条路。