Ekkles' Diary

All human wisdom is summed up in two words: Wait and Hope

0%

使用pandoc渲染

在之前的文章中,我们讲到了Mathjax来在Hexo中输入公式。

由于Hexo的默认渲染使用hexo-renderer-marked,在渲染Mathjax的过程中存在一些问题。

这里我使用了hexo-renderer-pandoc渲染器来替代了默认的渲染器。

安装

依赖安装

使用hexo-renderer-pandoc依赖于pandoc,所以需要先安装pandoc,参见pandoc官网

https://pandoc.org/installing.html

替换渲染器

在Hexo站点根目录执行以下命令

1
2
npm uninstall hexo-renderer-marked  # 删除默认渲染器
npm install hexo-renderer-pandoc # 安装hexo-renderer-pandoc

修改主题配置

在主题配置文件中修改下列参数

1
2
3
4
5
6
7
8
9
10
11
12
# Math Formulas Render Support
math:
# Default (true) will load mathjax / katex script on demand.
# That is it only render those page which has `mathjax: true` in Front-matter.
# If you set it to false, it will load mathjax / katex srcipt EVERY PAGE.
per_page: true

# hexo-renderer-pandoc (or hexo-renderer-kramed) required for full MathJax support.
mathjax:
enable: true
# See: https://mhchem.github.io/MathJax-mhchem/
mhchem: false

使用

正如之前的文章所述,Markdown是针对方便书写阅读的目标而设计的,Markdown的创立者John Gruber说过:

一份Markdown格式的文件应该要能以纯文字形式直接发表,并且一眼看过去不存在任何标记用的标签或格式指令。

这项原则同样也是pandoc 在制订表格、脚注以及其他扩充的语法时,所依循的规范。

然而,pandoc 的目标与原始markdown 的最初目标有着方向性的不同。在markdown 原本的设计中,HTML 是其主要输出对象;然而pandoc 则是针对多种输出格式而设计。因此,虽然pandoc 同样也允许直接嵌入HTML 标签,但并不鼓励这样的作法,取而代之的是pandoc 提供了许多非HTML 的方式,来让使用者输入像是定义清单、表格、数学公式以及脚注等诸如此类的重要文件元素。

因此,pandoc与默认的Markdown语法存在着很大的区别,这里摘录一部分pandoc不同的规则放在这里,具体可以参见pandoc官网的介绍

换行

在pandoc中,换行并非简单的回车,而是需要使用一个或多个空行来将一行或多行文本分开,一个换行符(即一个回车)将被视为空格

如果需要硬换行,在一行的结尾使用两个或两个以上的空格。

标题

标题支持两种格式:Setext以及ATX格式

Setext格式即一行文字下面接着一行=(一级标题)或-(二级标题)

1
2
3
4
一级标题
==================
二级标题
------------------

ATX模式即Markdown默认的标题格式,使用1-6个#来表达一至六级标题。

## 二级标题
### 三级标题

标题文字可以包含行内格式,例如强调。 # 一级标题带有链接 and 强调

原始markdown语法在标题之前并不需要预留空行。但是Pandoc需要在标题之前预留空行,除了文章开头第一行的标题。这是因为以#符号开头的情况在一般文字段落中相当常见,这会导致非预期的标题。例如下面的例子:

I like several of their flavors of ice cream:
#22, for example, and #5.

HTML, LaTeX与ConTex的标题识别符

在标题文字所在行的行尾,可以使用以下语法为标题加上属性:

