解析markdown_markdown-it 原理浅析

前言

最近使用 markdown-it 比较多,也开发了一些插件,在这个过程中对源码进行了研读,最终写了这篇文章。需要了解细节的读者可以自行阅读文档。

此文分为两个部分:原理剖析和原理应用(编写插件)。

markdown-it 原理

输入一串 markdown 代码,最后得到一串 html 代码,整体流程如下:

de8218eb8b9279c13188f13095cf7b6b.png

我们以一个简单的例子来解释整个流程:​# 我是一个例子​ -> ​<h1>我是一个例子</h1>​

首先,它会被解析器拿到,经过各个解析规则处理后得到一个 token 流,接着这个 token 流被渲染器拿到,经过各个渲染规则处理后逐步拼接成一个 html 字符串。

解析器

markdown-it 内置了七个核心规则,在上图我对解析规则使用了虚线,因为它们是可以被启用/禁用的。我们这篇文章只来聊聊最核心的两个规则:block 和 inline。

规范指出:

我们可以将一篇 Markdown 文档视为一系列块,块是一种结构化的元素,如段落,块引用,列表,标题,规则和代码块。一些块(如块引号和列表项)可以包含其他块; 其他(如标题和段落)包含内联内容,如文本,链接,强调文本,图像,行内代码等。
块结构的解析优先级始终高于内联结构。这意味着解析可以分两步进行:
1.识别 markdown 文档的块结构;
2.将段落,标题和其他块结构中的文本行,作为内联结构解析。
注意,第一步需要按顺序处理行,但第二步可以并行化,因为一个块元素的内联解析不会影响任何其他块的内联解析。
块分为两种类型:容器块和叶子块,容器块可以包含其他块,但叶子块不能包含其他块。

具体解析时,会围绕着 line 和 character 两个维度来解析。

对于每一行来说,解释的结果有以下三种:

  1. 用来关闭一个或多个块结构。
  2. 用来创建一个或多个新块结构,作为最后打开的块结构的子节点。
  3. 可以将文本添加到树上剩余的最后(最深的)打开的块结构上。

对于我们这个例子,会先创建一个 heading 块,然后将文本内容添加到这个块上。下一行没有内容,于是块关闭。

字符包括非空白字符和空格(​U+0020​),制表符 (​U+0009​),换行符(​U+000A​),行列表(​U+000B​),换页(​U+000C​)或回车(​U+000D​)这些空白字符。这里我们不做展开。

这期间会接触到的规则有 block、inline、heading、text。

  1. block 规则,会用来解析 ​# 我是一个例子​
  • 先进入 tokenize 函数,内含十一个 block 规则。
  • heading 规则
  • 得到 heading_open 、inline、 heading_close 三个 token
  1. inline 规则,会用来解析 ​我是一个例子​
  • 先进入 parse 函数,内含四个 inline 规则
  • text 规则
  • 得到 text 的 token

解析完毕,我们得到了 3 + 1 个 token:

fe39066d8df7c6eba055031953b81d94.png

token 流

这里我们得到的结果不是一颗 AST 树,而是一个数组,markdown-it 称之为 token 流。为什么呢?

官方解释是:

  • Tokens 是一个简单的数组。(AST 是一个对象)
  • 打开的标签和关闭的标签可以隔离。
  • 将“内联容器(inline container)”作为一种特殊的 block token 对象。它有嵌套的 tokens,如粗体,斜体,文本等等。

这样做有什么好处呢?这样就可以并行处理 block 和 inline 类型的 token 了。

生成 token 流后,它们就被会传递给 renderer。

渲染器

它会遍历所有 token,将每个 token 传递给与 token 的 type 属性同名的规则。markdown-it 内置了九种规则:围栏、行内代码、代码块、html 块、行内 html、图片、硬换行、软换行、文本。

type 属性不在内置规则的 token 将会被被传入 renderToken 中当一个普通 token 处理,这里不作展开。

