Giter Site home page Giter Site logo

zmd's People

Contributors

xovel avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

zmd's Issues

代码块,行高亮

该功能由于设计原因,现在抽出来进行移除。


部分场景下,需要针对代码块中的指定行数进行高亮显示,以强调该行的重要性。

之前的实现方式思路如下:

  1. 在处理 fence code block 的时候,提取出里面高亮行信息。通常这个信息时由花括号包含的一个文本,里面可以直接设置行数,也可以设置一个范围,示例:{3} 表示高亮第 5 行;{5-7} 表示高亮 5 - 7 行。可以使用逗号分隔设置多个:{1,4-6, 9} 表示高亮 1、4、5、6、9 这四行。

  2. 在词法分析中,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 为数组遍历方法。

该方法未对里面的有效性进行验证。

  1. 在 parse 操作中,将相关参数传递给渲染器。

  2. 在渲染器中进行针对性处理。

...
  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>'
  }
...
  1. 给予一定的样式支持即可。
/* 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 进行实现即可。

相关插件的接入方式在做进一步设计。

README.md backup

已经实现 zmd 功能,之前的 README.md 在这里做一个备份。


zmd

怎么的,假装自己是 markdown 编译工具不行吗?

综述

拟进行实现的块级功能包括:表格、块引用、段落、列表、代码块、水平线、定义列表。

行内模块拟实现的部分包含但不局限于:链接、图片、加粗、斜体、行内代码、删除线、标记、上标、下标、下划线。

计划加入的额外功能:自定义 div、脚注、公式。

参考项目

最后一条是新出的,原理简单粗暴,跟 zmd 的最初设计思路一致。

zmd 的处理流程

接收到要处理的 markdown 文本,进行以下预处理工作:

  1. 开头与结尾的留白去除。
  2. 替换 TAB 键为四个空格。
  3. 统一 Linux/Unix 换行符,去除多个换行。

可能还有一些预处理,届时也会全部进行处理。

然后开始对文本进行分块解析。根据各个块级模块的优先顺序,循环检查,有符合要求的,进行切割,存入一个中间数组,以备后续的渲染解析。

这一遍流程走完之后,得到的数组应该就是包含了分块解析后的子块级模块。

这里有一个特殊项目,就是链接和图片,以及后续可能会使用到的缩写定义、脚注的定义。这一块将会直接存进一个对象当中,当后续解析的时候,在从这里进行具体的数据提取。

然后依次对这些块级模块做行内解析。这一块面临的问题比较多,还需要做进一步的理顺。

marked 原理分析

marked 是一个非常快速的 markdown 语法解析与编译工具。正如官方 repo 中的介绍一样:

A markdown parser and compiler. Built for speed.

marked 的源码分析,这里不做详细说明,提一下其核心原理:

首先,marked 通过正则表达式对输入的字符串进行循环判断,对块状模块(如表格代码块段落列表块引用分隔符等等)进行块状分析

然后,依次对这些块状模块进行解析。涉及到复杂的块状模块,比如表格列表,再进行一次块状分析。

块状分析完毕之后,依次对这些分析好的模块进行行内分析

行内分析的方式跟块状分析类似,行内模块有:链接图片行内代码加粗斜体删除线等等。

行内分析完毕之后,开始执行编译成 HTML 代码的工作。

块级模块优先级

  1. 换行。换行的正则判定为:/^\n+/
  2. 代码块。代码块的正则判定为:/^( {4}[^\n]+\n*)+/
  3. 围栏代码GFM 风格的代码块,正则判定为:/^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/
  4. 标题。#标识符的标题类型,正则判定为/^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/
  5. 无边缘竖线符的表格。正则:/^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/
  6. atx风格的标题/^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/
  7. 水平线/^( *[-*_]){3,} *(?:\n+|$)/
  8. 块引用/^( *>[^\n]+(\n(?! *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$))[^\n]+)*\n*)+/
  9. 列表/^( *)((?:[*+-]|\d+\.)) [\s\S]+?(?:\n+(?=\1?(?:[-*_] *){3,}(?:\n+|$))|\n+(?= *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$))|\n{2,}(?! )(?!\1(?:[*+-]|\d+\.) )\n*|\s*$)/
  10. HTML模块。HTML 模块的正则判定比较复杂:/^ *(?:<!--[\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 标签。
  11. 定义/^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/。定义模块用来处理图片或者链接的预定义,这个模块的内容处理过后会从文本中删除。
  12. 表格/^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/。跟第 5 条类似。
  13. 段落/^((?:[^\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*/
  14. 文本/^[^\n]+/。事实上,经过以上处理过后的东西如果仍然走到这一步,就按文本进行处理。