1
{#identifier .class .class key=value key=value}

虽然这个语法也包含加入类别(class)以及键/值形式的属性(attribute),但目前只有识别符(identifier/ID)在输出时有实际作用(且只在部分格式的输出,包括:HTML, LaTeX, ConTeXt, Textile, AsciiDoc)。举例来说,下面是将标题加上foo识别符的几种方法:

1
2
3
4
# My header {#foo}
## My header ## {#foo}
My other header {#foo}
---------------

(此语法与PHP Markdown Extra相容。)

具有unnumbered类别的标题将不会被编号,即使--number-sections的选项是开启的。单一连字符号( - )等同于.unnumbered,且更适用于非英文文件中。因此,

# My header {-}

与下面这行是等价的

# My header {.unnumbered}

没有明确指定ID(识别符)的标题将会依据其标题文字,自动指派一个独一无二的ID。由标题文字推导ID 的规则如下:

  • 移除所有格式,连结等。
  • 移除所有标点符号,除了底线、连字符号与句号。
  • 以连字符号取代所有空格与换行字元。
  • 将所有英文字母转为小写。
  • 移除第一个字元前的所有内容(ID 不能以数字或标点符号开头)。
  • 如果剩下为空字串,则使用section作为ID。 以下是一些范例,

HeaderIdentifier

Header identifiers in HTML
header-identifiers-in-html

Dogs ?–in my house?
dogs--in-my-house

[HTML], [S5], or [RTF]?
html-s5-or-rtf

Applications
applications

33
section

在大多数情况下,这些规则应该让人能够直接从标题文字推导出ID。唯一的例外是当有多个标题具有同样文字的情况;在这情况下,第一个标题的ID仍旧是透过以上规则推导而得;第二个则是在同样ID后加上-1;第三个加上-2;以此类推。

在开启--toc|--table-of-contents的选项时,这些ID是用来产生目录(Table of Contents)所需的页面连结。此外,这些ID也提供了一个简便的方式来输入跳到指定章节的连结。一个以ID产生的连结,其使用的语法看起来就像下面的例子:

See the section on
[header identifiers](#header-identifiers-in-html-latex-and-context).

然而要注意的一点是,只有在以HTML、LaTeX 与ConTeXt 格式输出时,才能以这种方式产生对应的章节连结。

如果指定了--section-divs选项,则每一个小节都会以div标签包住(或是section标签,如果有指定--html5选项的话),并且ID会被附加在用来包住小节的<div >(或是<section>)标签,而非附加在标题上。这使得整个小节都可以透过javascript来操作,或是采用不同的CSS设定。

Pandoc 假设每个标题都定义了其参考连结,因此,相较于以下的连结语法

[header identifiers](#header-identifiers-in-html)

你也可以单纯只写

[header identifiers]

[header identifiers][]

[the section on header identifiers][header identifiers]

如果有多个标题具有同样文字,对应的参考只会连结到第一个符合的标题,这时若要连结到其他符合的标题,就必须以先前提到的方式,明确指定连结到该标题的ID 。

与其他一般参考连结不同的是,这些参考连结是大小写有别的。

注意:如果你有明确定义了任何一个标题的标示符,那么选项implicit_header_references就没有作用。

区块引言

这里与Markdown的习惯类似,一个引言区块可以由一或多个段落或其他的区块元素(如清单或标题)组成,并且其行首均是由一个>符号加上一个空格作为开头。(>符号不一定要位在该行最左边,但不能缩进超过三个空格)。

1
2
3
4
5
> This is a block quote. This
> paragraph has two lines.
>
> 1. This is a list inside a block quote.
> 2. Second item.

如果想偷懒的话,可以只在每个段落的第一行行首输入>,后面的行首可以忽略

1
2
3
4
> This is a block quote. This
paragraph has two lines.
> 1. This is a list inside a block quote.
2. Second item.

区块引言是可以嵌套多层的,例如

1
2
3
> This is a block quote.
>
> > A block quote within a block quote

代码区块

缩进代码区块

一段以四个空格(或一个tab)缩进的文字区块会被视为字面区块(Verbatim Block):换句话说,特殊字元并不会转换为任何格式,单纯只以字面形式呈现,而所有的空格与换行也都会被保留。例如,

if (a > 3) {
  moveShip(5 * gravity, DOWN);
}

位于行首的缩排(四个空格或一个tab)并不会被视为字面区块的一部分,因此在输出时会被移除掉。

注意:在字面文字之间的空行并不需要也以四个空格字元做开头。

围栏代码区块

除了 ​​标准的缩进代码区块外,Pandoc也支援了围栏 ( fenced )代码区块的语法。这区块需以包含三个以上波浪线( ~ )或反引号( ``` )的一行作为开始,并以同样符号且至少同样长度的一行作为结束。所有介于开始与结束之间的文字行都会视为代码。不需要额外的缩进:

1
2
3
4
5
~~~~~~~
if (a > 3) {
moveShip(5 * gravity, DOWN);
}
~~~~~~~

如同一般的代码区块,围栏代码区块与其前后的文字之间必须以空行作间隔。

如果代码本身也包含了一整行的波浪线或反引号,那么只要在区块首尾处使用更长的波浪线或反引号即可:

1
2
3
4
5
~~~~~~~~~~~~~~~~
~~~~~~~~~~
code including tildes
~~~~~~~~~~
~~~~~~~~~~~~~~~~

要取消所有语法高亮,使用--no-highlight选项。要设定语法高亮的配色,则使用--highlight-style

清单

无序清单

无序清单是以项目符号作列举的清单。每条项目都以项目符号( * , +或- )作开头。下面是个简单的例子:

1
2
3
* one
* two
* three

这会产生一个「紧凑」清单。如果你想要一个「宽松」清单,也就是说以段落格式处理每个项目内的文字内容,那么只要在每个项目间加上空行即可:

1
2
3
4
5
* one

* two

* three

项目符号不能直接从行首最左边处输入,而必须以一至三个空格字元作缩进。项目符号后必须跟着一个空格字元。

清单项目中的接续行,若与该项目的第一行文字对齐(在项目符号之后),看上去会较为美观:

1
2
3
* here is my first
list item.
* and my second.

但markdown 也允许以下「偷懒」的格式:

1
2
3
* here is my first
list item.
* and my second.
### 四个空格规则 一个清单项目可以包含多个段落以及其他区块等级的内容。然而,后续的段落必须接在空行之后,并且以四个空格或一个tab 作缩进。因此,如果项目里第一个段落与后面段落对齐的话(也就是项目符号前置入两个空格),看上去会比较整齐美观:
1
2
3
4
5
* First paragraph.
Continued.
* Second paragraph. With a code block, which must be indented
eight spaces:
{ code }
清单项目也可以包含其他清单。在这情况下前置的空行是可有可无的。嵌套清单必须以四个空格或一个tab 作缩进:
1
2
3
4
5
6
7
8
9
* fruits
+ apples
- macintosh
- red delicious
+ pears
+ peaches
* vegetables
+ brocolli
+ chard
上一节提到,markdown 允许你以「偷懒」的方式书写,项目的接续行可以不和第一行对齐。不过,如果一个清单项目中包含了多个段落或是其他区块元素,那么每个元素的第一行都必须缩进对齐。
1
2
3
4
5
6
+ A lazy, lazy, list
item.
+ Another one; this looks
bad but is legal.
Second paragraph of second
list item.
注意:尽管针对接续段落的「四个空格规则」是出自于官方的markdown syntax guide,但是作为对应参考用的Markdown.pl实作版本中并未遵循此一规则。所以当输入时若接续段落的缩进少于四个空格时,pandoc所输出的结果会与Markdown.pl的输出有所出入。

在markdown syntax guide中并未明确表示「四个空格规则」是否一体适用于所有位于清单项目里的区块元素上;规范文件中只提及了段落与代码区块。但文件暗示了此规则适用于所有区块等级的内容(包含嵌套清单),并且pandoc以此方向进行解读与实作。

有序清单

有序清单与无序清单相类似,唯一的差别在于清单项目是以列举编号作开头,而不是项目符号。

在原始markdown 中,列举编号是阿拉伯数字后面接着一个句点与空格。数字本身代表的数值会​​被忽略,因此下面两个清单并无差别:

1
2
3
1. one
2. two
3. three
上下两个清单的输出是相同的。
1
2
3
5. one
7. two
1. three
与原始markdown不同的是,Pandoc除了使用阿拉伯数字作为有序清单的编号外,也可以使用大写或小写的英文字母,以及罗马数字。清单标记可以用括号包住,也可以单独一个右括号,抑或是句号。如果清单标记是大写字母接着一个句号,句号后请使用至少两个空格字元。1

除了清单标记外,Pandoc 也能判断清单的起始编号,这两项资讯都会保留于输出格式中。举例来说,下面的输入可以产生一个从编号9 ​​开始,以单括号为编号标记的清单,底下还跟着一个小写罗马数字的子清单:

1
2
3
4
5
6
 9) Ninth
10) Tenth
11) Eleventh
i. subone
ii. subtwo
iii. subthree
当遇到不同形式的清单标记时,Pandoc 会重新开始一个新的清单。所以,以下的输入会产生三份清单:
1
2
3
4
(2) Two
(5) Three
1. Four
* Five
如果需要预设的有序清单标记符号,可以使用#.:
1
2
3
#. one
#. two
#. three
### 定义清单

Pandoc支持定义清单,其语法的灵感来自于PHP Markdown Extra以及reStructuredText:2

1
2
3
4
5
6
Term 1
: Definition 1
Term 2 with *inline markup*
: Definition 2
{ some code, part of Definition 2 }
Third paragraph of definition 2.
每个专有名词(term) 都必须单独存在于一行,后面可以接着一个空行,也可以省略,但一定要接上一或多笔定义内容。一笔定义需由一个冒号或波浪线作开头,可以接上一或两个空格作为缩进。定义本身的内容主体(包括接在冒号或波浪线后的第一行)应该以四个空格缩进。一个专有名词可以有多个定义,而每个定义可以包含一或多个区块元素(段落、代码区块、清单等),每个区块元素都要缩进四个空格或一个tab。

如果你在定义内容后面留下空行(如同上面的范例),那么该段定义会被当作段落处理。在某些输出格式中,这意谓著成对的专有名词与定义内容间会有较大的空格间距。在定义与定义之间,以及定义与下个专有名词间不要留空行,即可产生一个比较紧凑的定义清单:

1
2
3
4
5
Term 1
~ Definition 1
Term 2
~ Definition 2a
~ Definition 2b
### 编号范例清单

这个特别的清单标记@可以用来产生连续编号的范例清单。清单中第一个以@标记的项目会被编号为’1’,接着编号为’2’,依此类推,直到文件结束。范例项目的编号不会局限于单一清单中,而是文件中所有以@为标记的项目均会次序递增其编号,直到最后一个。举例如下:

1
2
3
4
(@) My first example will be numbered (1).
(@) My second example will be numbered (2).
Explanation of examples.
(@) My third example will be numbered (3).
编号范例可以加上标签,并且在文件的其他地方作参照:
1
2
(@good) This is a good example.
As (@good) illustrates, ...
标签可以是由任何英文字母、底线或是连字符号所组成的字串。

紧凑与宽松清单

在与清单相关的「边界处理」上,Pandoc与Markdown.pl有着不同的处理结果。考虑如下代码:
1
2
3
4
5
6
+ First
+ Second:
- Fee
- Fie
- Foe
+ Third
Pandoc会将以上清单转换为「紧凑清单」(在“First”, “Second”或“Third”之中没有

标签),而markdown则会在“Second”与“Third” (但不包含“ First”)里面置入

标签,这是因为“Third”之前的空行而造成的结果。Pandoc依循着一个简单规则:如果文字后面跟着空行,那么就会被视为段落。既然“Second”后面是跟着一个清单,而非空行,那么就不会被视为段落了。至于子清单的后面是不是跟着空行,那就无关紧要了。(注意:即使是设定为markdown_strict格式,Pandoc仍是依以上方式处理清单项目是否为段落的判定。这个处理方式与markdown官方语法规范里的描述一致,然而却与Markdown.pl的处理不同。)

结束一个清单

如果你在清单之后放入一个缩排的代码区块,会有什么结果?

1
2
3
- item one
- item two
{ my code block }
问题大了!这边pandoc(其他的markdown实作也是如此)会将{ my code block }视为item two这个清单项目的第二个段落来处理,而不会将其视为一个代码区块。

要在item two之后「切断」清单,你可以插入一些没有缩排、输出时也不可见的内容,例如HTML的注解:

1
2
3
4
- item one
- item two
<!-​​- end of list -->
{ my code block }
当你想要两个各自独立的清单,而非一个大且连续的清单时,也可以运用同样的技巧:
1
2
3
4
5
6
7
1. one
2. two
3. three
<!-​​- -->
1. uno
2. dos
3. tres
## 分割线 一行中若包含三个以上的* , -或_符号(中间可以以空格字元分隔),则会产生一条分隔线:
1
2
* * * *
---------------
## 数学公式

所有介于两个\(字元之间的内容将会被视为TeX数学公式处理。**开头的\)右侧必须立刻接上任意文字,而结尾$的左侧同样也必须紧挨着文字**。这样一来,$20,000 and $30,000就不会被当作数学公式处理了。

如果基于某些原因,有必须使用\(符号将其他文字括住的需求时,那么可以**在\)前使用反斜线跳脱字元**,这样$就不会被当作数学公式的分隔符。

参考资料

https://daringfireball.net/projects/markdown/syntax#philosophy

https://pandoc.org/MANUAL.html#pandocs-markdown

https://www.bookstack.cn/read/mba811-Writing/Pandoc-01.md#%E6%97%A0%E5%BA%8F%E6%B8%85%E5%8D%95