回到我们的例子中来:

heading_open 会被渲染成 ​<h1>​

inline 中的 text 会被渲染成 ​我是一个例子​

heading_close 会被渲染成 ​</h1>​

markdown-it 插件

一些 markdown-it 插件就利用了上述的原理。

markdown-it-container

这个插件可以让你支持内容块:比如 vuepress 的内容块:

0630db26bfa6d3645074d6ed94839823.png


这是如何实现的呢?我们可以根据之前的介绍推测一个内容块的 token 流:

第一行和第三行有 block 型的 token,一个代表 open,一个代表 close。第二行是 inline 型的 token,其中的内容是 inline 型的。

由于内容块中是 inline 类型,所以围栏、行内代码、代码块、html 块、行内 html、图片、硬换行、软换行、文本都是支持的。

实际上,我们会逐行扫描,找到匹配 ​::: tip​ 这样的内容块语法,将它作为一个块结构开始进行解析,直到有 ​:::​ 的行结束。其中的每一行,都将解析为 paragraph_open、inline、paragraph_close。

解析后的 token 流最后分别渲染 ​<div>​ 、若干 p 标签、 ​</div>​。

markdown-it-anchor

这个插件可以对标题进行锚点抽取,以便阅读文档时能快速定位位置。

这里也可以推测一下,是不是往原本是 heading_open type 的 token 之前插入了一个 token 呢?这个 token 渲染出来就是锚点。

实际上,的确是插入了 token,但不止一个,因为锚点是可点击的,所以实际上是一个 a 链接,也就是 link_open、inline、link_close 三个 token。而且也不是插入在 heading_open 之前,而是 heading_open 和 heading_close 之间的 inline 子元素里了,因为 ​#​ 是和 ​Markdown 语法​平级的。

注意事项:
1.因为标题可能是@#$等特殊字符,会造成 url 哈希无效,所以需要对锚点的哈希值转义。
2.可能会出现重名的标题,所以需要对哈希进行标记

给链接添加属性

官方有一个写插件的例子:添加 target="_blank" 属性到所有链接。

有两种方式:

  1. 修改渲染器规则
// 如果覆盖,或者是对默认渲染器的代理,则记住老的渲染器。
var defaultRender = md.renderer.rules.link_open || function(tokens, idx, options, env, self) {return self.renderToken(tokens, idx, options);
};md.renderer.rules.link_open = function (tokens, idx, options, env, self) {// 如果你确认其他的插件不能添加 `target` - 放弃以下检查:var aIndex = tokens[idx].attrIndex('target');if (aIndex < 0) {tokens[idx].attrPush(['target', '_blank']); // 添加新属性} else {tokens[idx].attrs[aIndex][1] = '_blank';    // 替换已经存在的属性值}// 传递 token 到默认的渲染器。return defaultRender(tokens, idx, options, env, self);
};
  1. 修改 token
var iterator = require('markdown-it-for-inline');var md = require('markdown-it')().use(iterator, 'url_new_win', 'link_open', function (tokens, idx) {var aIndex = tokens[idx].attrIndex('target');if (aIndex < 0) {tokens[idx].attrPush(['target', '_blank']);} else {tokens[idx].attrs[aIndex][1] = '_blank';}});

结语

markdown-it 作为一款经典的 js 解析 markdown 的库,其中思想和设计都可以细细揣摩,回味久久。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/508187.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第一次失效_特斯拉螺栓腐蚀失效分析_搜狐汽车

本文车型速览除了文章作者的主观观点外&#xff0c;我们正尝试基于全网可查的客观数据&#xff0c;为您提供中立、客观的参考依据&#xff1a;本文部分车型速览&#xff1a;展开微信扫码&#xff0c;直接一次看完附近所有城市低价(附近城市均有经销商可售卖至本市) 提交成功&am…

c#计算长方形的周长和面积公式_Java面向对象练习题之计算圆的面积和周长

