xovel / zmd Goto Github PK
View Code? Open in Web Editor NEW怎么的,假装自己是 markdown parser。
Home Page: https://xovel.github.io/zmd
License: MIT License
怎么的,假装自己是 markdown parser。
Home Page: https://xovel.github.io/zmd
License: MIT License
听说有一个操作,叫做 REDOS,形如 /c*$/
的正则构造可能会导致 REDOS,吓得我赶紧写了一个方法做为替代:
// trimEnd by token without regular expression
// /c*$/ is vulnerable to REDOS. ??
function _trimEnd(text, token) {
while (text && token && text.substring(text.length - token.length) === token) {
text = text.substring(0, text.length - token.length)
}
return text
}
front-matter
front-matter
指的是位于文档最开头部分的一段属性描述性质的文本。
通常有以下三种形式:
以 YAML 语法进行描述,开头和结尾为 ---
标识符。
以 TOML 语法进行描述,开头和结尾为 +++
标识符。
一个简单对象的 JSON 文本,即以 {
开头,}
结束。或者开头和结尾都为 ;;;
标识符。
以前在 hexo
进行站点构建的时候,从里面提取出了 yaml
文本匹配的正则:
/^(\ufeff?(= yaml =|---)$([\s\S]*?)^(?:\2|\.\.\.)$(?:\n)?)/m
正则不对里面的语法是否合规进行校验。
该操作应该同样适用于 toml
语法。
至于 JSON,可能相对来说就比较复杂了,根据 JSON 规范,在对象和数组方面,它是支持各种嵌套的,这导致了正则表达式的检测机制就不起作用了。
根据 front-matter
的属性描述,基本上可以排除对象中包含对象,数组中包含数组、数组中包含对象的情形,也就是编写一个简单的 JSON 匹配的正则表达式也可以满足。
于是根据 JSON 规范 编制了几个简单的正则:
var re = {
string: /"(?:(?:[^\x00-\x1f"\\]|\\(?:[\\\/bfnrt]|u[0-9a-fA-F]{4}))*)"/,
number: /-?(?:0|1-9\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/,
array: /\[(?:\s*(?:string|number|true|false|null)\s*,)*?\s*(?:string|number|true|false|null)\s*\]/,
value: /string|number|true|false|null|array/,
// simple json object
json: /\{(?:\s*(?:string)\s*:\s(?:value)\s*,)*?\s*(?:string)\s*:\s(?:value)\s*\}/
}
最终生成的 JSON 简单对象的正则表达式大概长这样:
\{(?:\s*(?:"(?:(?:[^\x00-\x1f"\\]|\\(?:[\\\/bfnrt]|u[0-9a-fA-F]{4}))*)")\s*:\s(?:"(?:(?:[^\x00-\x1f"\\]|\\(?:[\\\/bfnrt]|u[0-9a-fA-F]{4}))*)"|-?(?:0|1-9\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?|true|false|null|\[(?:\s*(?:"(?:(?:[^\x00-\x1f"\\]|\\(?:[\\\/bfnrt]|u[0-9a-fA-F]{4}))*)"|-?(?:0|1-9\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?|true|false|null)\s*,)*?\s*(?:"(?:(?:[^\x00-\x1f"\\]|\\(?:[\\\/bfnrt]|u[0-9a-fA-F]{4}))*)"|-?(?:0|1-9\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?|true|false|null)\s*\])\s*,)*?\s*(?:"(?:(?:[^\x00-\x1f"\\]|\\(?:[\\\/bfnrt]|u[0-9a-fA-F]{4}))*)")\s*:\s(?:"(?:(?:[^\x00-\x1f"\\]|\\(?:[\\\/bfnrt]|u[0-9a-fA-F]{4}))*)"|-?(?:0|1-9\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?|true|false|null|\[(?:\s*(?:"(?:(?:[^\x00-\x1f"\\]|\\(?:[\\\/bfnrt]|u[0-9a-fA-F]{4}))*)"|-?(?:0|1-9\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?|true|false|null)\s*,)*?\s*(?:"(?:(?:[^\x00-\x1f"\\]|\\(?:[\\\/bfnrt]|u[0-9a-fA-F]{4}))*)"|-?(?:0|1-9\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?|true|false|null)\s*\])\s*\}
最终匹配 front-matter
的正则就可以是这样的:
/^(?:([+-;])\1{2} *\n([\s\S]*?)\1{3}|(json)) *(?:\n+|$)/
其实完全可以直接采用 JONS.parse
尝试解析。但既然本项目采用正则进行结果解析,所以也针对这个进行了一番模拟检测。
细想之下,该特性,已经不在 markdown 解析的范畴之内了。所以就当做是一个昙花一现的事情吧。
这里留一个记录,此功能将先做移除处理。
zmd
后续将会设计一个比较恰当的插件系统,以便于支持 front-matter
的解析。
目前 zmd
对 list
的支持效果非常差劲,测试规范覆盖率时只有 33.33% 和 25.93%,可谓是极其惨淡了。
如果只考虑单行的 list item,那么这个正则就可以满足要求了:
/^( {0,3}([*+-]|\d{1,9}[.)]))((?: *\n|$)|( +[^ \n][^\n]*)(?:\n|$))/
针对后续的场景,可以对其作出向断言并截取内容的操作。但由于规范描述的东西每一个都是独立存在的节奏,处理起来复杂程度较高。
已经实现
zmd
功能,之前的README.md
在这里做一个备份。
怎么的,假装自己是 markdown
编译工具不行吗?
拟进行实现的块级功能包括:表格、块引用、段落、列表、代码块、水平线、定义列表。
行内模块拟实现的部分包含但不局限于:链接、图片、加粗、斜体、行内代码、删除线、标记、上标、下标、下划线。
计划加入的额外功能:自定义 div、脚注、公式。
最后一条是新出的,原理简单粗暴,跟 zmd 的最初设计思路一致。
zmd
的处理流程接收到要处理的 markdown 文本,进行以下预处理工作:
可能还有一些预处理,届时也会全部进行处理。
然后开始对文本进行分块解析。根据各个块级模块的优先顺序,循环检查,有符合要求的,进行切割,存入一个中间数组,以备后续的渲染解析。
这一遍流程走完之后,得到的数组应该就是包含了分块解析后的子块级模块。
这里有一个特殊项目,就是链接和图片,以及后续可能会使用到的缩写定义、脚注的定义。这一块将会直接存进一个对象当中,当后续解析的时候,在从这里进行具体的数据提取。
然后依次对这些块级模块做行内解析。这一块面临的问题比较多,还需要做进一步的理顺。
marked
原理分析marked
是一个非常快速的 markdown 语法解析与编译工具。正如官方 repo 中的介绍一样:
A markdown parser and compiler. Built for speed.
marked
的源码分析,这里不做详细说明,提一下其核心原理:
首先,marked
通过正则表达式对输入的字符串进行循环判断,对块状模块(如表格,代码块,段落,列表,块引用,分隔符等等)进行块状分析。
然后,依次对这些块状模块进行解析。涉及到复杂的块状模块,比如表格,列表,再进行一次块状分析。
块状分析完毕之后,依次对这些分析好的模块进行行内分析。
行内分析的方式跟块状分析类似,行内模块有:链接,图片,行内代码,加粗,斜体,删除线等等。
行内分析完毕之后,开始执行编译成 HTML 代码的工作。
/^\n+/
/^( {4}[^\n]+\n*)+/
GFM
风格的代码块,正则判定为:/^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/
/^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/
/^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/
/^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/
/^( *[-*_]){3,} *(?:\n+|$)/
/^( *>[^\n]+(\n(?! *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$))[^\n]+)*\n*)+/
/^( *)((?:[*+-]|\d+\.)) [\s\S]+?(?:\n+(?=\1?(?:[-*_] *){3,}(?:\n+|$))|\n+(?= *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$))|\n{2,}(?! )(?!\1(?:[*+-]|\d+\.) )\n*|\s*$)/
/^ *(?:<!--[\s\S]*?--> *(?:\n|\s*$)|<((?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\b)\w+(?!:\/|[^\w\s@]*@)\b)[\s\S]+?<\/\1> *(?:\n{2,}|\s*$)|<(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\b)\w+(?!:\/|[^\w\s@]*@)\b(?:"[^"]*"|'[^']*'|[^'">])*?> *(?:\n{2,}|\s*$))/
。中间一大串有竖线分割的字符串的每个单元表示 HTML 模块将会忽略的 HTML 标签。/^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/
。定义模块用来处理图片或者链接的预定义,这个模块的内容处理过后会从文本中删除。/^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/
。跟第 5 条类似。/^((?:[^\n]+\n?(?! *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\2 *(?:\n+|$)|( *)((?:[*+-]|\d+\.)) [\s\S]+?(?:\n+(?=\3?(?:[-*_] *){3,}(?:\n+|$))|\n+(?= *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$))|\n{2,}(?! )(?!\1(?:[*+-]|\d+\.) )\n*|\s*$)|( *[-*_]){3,} *(?:\n+|$)| *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)|([^\n]+)\n *(=|-){2,} *(?:\n+|$)|( *>[^\n]+(\n(?! *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$))[^\n]+)*\n*)+|<(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\b)\w+(?!:\/|[^\w\s@]*@)\b| *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)))+)\n*/
/^[^\n]+/
。事实上,经过以上处理过后的东西如果仍然走到这一步,就按文本进行处理。/^\\([\\`*{}\[\]()#+\-.!_>~|])/
。/^<([^ >]+(@|:\/)[^ >]+)>/
。这里的自动链接是通过对应尖括号 <>
显式指定的。/^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/
。对符合规则的代码自动添加链接效果。/^<!--[\s\S]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/
。/^!?\[((?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*)\]\(\s*<?([\s\S]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*\)/
/^!?\[((?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*)\]\s*\[([^\]]*)\]/
/^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/
/^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/
/^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/
/^ {2,}\n(?!\s*$)/
。广义的规则为:/^ *\n(?!\s*$)/
/^~~(?=\S)([\s\S]*?\S)~~/
/^[\s\S]+?(?=[\\<!\[_*`~]|https?:\/\/| {2,}\n|$)/
markdown
语法中同级模块是支持嵌套的,marked
在进行解析的时候也是支持嵌套的。具体实现了嵌套的模块如下:
marked 速度快的地方就体现这对这一块的处理上面。在块级模块的分析中,即列表和块引用,在每个块级模块分析处理的前后加入 start
和 end
标识,然后进行递归分析,方便在后面进行 HTML 解析的时候根据标识符来进行具体的操作。
行内代码和代码块的部分需要进行 HTML 编码变换,以支持特殊字符如 <
、>
等。
)
。*
```
包起来的文本中不能包含 ```
。gfm
中的任务列表暂不支持。(编译渲染函数中可以稍作调整以支持该功能,因为一些特定因素,作者没有将其加入源代码中。)remarkable
原理分析remarkable
是另一个速度极快的 markdown 解析工具。在常见的开源 markdown 解析工具中,remarkable
与 marked
的速度是最快的。
remarkable
采用的是模块化设计,构建之后的源代码中,包含了 59 个子模块(1.5.0
版本)。这也导致了另一个问题:代码体积较大。
remarkable
和 marked
的一些辅助函数本质上是一样的,比如 HTML 转码、正则表达式的替换、混淆链接等。两者对文本的处理步骤是类似的,都是先对文本进行预处理,分析块级模块,在需要进行嵌套的地方进行递归。块级模块的分析中也加入了 start
和 end
标识。事实上,大部分的模块包括行内模块都加入了 start
和 end
标识(图片/上标/下标/换行/普通文本没有,特殊模块比如代码也没有)。具体代码在 renderer
模块中。
remarkable
支持的模块相对 marked
,remarkable
支持脚注、定义列表、任务列表等块级模块的解析。行内模块支持标记、上标、下标等,种类更多,功能更强大。
总的来说,顺序是:marked
> remarkable
> markdown-it
> showdown
。
有个问题:标题在解析的时候生成的 ID 中不带中文了,中文全部变成了短杠
-
。
来看看 marked
中关于标题的具体实现函数:
Renderer.prototype.heading = function(text, level, raw) {
return '<h'
+ level
+ ' id="'
+ this.options.headerPrefix
+ raw.toLowerCase().replace(/[^\w]+/g, '-')
+ '">'
+ text
+ '</h'
+ level
+ '>\n';
};
造成中文丢失的核心代码是这一句:raw.toLowerCase().replace(/[^\w]+/g, '-')
。
/[^\w]+/g
这个正则表达式匹配的是不是字母或者数字的字符,诸如符号和中文这些字符全部会替换成短杠 -
。
仔细看了一下在用的 hexo
博客中,对 Renderer
做了特殊处理,改变了其行为,具体代码如下:
// Add id attribute to headings
Renderer.prototype.heading = function(text, level) {
var id = anchorId(stripHTML(text));
var headingId = this._headingId;
// Add a number after id if repeated
if (headingId[id]) {
id += '-' + headingId[id]++;
} else {
headingId[id] = 1;
}
// add headerlink
return '<h' + level + ' id="' + id + '"><a href="#' + id + '" class="headerlink" title="' + stripHTML(text) + '"></a>' + text + '</h' + level + '>';
};
该文件位置在:
...\node_modules\hexo-renderer-marked\lib\renderer.js
以上的代码中,在生成标题标签的 ID 的时候有一个重复判定的操作。var id = anchorId(stripHTML(text));
这一句代码是生成标题 ID 的代码,如果文章内有多个重名的标题,那么就需要在后面添加一个额外的标识来区别这些标题,以使得每个标题的 ID 是唯一的。
表格的处理:
markdown 语法的本身是不支持表格的,但是诸多 markdown 的解析工具中,都对表格添加了支持,其语法基本一般如下:
a | |b | c |
---|---|---|
A | B | C |
D | E | F |
----
定义列表相关处理
定义列表并非每个 markdown 解析器都支持,通常来说,其语法是:
```markdown
: abc
def
另一种语法是
AAA
: aaa
zmd.js 的设定是支持定义列表这种语法,但具体语法要采用哪一种还需要进一步确定。
在现行的 github 的 markdown 解析操作中,单独针对反勾号的语法并没有明确指出。
在使用 marked 工具进行解析的时候,对应的 code 的正则判定表达式为: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/
。
然而输入 `` ``` ``
这个进行测试的时候,结果却多留出了一个空格,解析结果为:
> m('`` ``` ``')
'<p><code>``` </code></p>\n'
m 为 `require('marked') 的变量引用,下同。
在 github 中采用 ```` `` ``` `` ````
这样的方式可以得到 `` ``` ``
这样的结果,对应解析出来的东西就是 ```
了。并没有多余的空格留存。
故此,在 zmd
实现的时候,对这将重新进行实现,需要对正则表达式进行重新书写。
markdown
语法支持 blockquote
的嵌套,于是有一段丧心病狂的测试代码出现了:
m('> '.repeat(10000) + ' 1')
而在 zmd
的处理的时候,会出现这样的错误:
RangeError: Maximum call stack size exceeded
at Parser.compile (https://xovel.cn/zmd/zmd.js:1:1)
at Parser.compile (https://xovel.cn/zmd/zmd.js:1346:22)
at Parser.compile (https://xovel.cn/zmd/zmd.js:1346:22)
at Parser.compile (https://xovel.cn/zmd/zmd.js:1346:22)
at Parser.compile (https://xovel.cn/zmd/zmd.js:1346:22)
at Parser.compile (https://xovel.cn/zmd/zmd.js:1346:22)
at Parser.compile (https://xovel.cn/zmd/zmd.js:1346:22)
at Parser.compile (https://xovel.cn/zmd/zmd.js:1346:22)
at Parser.compile (https://xovel.cn/zmd/zmd.js:1346:22)
at Parser.compile (https://xovel.cn/zmd/zmd.js:1346:22)
多么熟悉的错误提示:Maximum call stack size exceeded
,便就是传说中的爆栈。
上面的代码是这个:
...
while (this.next().type !== 'blockquote_close') {
body += this.compile()
}
...
这段文本如果在 GitHub 的编辑器中,得出的结果长这样:
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote>
可以看到,从 256 行开始做了截断操作。后面的内容给忽略掉了。
zmd
的核心参考项目 marked
曾经想过试图在不截断的情况下处理这个问题,但是很遗憾,最终未能成功。截至本 issue 发布之日,marked
已经回滚该操作。
marked
采用的方式是预判下一个直接跟随的词法分析结果是不是 blockquote_start
,如果是,则挂起一个操作,继续往下,然后在收尾的一并处理 blockquote_end
。
然而却出现了不能正确渲染 > 1\n> > 2
这样的文本,本人曾经调试过,问题出在 this.next
环节,词法分析结果已经走完,但仍然会因为前面挂起了 blockquote_start
,收尾的时候判断出了点问题,导致操作失败。
所以,该如何处理这个问题,像 GitHub 一样直接做一个截断如何?还是放任该问题不管不顾?毕竟,实际操作中没有人会去搞一个这么大的嵌套。实测 5000 的时候勉强还是可以解决。
说到嵌套,相信列表也会面临同样的问题。
\t
,也就是 tab
制表符,在进行 zmd
进行解析的时候针对该符号的处理经过了一番短暂的探索,最终决定沿用 marked
的处理方式,即直接全局替换。
有尝试过只替换开头的一部分:
src = src.replace(/^ {0,3}\t/gm, ' ')
然而却出现了更多令人难以捉摸的情况:
\t\t
)不方便进行处理,或者造成代码比较复杂。\t
的,但只采用上面的替换方式,需要更新对应的正则判定规则。setext heading
同上。...
text = cap[0].replace(/^ {0,4}/gm, '')
// remove initial empty line
.replace(/^(?: *\n)+/, '')
// replace trailing empty lines to a sigle line break
.replace(/(?: *\n)+$/, '\n')
...
该功能由于设计原因,现在抽出来进行移除。
部分场景下,需要针对代码块中的指定行数进行高亮显示,以强调该行的重要性。
之前的实现方式思路如下:
在处理 fence code block 的时候,提取出里面高亮行信息。通常这个信息时由花括号包含的一个文本,里面可以直接设置行数,也可以设置一个范围,示例:{3}
表示高亮第 5 行;{5-7}
表示高亮 5 - 7 行。可以使用逗号分隔设置多个:{1,4-6, 9}
表示高亮 1、4、5、6、9 这四行。
在词法分析中,fence 模块中解析该语法。解析函数可以长这样:
function getHLines(text, code) {
var ret = []
if (text) {
var lines = code.split('\n').length
_each(text.split(/ *, */), function (item) {
if (item.indexOf('-') === -1) {
var n = +item
if (n && n <= lines) {
ret.push(n)
}
} else {
var nn = item.split(/ *- */)
for (var i = +nn[0]; i <= +nn[1] && i <= lines; i++) {
ret.push(i)
}
}
})
}
return ret
}
其中
_each
为数组遍历方法。该方法未对里面的有效性进行验证。
在 parse 操作中,将相关参数传递给渲染器。
在渲染器中进行针对性处理。
...
if (this.options.highlightLine && hLines && hLines.length > 0) {
var wrap = ''
wrap = '<div class="code-hl">\n'
for (var i = 0; i < hLines.length; i++) {
wrap += '<div class="code-hl-item" style="top:'
wrap += ((hLines[i] - 1) * (this.options.highlightHeight || 20) + (this.options.highlightOffsetTop || 16))
wrap += 'px"></div>\n'
}
out = wrap + out + '</div>'
}
...
/* code highlight lines */
.code-hl {
position: relative;
background-color: #f6f8fa;
border-radius: 3px;
}
.code-hl pre {
line-height: 20px;
background: none;
position: relative;
z-index: 2;
}
.code-hl-item {
position: absolute;
z-index: 1;
height: 20px; width: 100%;
background-color: rgba(0, 0, 0, .1);
}
由于该功能涉及到的代码量过于繁杂,故此进行移除。GFM 规范中并没有要求解析这类数据。
转移办法为在 fence 词法分析的时候将信息收集起来,parse 的时候将其传入 renderer,通过自定义 renderer 进行实现即可。
相关插件的接入方式在做进一步设计。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.