十分钟定制自己的Markdown语法
作为一门易读易写的语音,markdown的应用越来越广,对markdown语法解析规则进行特殊扩展的场景诉求也越来越多。
本文为大家简单介绍以marked.js
为基础的markdown语法扩展。
marked.js基本用法
marked.js是一款性能不错的前端markdown解析库,它的用法非常简单marked.js readme
import marked from 'marked';
marked.setOptions({
breaks: true, // 是否回车换行
tables:
highlight(code, lang) { // 语法高亮
let val = code;
if (lang) {
val = hljs.highlight(lang, code).value;
} else {
val = hljs.highlightAuto(code).value;
}
return val;
},
....
});
const html = marked('# 这是一个一级标题');
// output: <h1 id="这是一个一级标题">这是一个一级标题</h1>
renderer对象
marked.js
的所有输出基本都依赖renderer
对象,并且这个renderer我们是可以自定义规则的。
比如我们希望图片后面带上图片的说明
const renderer = new marked.Renderer();
renderer.image = function(href, title, text) {
return '<img src="' + imageLink + '" alt="' + title + '"/><div class="desc">'+ title +'</div>';
};
const html = marked('[测试图片](http://test.png)');
// output: <img src="http://test.png" alt="测试图片"/><div class="desc">测试图片</div>
自定义Lexer Token
通过自定义renderer
对象的方式我们已经可以满足大部分自定义场景的诉求了。
什么?你说renderer只能自定义规则的输出,你还要加自己的解析规则?没关系,接着往下看。
block对象
打开marked.js源码,映入眼帘的就是一个包裹着一大堆正则的block对象
var block = {
newline: /^\n+/,
code: /^( {4}[^\n]+\n*)+/,
fences: /^ {0,3}(`{3,}|~{3,})([^`~\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?:\n+|$)|$)/,
hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/,
heading: /^ {0,3}(#{1,6}) +([^\n]*?)(?: +#+)? *(?:\n+|$)/
...
}
可以猜测这应该是块级markdown语法的解析规则
Lexer.prototype.token
接着往下看你会发现这么一段代码,block里的正则用于校验lexer(层),并返回相应的token
Lexer.rules = block;
Lexer.prototype.lex = function(src) {
src = src
.replace(/\r\n|\r/g, '\n')
.replace(/\t/g, ' ')
.replace(/\u00a0/g, ' ')
.replace(/\u2424/g, '\n');
return this.token(src, true);
};
Lexer.prototype.token = function(src, top) {
src = src.replace(/^ +$/gm, '');
while (src) {
// newline
if ((cap = this.rules.newline.exec(src))) {
src = src.substring(cap[0].length);
if (cap[0].length > 1) {
this.tokens.push({
type: 'space',
});
}
}
// code
if ((cap = this.rules.code.exec(src))) {
var lastToken = this.tokens[this.tokens.length - 1];
src = src.substring(cap[0].length);
// An indented code block cannot interrupt a paragraph.
if (lastToken && lastToken.type === 'paragraph') {
lastToken.text += '\n' + cap[0].trimRight();
} else {
cap = cap[0].replace(/^ {4}/gm, '');
this.tokens.push({
type: 'code',
codeBlockStyle: 'indented',
text: !this.options.pedantic ? rtrim(cap, '\n') : cap,
});
}
continue;
}
...
}
}
我们再来看一下marked入口函数以及return的Parser
对象
- 入口函数
function marked(src, opt, callback) {
// throw error in case of non string input
if (typeof src === 'undefined' || src === null) {...}
if (typeof src !== 'string') {...}
if (callback || typeof opt === 'function') {...}
try {
if (opt) opt = merge({}, marked.defaults, opt);
checkSanitizeDeprecation(opt);
return Parser.parse(Lexer.lex(src, opt), opt);
} catch (e) {
e.message += '\nPlease report this to https://github.com/markedjs/marked.';
if ((opt || marked.defaults).silent) {
return '<p>An error occurred:</p><pre>' + escape(e.message + '', true) + '</pre>';
}
throw e;
}
}
- Parser
Parser.prototype.parse = function(src) {
this.inline = new InlineLexer(src.links, this.options);
// use an InlineLexer with a TextRenderer to extract pure text
this.inlineText = new InlineLexer(
src.links,
merge({}, this.options, { renderer: new TextRenderer() }),
);
this.tokens = src.reverse();
var out = '';
while (this.next()) {
out += this.tok(); // 使用tok方法循环输出解析后的字符串
}
return out;
};
Parser.prototype.next = function() {
this.token = this.tokens.pop(); // lexer解析的tokens队列
return this.token;
};
Parser.prototype.tok = function() {
switch (this.token.type) {
case 'space': {
return '';
}
case 'hr': {
return this.renderer.hr();
}
...
}
}
markdown字符串首先按blocks中的正则解析成tokens队列,然后按照token输出不同的字符串。
添加自定义规则
看到这里我们也发现了,要想自定义规则就需要添加token
也就需要重写Lexer.token
方法。那只能copy过来改源码了。
举个栗子,我们希望把markdown语法里(((text)))
字符串解析成<button>text</button>
首先我们在blocks里加上正则。注:Lexer.token为块级解析,行内字符串规则需在inlineLexer中添加
var blocks = {
...
button: /^\(\(\((.+)?\)\)\)/
}
然后在Lexer.prototype.token
方法中加上我们的token
Lexer.prototype.token = function (src, top){
...
if ((cap = this.rules.button.exec(src))) {
src = src.substring(cap[0].length);
if (cap[0].length > 1) {
this.tokens.push({
type: 'button',
text: cap[1]
});
}
}
}
最后在Parser.prototype.tok
方法中加上我们要输出的字符串
Parser.prototype.tok = function () {
...
case 'button': {
return '<button>' + this.token.text + '</button>';
}
}
写在最后
最后咱们来体验一下投票功能吧
0条评论