Token.prototype.attrIndex = function attrIndex(name) {
var attrs, i, len;
if (!this.attrs) { return -1; }
attrs = this.attrs;
for (i = 0, len = attrs.length; i < len; i++) {
if (attrs[i][0] === name) { return i; }
}
return -1;
};
根据 attribute name 返回索引。
attrPush()
Token.prototype.attrPush = function attrPush(attrData) {
if (this.attrs) {
this.attrs.push(attrData);
} else {
this.attrs = [ attrData ];
}
};
添加一个 [name, value] 对。
attrSet
Token.prototype.attrSet = function attrSet(name, value) {
var idx = this.attrIndex(name),
attrData = [ name, value ];
if (idx < 0) {
this.attrPush(attrData);
} else {
this.attrs[idx] = attrData;
}
};
覆盖或添加一个 [name, value] 对。
attrGet
Token.prototype.attrGet = function attrGet(name) {
var idx = this.attrIndex(name), value = null;
if (idx >= 0) {
value = this.attrs[idx][1];
}
return value;
};
根据 name 返回属性值
attrJoin
Token.prototype.attrJoin = function attrJoin(name, value) {
var idx = this.attrIndex(name);
if (idx < 0) {
this.attrPush([ name, value ]);
} else {
this.attrs[idx][1] = this.attrs[idx][1] + ' ' + value;
}
};
Ruler.prototype.enable = function (list, ignoreInvalid) {
if (!Array.isArray(list)) { list = [ list ]; }
var result = [];
// Search by name and enable
list.forEach(function (name) {
var idx = this.__find__(name);
if (idx < 0) {
if (ignoreInvalid) { return; }
throw new Error('Rules manager: invalid rule name ' + name);
}
this.__rules__[idx].enabled = true;
result.push(name);
}, this);
this.__cache__ = null;
return result;
};
开启 list 列出的 rule,不影响其他 rule。
enableOnly
Ruler.prototype.enableOnly = function (list, ignoreInvalid) {
if (!Array.isArray(list)) { list = [ list ]; }
this.__rules__.forEach(function (rule) { rule.enabled = false; });
this.enable(list, ignoreInvalid);
};
先将其他 rule 都禁用,仅仅只开启 list 对应的 rule。
getRules
Ruler.prototype.getRules = function (chainName) {
if (this.__cache__ === null) {
this.__compile__();
}
return this.__cache__[chainName] || [];
};
前言
要想理清 MarkdownIt 源码的来龙去脉,必须要清楚两个基础类—— Ruler & Token。
Token
俗称词法单元。
md 接收一个字符串,经过一系列的 parser 的处理,变成了一个个 token,接着调用 render 对应的rule,将 token 作为输入,最后输出 HTML 字符串。
先来看下 Token 的定义,位于
lib/token.js
。type
token 的类型,比如
paragraph_open
、paragraph_close
、hr
,分别会渲染成<p>
、</p>
、<hr>
。tag
标签名称,比如
p
、strong
、''
(空字符串。代表是文字)等等。attrs
HTML 标签元素的特性,如果存在,则是一个二维数组,比如
[["href", "http://dev.nodeca.com"]]
map
token 的位置信息,数组只有两个元素,前者是起始行、后者是结束行。
nesting
标签的类型,1 是开标签,0 是自闭合标签,-1 是关标签。例如
<p>
、<hr>
、</p>
。level
缩紧的层级。
children
子token。只有 type 为 inline 或者 image 的 token 会有 children。因为 inline 类型的 token 还会经历一次 parser,提取出更详细的 token,比如以下的场景。
content
放置标签之间的内容。
markup
一些特定语法的标记。比如 "```" 表明是一个 code block。"**" 是强调的语法。"-" 或者 "+" 是一个列表。
info
type 为 fence 的 token 会有 info 属性。什么是 fence 呢,如下:
**/
meta
一般插件用来放任意数据的。
block
ParserCore 生成的 token 的 block 为 true,ParserInline 生成的 token 的 block 为 true。
hidden
如果为 true,该 token 不会被 render。
接下来看一下原型上的方法。
attrIndex()
根据 attribute name 返回索引。
attrPush()
添加一个 [name, value] 对。
attrSet
覆盖或添加一个 [name, value] 对。
attrGet
根据 name 返回属性值
attrJoin
根据 name 将当前的 value 拼接到以前的 value 上去。
Token 小结
Token 是 MarkdownIt 内部最基础的类,也是最小的分割单元。它是 parse 的产物,也是 output 的依据。
Ruler
再来看下 MarkdownIt 另外的一个类 —— Ruler,可以认为它是职责链函数的管理器。因为它内部存储了很多 rule 函数,rule 的职能分为两种,一种是 parse rule,用来解析用户传入的字符串,生成 token,另一种是 render rule,在产出 token 之后,再根据 token 的类型调用不同的 render rule,最终吐出 HTML 字符串。
先从 constructor 说起。
__rules__
用来放所有的 rule 对象,它的结构如下:
有些人会对 alt 疑惑,这个先留个坑,在分析
__compile__
方法的时候会细说。cache
用来存放 rule chain 的信息,它的结构如下:
再来分析一下原型上各个方法的作用。
__find__
根据 rule name 查找它在 __rules__ 的索引。
__compile__
生成职责链信息。
举个栗子:
at
用来替换某一个 rule 的 fn 或者更改它所属的 chain name。
before
在某个 rule 之前插入一个新 rule。
after
在某个 rule 之后插入一个新 rule。
push
增加 rule。
enable
开启 list 列出的 rule,不影响其他 rule。
enableOnly
先将其他 rule 都禁用,仅仅只开启 list 对应的 rule。
getRules
根据 rule chain 的 key,获取对应的 fn 函数队列。
Ruler 小结
可以看出,Ruler 是相当的灵活,不管是
at
、before
、after
、enable
还是其他方法,都赋予了 Ruler 极大的灵活性与扩展性,作为使用方,可以利用这些优秀的架构设计满足特定需求。总结
分析完了 Token 与 Ruler 这些基础类,我们将进一步揭开 MarkdownIt 源码的面纱。后面的文章,再分析怎么从 src 字符串 parse 生成 token 的,token 又是怎么被 renderer.render 输出成最后的字符串。下一篇,我们将进入 MarkdownIt 的入口 parser —— CoreParser 的分析。