行内优先级

  1. 转义。首先对转义字符进行处理。转义的正则表达式为:/^\\([\\`*{}\[\]()#+\-.!_>~|])/
  2. 自动链接/^<([^ >]+(@|:\/)[^ >]+)>/。这里的自动链接是通过对应尖括号 <> 显式指定的。
  3. 自动URL/^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/。对符合规则的代码自动添加链接效果。
  4. 行内HTML标签/^<!--[\s\S]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/
  5. 链接和图片/^!?\[((?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*)\]\(\s*<?([\s\S]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*\)/
  6. 引用类型的链接和图片/^!?\[((?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*)\]\s*\[([^\]]*)\]/
  7. 强调/加粗/^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/
  8. 斜体/^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/
  9. 行内代码/^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/
  10. 换行/^ {2,}\n(?!\s*$)/。广义的规则为:/^ *\n(?!\s*$)/
  11. 删除线/^~~(?=\S)([\s\S]*?\S)~~/
  12. 文本/^[\s\S]+?(?=[\\<!\[_*`~]|https?:\/\/| {2,}\n|$)/

嵌套的处理

markdown 语法中同级模块是支持嵌套的,marked 在进行解析的时候也是支持嵌套的。具体实现了嵌套的模块如下:

  • 列表
  • 块引用
  • 链接可以嵌套强调/斜体/删除线/行内代码,强调/删除线可以嵌套斜体/行内代码

marked 速度快的地方就体现这对这一块的处理上面。在块级模块的分析中,即列表和块引用,在每个块级模块分析处理的前后加入 startend 标识,然后进行递归分析,方便在后面进行 HTML 解析的时候根据标识符来进行具体的操作。

HTML 转码

行内代码和代码块的部分需要进行 HTML 编码变换,以支持特殊字符如 <> 等。

尚未解决的一些问题

  • 链接方式的 url 内不能包含圆括号 )*
  • 代码块语法 ``` 包起来的文本中不能包含 ```
  • gfm 中的任务列表暂不支持。(编译渲染函数中可以稍作调整以支持该功能,因为一些特定因素,作者没有将其加入源代码中。)

remarkable 原理分析

remarkable 是另一个速度极快的 markdown 解析工具。在常见的开源 markdown 解析工具中,remarkablemarked 的速度是最快的。

remarkable 采用的是模块化设计,构建之后的源代码中,包含了 59 个子模块(1.5.0 版本)。这也导致了另一个问题:代码体积较大。

remarkablemarked 的一些辅助函数本质上是一样的,比如 HTML 转码、正则表达式的替换、混淆链接等。两者对文本的处理步骤是类似的,都是先对文本进行预处理,分析块级模块,在需要进行嵌套的地方进行递归。块级模块的分析中也加入了 startend 标识。事实上,大部分的模块包括行内模块都加入了 startend 标识(图片/上标/下标/换行/普通文本没有,特殊模块比如代码也没有)。具体代码在 renderer 模块中。

remarkable 支持的模块

相对 markedremarkable 支持脚注、定义列表、任务列表等块级模块的解析。行内模块支持标记、上标、下标等,种类更多,功能更强大。

各个 markdown 解析工具速度的比较

总的来说,顺序是: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 的设定是支持定义列表这种语法,但具体语法要采用哪一种还需要进一步确定。

list 相关

目前 zmdlist 的支持效果非常差劲,测试规范覆盖率时只有 33.33% 和 25.93%,可谓是极其惨淡了。

如果只考虑单行的 list item,那么这个正则就可以满足要求了:

/^( {0,3}([*+-]|\d{1,9}[.)]))((?: *\n|$)|( +[^ \n][^\n]*)(?:\n|$))/

针对后续的场景,可以对其作出向断言并截取内容的操作。但由于规范描述的东西每一个都是独立存在的节奏,处理起来复杂程度较高。

tab 相关

\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')
...

关于 blockquote 的嵌套处理

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 编辑器中的结果

这段文本如果在 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 的时候勉强还是可以解决。


说到嵌套,相信列表也会面临同样的问题。

trimEnd 相关

听说有一个操作,叫做 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
}

关于反勾号 `` ` `` 的处理的探讨

在现行的 github 的 markdown 解析操作中,单独针对反勾号的语法并没有明确指出。

在使用 marked 工具进行解析的时候,对应的 code 的正则判定表达式为: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/

然而输入 `` ``` `` 这个进行测试的时候,结果却多留出了一个空格,解析结果为:

> m('`` ``` ``')
'<p><code>``` </code></p>\n'

m 为 `require('marked') 的变量引用,下同。

在 github 中采用 ```` `` ``` `` ```` 这样的方式可以得到 `` ``` `` 这样的结果,对应解析出来的东西就是 ``` 了。并没有多余的空格留存。

故此,在 zmd 实现的时候,对这将重新进行实现,需要对正则表达式进行重新书写。

front-matter 相关处理

什么是 front-matter

front-matter 指的是位于文档最开头部分的一段属性描述性质的文本。

通常有以下三种形式:

YAML

以 YAML 语法进行描述,开头和结尾为 --- 标识符。

TOML

以 TOML 语法进行描述,开头和结尾为 +++ 标识符。

JSON

一个简单对象的 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 的解析。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.