10

解析 Markdown 文档并处理其元素以输出另一个 Markdown 文档的选项有哪些?

让我们说吧

```
# unaffected #
```

# H1 #

H1
==

## H2 ##

H2
--

### H3 ###

应转换为

```
# unaffected #
```

## H1 ##

H1
--

### H2 ###

### H2 ###

#### H3 ####

在节点环境中。目标元素可能会有所不同(例如#### 可能会转换为**)。

文档可能包含其他不受影响的标记元素。

如何获得?显然,不使用正则表达式(使用正则表达式而不是成熟的词法分析器会影响# unaffected #)。我希望使用marked,但它似乎只能输出 HTML,不能输出 Markdown。

4

5 回答 5

5

这是一个带有外部降价解析器的解决方案,pandoc. 它允许在 haskell 或 python 中自定义过滤器来修改输入(还有一个node.js 端口)。这是一个 python 过滤器,它将每个标题增加一级。让我们将其另存为header_increase.py.

from pandocfilters import toJSONFilter, Header

def header_increase(key, value, format, meta):
    if key == 'Header' and value[0] < 7:
        value[0] = value[0] + 1
        return Header(value[0], value[1], value[2])

if __name__ == "__main__":
    toJSONFilter(header_increase)

它不会影响代码块。但是,它可能会将h1 和 h2 元素的setex 样式标头===(使用or ---)转换为 atx 样式标头(使用#),反之亦然。

要使用该脚本,可以从命令行调用 pandoc:

pandoc input.md --filter header_increase.py -o output.md -t markdown

使用 node.js,您可以使用pdc调用 pandoc。

var pdc = require('pdc');
pdc(input_md, 'markdown', 'markdown', [ '--filter', './header_increase.py' ], function(err, result) {
  if (err)
    throw err;

  console.log(result);
});
于 2016-02-11T14:06:26.513 回答
3

您是否考虑过使用 HTML 作为中间格式?一旦在 HTML 中,标题类型之间的差异将无法区分,因此 Markdown -> HTML 转换将为您有效地规范它们。有很多 markdown -> HTML 转换器,还有一些 HTML -> markdown。

我使用这两个包整理了一个示例:

我不知道您在这里是否有任何性能要求(阅读:这很慢......)但这是一个投资非常低的解决方案。看一看:

var md = require('markdown-it')(),
    h2m = require('h2m');

var mdContent = `
\`\`\`
# unaffected #
\`\`\`

# H1 #

H1
==

## H2 ##

H2
--

### H3 ###
`;

var htmlContent = md.render(mdContent);
var newMdContent = h2m(htmlContent, {converter: 'MarkdownExtra'});
console.log(newMdContent);

您可能需要混合使用各种组件才能获得正确的方言支持等等。我试了一堆,不能完全匹配你的输出。我想也许--是被不同的解释?这是输出,我会让你决定它是否足够好:

```
# unaffected #

```

# H1 #

# H1 #

## H2 ##

## H2 ##

### H3 ###
于 2016-02-13T05:31:14.080 回答
2

尽管 Markdown 看起来很简单,但实际上解析起来有些复杂。每个部分都建立在下一个部分的基础上,因此要涵盖所有边缘情况,即使您只想处理文档的一部分,您也需要一个完整的解析器。

例如,各种类型的块级元素可以嵌套在其他块级元素(列表、块引用等)中。大多数实现依赖于解析器中不同的特定事件顺序,以确保正确解析整个文档。如果您删除较早的部分之一,许多后期的部分会破裂。例如,代码块内的 Markdown 标记不会被解析为 Markdown,因为第一步是查找和识别代码块,以便解析中的后续步骤永远不会看到代码块。

因此,为了实现您的目标并涵盖所有可能的边缘情况,您需要一个完整的 Markdown 解析器。但是,由于您不想输出 HTML,因此您的选择有限,您需要做一些工作才能获得有效的解决方案。

Markdown 解析器基本上有三种风格(我在这里概括一下):

  1. 使用正则表达式字符串替换将 Markdown 标记换成源文档中的 HTML 标记。
  2. 使用解析器(在每个步骤中)调用的渲染,因为它解析输出新文档的文档。
  3. 生成树对象或令牌列表(具体情况因实现而异),在后续步骤中将其呈现(转换为字符串)到新文档。

原始参考实现(markdown.pl)属于第一种类型,可能对您无用。我只是为了完整性而提到它。

Marked属于第二种类型,虽然可以使用,但您需要编写自己的渲染器,并让渲染器在渲染文档的同时修改文档。虽然通常是一种性能解决方案,但当您需要修改文档时,它并不总是最好的方法,尤其是当您需要文档中其他地方的上下文时。但是,您应该能够使其工作。

例如,要改编文档中的示例,您可能会执行以下操作(从此处multiplyString借用):

function multiplyString (str, num) {
    return num ? Array(num + 1).join(str) : "";
}

renderer.heading = function (text, level) {
    return multiplyString("#", level+1) + " " + text;
}

当然,您还需要为所有其他块级渲染器方法和输出 Markdown 语法的内联级渲染器方法创建渲染器。请参阅下面关于一般渲染器的评论。

Markdown-JS属于第三种类型(事实证明,Marked 还提供了一个较低级别的 API来访问令牌,因此它也可以以这种方式使用)。如其自述文件所述:

中间代表

在内部,将 Markdown 块转换为 HTML 块的过程分为三个步骤:

  1. 将 Markdown 解析为 JsonML 树。在解析中找到的任何引用都存储在 key 下的根节点的属性哈希中references
  2. 将 Markdown 树转换为 HTML 树。重命名任何需要它的节点(bulletlist例如ul)并查找链接或图像使用的任何引用。完成后删除引用属性。
  3. 对 HTML 树进行字符串化,注意不要破坏空白很重要的空白(例如,围绕内联元素)。

如果您需要在中间阶段对数据进行一些处理或修改,则可以单独调用此过程的每个步骤。

您可以在步骤 1 或步骤 2 中获取树对象并进行修改。但是,我建议第 1 步,因为 JsonML 树将更接近实际的 Markdown 文档,因为第 2 步中的 HTML 树是要输出的 HTML 的表示。请注意,在任何实现中,HTML 都会丢失有关原始 Markdown 的一些信息。例如,星号或下划线是否用于强调(*foo*vs. _foo_),或者星号、破折号(连字符)或加号是否用作列表项目符号?我不确定 JsonML 树包含多少细节(没有亲自使用过),但它肯定比第 2 步中的 HTML 树更多。

一旦您对 JsonML 树进行了修改(可能使用此处列出的工具之一,那么您可能希望跳过第 2 步并实施您自己的第 3 步,将 JsonML 树呈现(字符串化)回 Markdown 文档。

这就是困难的部分。Markdown 解析器很少输出 Markdown。事实上,Markdown 解析器很少输出除 HTML 之外的任何内容。最流行的例外是 Pandoc,它是用于多种输入和输出格式的文档转换器。但是,想要继续使用 JavaScript 解决方案,您选择的任何库都需要您编写自己的渲染器,该渲染器将输出 Markdown(除非搜索出现由其他第三方构建的渲染器)。当然,一旦你这样做了,如果你提供它,其他人将来可以从中受益。不幸的是,构建 Markdown 渲染器超出了这个答案的范围。

构建渲染器时一种可能的捷径是,如果您使用的 Markdown 库恰好将位置信息存储在其标记列表中(或以其他方式让您可以访问每个元素的原始原始 Markdown),您可以使用渲染器中的该信息以简单地复制和输出原始 Markdown 文本,除非您需要更改它。例如,markdown-it库提供关于Token.map和/或Token.markup属性的数据。您仍然需要创建自己的渲染器,但让 Markdown 看起来更像原始渲染器应该更容易。

最后,我没有亲自使用过,也不推荐上面提到的任何特定的 Markdown 解析器。它们只是各种类型解析器的流行示例,用于演示如何创建解决方案。您可能会找到更适合您需求的不同实现。一个冗长但不完整的列表在这里

于 2016-02-11T17:02:28.953 回答
1

您必须使用正则表达式。marked本身使用正则表达式来解析文档。你为什么不呢?

这是您需要的一些正则表达式,来自github 上的marked.js 源代码

var block = {
  newline: /^\n+/,
  code: /^( {4}[^\n]+\n*)+/,
  fences: noop,
  hr: /^( *[-*_]){3,} *(?:\n+|$)/,
  heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,
  nptable: noop,
  lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,
  blockquote: /^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/,
  list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
  html: /^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,
  def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,
  table: noop,
  paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,
  text: /^[^\n]+/
};

如果你真的不想使用正则表达式,你可以 forkmarked对象。并覆盖Renderer对象。

github上标记的分为两个组件。一个用于解析,一个用于渲染。您可以轻松地将渲染更改为您自己的渲染。(编译器)

Render.js 中的一个函数示例

Renderer.prototype.blockquote = function(quote) {
  return '<blockquote>\n' + quote + '</blockquote>\n';
};)
于 2016-02-07T21:37:25.520 回答
0

也许这是不完整的答案。不受影响地复制到其他文件中。

然后全部替换

  1. #space##space

  2. space#space##

于 2016-02-17T16:10:29.250 回答