markdown-it源码分析系列六——最终渲染器render函数的规则定义与解析器方法分析
2023-11-03 23:56:49
在这个系列的之前几篇文章中,我们分析了 markdown-it 的核心对象 Parser 的类定义与构造函数,分析了 markdown-it 语法规则定义的对象 core,分析了 markdown-it 的 Token 类定义与实例的创建与渲染。我们分析到了 token 已经生成了,如何使用它们来渲染成我们需要的内容呢?而负责这件事的,就是 markdown-it 的 Renderer 对象。本篇将对它的实现进行分析。
Renderer 类与方法
在所有 Parser 编译生成完 tokens 的时候,就传给 Renderer.render 方法了。我们来看下 Renderer 的定义。它位于 lib/renderer.js。
var Renderer = function(rules) {
this._rules = rules;
};
Renderer 是一个构造函数,它接收一个 rules 参数,该参数是一个对象,其中包含了不同类型的 token 渲染函数。
Renderer.prototype.render = function(tokens, options, env) {
var html = '';
var rules = this._rules;
for (var i = 0, len = tokens.length; i < len; i++) {
var token = tokens[i];
// 换行符,跳过。
if (token.type === 'space') {
continue;
}
// 其他类型,调用对应的渲染规则来生成 HTML。
html += rules[token.type](token, options, env, this);
}
return html;
};
Renderer.prototype.render 方法是 Renderer 的核心方法,它接收 tokens、options 和 env 三个参数,返回渲染后的 HTML 字符串。
- tokens 是一个包含所有 token 的数组。
- options 是一个包含各种选项的对象,这些选项可以影响渲染后的 HTML 的样式。
- env 是一个包含环境变量的对象,这些变量可以影响渲染后的 HTML 的内容。
在 render 方法中,我们首先创建一个空字符串 html 来存储渲染后的 HTML 字符串。然后,我们遍历 tokens 数组,对于每个 token,我们调用对应的渲染规则来生成 HTML 字符串。最后,我们将生成的 HTML 字符串连接成一个字符串并返回。
再来细看 render 方法的具体实现。首先,我们检查 token 的类型。如果是 space 类型,我们直接跳过,因为 space 类型 token 表示换行符,我们不需要在 HTML 中渲染换行符。
if (token.type === 'space') {
continue;
}
如果不是 space 类型,我们调用对应的渲染规则来生成 HTML 字符串。
html += rules[token.type](token, options, env, this);
其中,rules[token.type] 是一个函数,它接收 token、options、env 和 this 四个参数,返回渲染后的 HTML 字符串。
最后,我们将生成的 HTML 字符串连接成一个字符串并返回。
return html;
渲染规则
渲染规则是用来将 token 渲染成 HTML 字符串的函数。这些渲染规则定义在 default_rules 对象中,该对象位于 lib/renderer_rules.js。
var default_rules = {
// 代码块
'code': function(tokens, idx, options, env, self) {
return highlight(
tokens[idx].content,
tokens[idx].info,
options.highlight
);
},
// 引用
'blockquote': function(tokens, idx, options, env, self) {
return '<blockquote>\n' + self.render(tokens[idx].tokens, options, env) + '</blockquote>';
},
// 列表
'list': function(tokens, idx, options, env, self) {
var bullet = tokens[idx].ordered ? 'ol' : 'ul';
var html = '<' + bullet + '>\n';
for (var i = 0, len = tokens[idx].items.length; i < len; i++) {
html += '<li>' + self.render(tokens[idx].items[i].tokens, options, env) + '</li>\n';
}
html += '</' + bullet + '>\n';
return html;
},
// 标题
'heading': function(tokens, idx, options, env, self) {
var heading = 'h' + tokens[idx].hLevel;
return '<' + heading + '>' + self.render(tokens[idx].tokens, options, env) + '</' + heading + '>\n';
},
// 链接
'link': function(tokens, idx, options, env, self) {
return '<a href="' + self.renderInlineAsText(tokens[idx].href, tokens, options, env) + '"' +
(tokens[idx].title ? ' title="' + self.renderInlineAsText(tokens[idx].title, tokens, options, env) + '"' : '') +
'>' + self.renderInlineAsText(tokens[idx].content, tokens, options, env) + '</a>';
},
// 强调
'strong': function(tokens, idx, options, env, self) {
return '<strong>' + self.renderInlineAsText(tokens[idx].content, tokens, options, env) + '</strong>';
},
// 斜体
'em': function(tokens, idx, options, env, self) {
return '<em>' + self.renderInlineAsText(tokens[idx].content, tokens, options, env) + '</em>';
},
// 删除线
'del': function(tokens, idx, options, env, self) {
return '<del>' + self.renderInlineAsText(tokens[idx].content, tokens, options, env) + '</del>';
},
// 代码
'code': function(tokens, idx, options, env, self) {
return '<code>' + self.renderInlineAsText(tokens[idx].content, tokens, options, env) + '</code>';
},
// 引用
'br': function(tokens, idx, options, env, self) {
return '<br>';
},
// 图片
'image': function(tokens, idx, options, env, self) {
return '<img src="' + self.renderInlineAsText(tokens[idx].href, tokens, options, env) + '" alt="' + self.renderInlineAsText(tokens[idx].title, tokens, options, env) + '"';
},
// 表格
'table': function(tokens, idx, options, env, self) {
var html = '<table>\n<thead>\n';
for (var i = 0, len = tokens[idx].header.length; i < len; i++) {
html += '<th>' + self.render(tokens[idx].header[i].tokens, options, env) + '</th>\n';
}
html += '</thead>\n<tbody>\n';
for (var j = 0, len = tokens[idx].rows.length; j < len; j++) {
html += '<tr>';
for (var k = 0, len = tokens[idx].rows[j].length; k < len; k++) {
html += '<td>' + self.render(tokens[idx].rows[j][k].tokens, options, env) + '</td>';
}
html += '</tr>\n';
}
html += '</tbody>\n</table>';
return html;
},
// 表格行
'tablerow': function(tokens, idx, options, env, self) {
var html = '<tr>';
for (var i = 0, len = tokens[idx].cells.length; i < len; i++) {
html += '<td>' + self.render(tokens[idx].cells[i].tokens, options, env) + '</td>';
}
html += '</tr>';
return html;
},
// 表格头
'tablecell': function(tokens, idx, options, env, self) {
var html = '<th>' + self.render(tokens[idx].tokens, options, env) + '</th>';
return html;
},
// 表格数据
'tablecell': function(tokens, idx, options, env, self) {
var html = '<td>' + self.render(tokens[idx].tokens, options, env) + '</td>';
return html