创建一个圆Circle类。为该类提供一个变量r表示半径&#xff0c;一个常量PI表示圆周率&#xff1b;同时为该类提供两个方法&#xff1a;方法一用于求圆的面积&#xff0c;方法二用于求圆的周长&#xff1b;为该类提供一个无参的构造方法&#xff0c;用于初始化r的值为4。在main方…

简单计算机面试题库及答案_试讲可以看教案吗?必看的面试考前问题解答

“皮卡皮卡&#xff01;”教资面试倒计时4天啦快使尽全力发出你的十万伏特胜利就在前方部分小伙伴们还对教资面试存在种种疑惑下面牛奶为大家一一作出解答教师资格面试题目是如何确定的?答&#xff1a;面试题目分为试讲题目、结构化题目和答辩题目。▲试讲题目是考生在备课前&…

python输出读取的空格数目_Python 3基础教程: 输入和输出具体代码实例

在前面文章中&#xff0c;我们其实已经接触了 Python 的输入输出的功能。本章节我们将具体介绍 Python 的输入输出。输出格式美化Python两种输出值的方式: 表达式语句和 print() 函数。(第三种方式是使用文件对象的 write() 方法; 标准输出文件可以用 sys.stdout 引用。)如果你…

点击button后改变文字_27. 教你零基础搭建小程序:小程序的常见组件—button

筒子们&#xff0c;这一章接着讲Button 标签。开放能力的属性button标签的开放能力是指open-type 属性。其中&#xff0c;这个属性的合法值包括如下&#xff1a;这一部分呢&#xff0c;需要分为两个方式来演示。一是可在模拟器中直接看效果&#xff1b; 二是要通过真机调试看效…

语言把数据写入csv文件_把JSON/CSV文件打造成MySQL数据库

生活中&#xff0c;你我一定都看到过这种「xx元爆改出租屋」&#xff0c;「爆改小汽车」之类的文章&#xff0c;做为IT人&#xff0c;折腾的劲头一点也不差。软件开发过程中&#xff0c;你是否有时候&#xff0c;会拿着业务提供的一个个CSV或者JSON的数据文件&#xff0c;写个解…

图片饱和度_摄影后期完全调色指南(三):饱和度与自然饱和度有什么区别?...

泼辣年度大课 | 摄影后期完全调色指南(一)摄影后期完全调色指南(二)&#xff1a;详解色温与色调在上一篇教程里面我们讲到了全局色彩调整中的色温和色调工具&#xff0c;今天来讲解剩下的两个工具&#xff1a;饱和度与自然饱和度工具&#xff0c;这两个工具使用的频率也非常的高…

windows 下的文件对比工具

deffinity,一款专用于文件对比的工具,安装简单,使用方便 下载链接如下: https://www.appinn.com/diffinity-for-win/

点名册_骑士新书《万界点名册》十万收藏火爆气势不减当年修真聊天群

最近好多人都在讨论圣骑士的传说新书《万界点名册》&#xff0c;新书还没有发布&#xff0c;起点就给了闪屏推荐&#xff0c;引得的很多读者心痒痒的想要看看到底是怎样的神作。所以才发布两万多字的新书投资人就快一万人了&#xff0c;收藏也突破了十万。这速度好像是坐了火箭…

500张思维导图记38000单词_思维导图记单词,环环相扣难遗忘——2020奇速英语夏令营第三天...

2020年8月6日&#xff0c;是奇速英语19期夏令营第二批单词阅读营正式上课的第3天&#xff0c;为了激发各班学员学习兴趣&#xff0c;保证学习效果&#xff0c;奇速英语专门制定了小组PK的积分奖励制在今天的学习当中发挥了极大的效果。在这一机制的刺激和奇速英语老师们的高效教…

语音识别热词_出门问问 TWS 耳机语音交互解决方案

随着芯片、算法商在技术及应用方面的演进&#xff0c;尤其是连接、降噪方向的重大突破&#xff0c;越来越多的手机及耳机厂商推出了自家功能丰富、交互舒适的 TWS 耳机。近日&#xff0c;出门问问新品发布会上推出的 TicPods 2 系列产品&#xff0c;在人机交互和语音助理方面&a…

go tcp连接_TCP漫谈之keepalive和time_wait

TCP是一个有状态通讯协议&#xff0c;所谓的有状态是指通信过程中通信的双方各自维护连接的状态。一、TCP keepalive先简单回顾一下TCP连接建立和断开的整个过程。(这里主要考虑主流程&#xff0c;关于丢包、拥塞、窗口、失败重试等情况后面详细讨论。)首先是客户端发送syn(Syn…

mysql update 联合更新_Mysql update多表联合更新的方法小结

下面我建两个表&#xff0c;并执行一系列sql语句&#xff0c;仔细观察sql执行后表中数据的变化&#xff0c;很容易就能理解多表联合更新的用法student表 class表1. 执行 UPDATE student s , class c SET s.class_nametest00,c.stu_nametest00 WHERE s.class_id c.idstudent表 …

mysql查询开启事务_MySQL中的查询事务问题

之前帮同学做个app的后台&#xff0c;使用了MySQLMyBatis&#xff0c;遇到了一个查询提交的问题&#xff0c;卡了很久&#xff0c;现在有时间了来复盘下环境情况假设有学生表&#xff1a;USE test;CREATE TABLE student (Id int NOT NULL PRIMARY KEY AUTO_INCREMENT,Name varc…

通过gparted 调整 ubuntu 磁盘

1. 启动和安装 1. 安装 sudo apt-get install gparted 2. 启动 sudo gparted2.配置 如果想扩充磁盘,需要有未分配空间,且该未分配空间位于partition相邻的格子

linux查看mysql表空间使用率_Oracle查看数据库表空间使用情况sql语句

Oracle查看数据库表空间使用情况sql语句SELECT UPPER(F.TABLESPACE_NAME) "表空间名",D.TOT_GROOTTE_MB "表空间大小(M)",D.TOT_GROOTTE_MB - F.TOTAL_BYTES "已使用空间(M)",TO_CHAR(ROUND((D.TOT_GROOTTE_MB - F.TOTAL_…

mysql 禁止转义_必须转义哪些字符才能阻止(我的)SQL注入?

6 个答案:答案 0 :(得分&#xff1a;46)关于退格字符的猜测&#xff1a;想象一下&#xff0c;我发送了一封电子邮件“嗨&#xff0c;这是根据需要更新数据库的查询”和带有的附加文本文件INSERT INTO students VALUES ("Bobby Tables",12,"abc",3.6);你捕获…

mysql 失效转移_MySQL基于MHA的FailOver过程

大家好&#xff0c;我是anyux。本文介绍MySQL基于MHA的FailOver过程。MHA FailOver过程详解什么是FailOver故障转移主库宕机&#xff0c;一直到业务恢复正常的处理过程如何处理FailOver1.快速监控到主库宕机2.选择新主节点&#xff0c;选择策略mysqladmin ping检查数据库状态&a…

kali 切换图形界面_kali Linux 文本图形界面切换遇到的怪问题

前段装了在Virtual Box上装一个Kali Linux玩&#xff0c;然后设为了开机进入文本界面&#xff0c;后来遇到无法上网的问题&#xff0c;网上找到解决方法&#xff0c;说是NAT地址转换和host-only双网卡顺序问题&#xff0c;按照网上的说法调整顺序后一切正常。问题及调整方法详见…

linux mysql更改生效_linux下面MySQL变量修改及生效

今天在访问mysql项目的时候突然报500错误&#xff0c;没有找到连接&#xff0c;因此想到mysql的连接时间。mysql> show global variables;主要就是连接时间是28800(8小时)&#xff0c;而且任务调度也没打开&#xff0c;因此想到修改全局变量的值。1.修改任务调度装:1.1具体的…