Markdown¶
约 5186 个字 243 行代码 预计阅读时间 24 分钟
什么是 Markdown¶
Markdown 是一种轻量级标记语言,用于使用纯文本编辑器创建格式化文本。John Gruber 于 2004 年创建了 Markdown,作为一种易于以源代码形式阅读的标记语言。
———— Wikipedia
Markdown 的优势是显著的,它足够轻巧,易读易写;但是,其缺陷也是显著的,那就是初代版本的语法规范性不足,在实现中经常产生偏离作者意图的错误(最重要的是 Markdown 本身没有任何内容会被判定为“语法错误”,所以不会产生任何报错
随着 Markdown 的广泛应用,许多更为细致的语法规范应运而生。其中最为著名的是 CommonMark(标准化
Markdown 的本质
Markdown 的本质是一种标记语言,可以轻松转换成 HTML(映射到 HTML 的子集
———— TonyCrane
Markdown 有什么用 ¶
- 编写文档,支持包括 PDF 在内的多种导出格式
- VSCode + Markdown Preview Enhanced
- Typora(not recommend)
- 搭建网站,极大程度减小 HTML 网页编写工作量
Markdown 语法规范 ¶
这里不再赘述基本的 Markdown 语法,如果想要快速入门,建议阅读 CommonMark 官网的教程。
Markdown 还有许多常用的扩展语法,例如表格、脚注、任务列表、流程图、数学公式等。这些扩展语法在 Markdown Extra、LiaScript 等变体中引入,目前许多主流的 Markdown 编辑器或解析器已经可以识别它们。
Abstract
接下去的篇幅将会列举一些值得关注的 Markdown 语法规范,许多人在使用 Markdown 的时候可能并没有注意到这些规范性问题。
Info
以下规范基于 CommonMark 3.0 标准。
以下示例用的 Markdown 解析器为 commonmark.js
不建议使用的语法 ¶
当你在阅读 Markdown 基本语法的时候,会发现原来同一个效果可以由多种写法实现(比如分割线既可以是 ---
也可以是 ***
这里罗列一下笔者几乎不用的语法:
Setext 式标题、*
_
式分割线、+
*
式无序列表、)
式有序列表、缩进式代码块、< >
自动链接(autolinks_
式强调等。
段落、硬换行与软换行 ¶
在很多时候,人们往往倾向于减少空行的使用(比如写代码的压行
Markdown 空行的定义
空行指的是,没有任何字符,或者只包含空格(U+0020
)和制表符(U+0009
)的一行。
Markdown 中最基本的文本片段是段落(paragraphs
Markdown 源码:
- 这里行末需要两个或更多的空格
解析为 HTML:
渲染效果:
这是第一段
这是第二段
这几种情形的区别还是挺明显的。当你想要另起一段的时候,你需要插入至少一个空行;当你仅仅想要换行的时候,你需要用两个或更多的空格,或者一个反斜杠来实现。那么,软换行呢?
中文排版中不建议使用软换行
这个是真的不建议,除非你清楚地知道你在用软换行做什么。
软换行的关键在于,它在衔接上下文的时候自动补上了一个空格。如果这是西文排版,那这个空格就非常合适;但如果这是中文排版,那这个空格就非常突兀。
关于段落和换行,还有一些值得注意的地方:
- 一定要养成添加空行的习惯,从而确保你的 Markdown 源码在任何地方都能被正确解析。
- 除了段落之外,建议在不同的结构之间都插入空行,比如段落和列表之间、段落和引用之间、段落和代码块之间等。
这些空行并不都是必须的,但是本着宁多不少、一以贯之的原则,还是建议养成添加空行的习惯。
- 除了段落之外,建议在不同的结构之间都插入空行,比如段落和列表之间、段落和引用之间、段落和代码块之间等。
- 是否使用硬换行是个见仁见智的问题。
- 就我个人而言,我不喜欢用空格来换行(太不明显了
) ,也不喜欢用反斜杠来换行(有时候无法正确解析,比如 MkDocs) 。
我会直接在行末加上<br />
。 - 不过更多时候,我还是习惯于只用段落不用换行。笔者认为一段话在 Markdown 源码里就应该同一行写完,这是更符合 Markdown 语法理念的习惯。
- 就我个人而言,我不喜欢用空格来换行(太不明显了
容器:块引用与列表项 ¶
容器(container blocks)是 Markdown 中一个很有意思的块结构,容器内可以使用任何 Markdown 语法和结构(包括嵌套容器本身
容器分为两种:块引用(block quotes)与列表项(list items
块引用的标记 >
和其后的文本之间原则上需要用一个空格隔开,但是这个空格是可以省略的(但建议养成打空格的习惯,原因见下文“如何落入容器”
列表项的标记 -
.
和其后的文本之间原则上需要用一个空格隔开,并且不同于块引用,这个空格是不可以省略的,而且也最好不要插入过多的空格,以免被解析为缩进式代码块(不要插入过多空格这一点对于几乎所有 Markdown 语法都是成立的
列表(list)由同种类型的列表项(list items)组成,列表中的空行会影响列表行距的宽窄,这一点有些复杂(注意观察渲染效果的细微差别,以及 HTML 代码中的 <p>
标签
渲染效果:
- 这是第一个母列表项
- 这是第一个子列表项
- 这是第二个子列表项
- 这是第二个母列表项
Markdown 源码:
解析为 HTML:
渲染效果:
-
这是第一个母列表项
- 这是第一个子列表项
- 这是第二个子列表项
-
这是第二个母列表项
Markdown 源码:
- 这里是属于母列表的一个空行
解析为 HTML:
渲染效果:
- 这是第一个母列表项
-
这是第一个子列表项
-
这是第二个子列表项
-
- 这是第二个母列表项
Markdown 源码:
- 这里是属于子列表的一个空行
解析为 HTML:
关于列表还有一点值得注意,就是如何在不落入上一个列表的情况下,另起一个新的列表(注意仔细看渲染效果的行距差别
如何落入容器 ¶
前面提到,容器可以包含一切 Markdown 语法和结构,但问题的关键在于如何将一段 Markdown 代码判定为落入容器(成为这个容器内的一部分
- 块引用通过
>
来标识某一行落入容器,这是很好判断的。 - 列表项通过适当的缩进来标识某一行落入容器,这里很容易产生歧义。
- 想要落入列表项,必须至少缩进到列表项
-
.
标记后的文本位置,但也不能缩进太多,以免被解析为缩进式代码块。
- 想要落入列表项,必须至少缩进到列表项
听起来有点抽象,我们来看几个例子:
Markdown 源码:
-
这里是一个落入块引用的空行
-
这里是一个没有落入块引用的空行
解析为 HTML:
渲染效果:
这是一句话
这也是一句话
这还是一句话
为什么建议不要省略块引用的空格
通过一个例子来说明:
发生了什么?仔细思考你就会发现,虽然我们有权省略块引用后的空格,但是在判定是否落入容器的时候,列表项可不惯着你,它默认你的 >
后有一个空格,所以要求你在缩进的时候算上这个空格。换言之,直接和 -
后的文本对齐并不总是适用,有时会需要多打一个空格。
反斜杠转义 ¶
在 Markdown 中,所有 ASCII 中的标点符号都可以被 \
转义(backslash-escaped
但是反斜杠转义在内联代码、代码块、自动链接、纯 HTML 中是无效的,在其他语法和结构中则都是有效的,下面给出几个例子:
内联代码 ¶
上文提到反斜杠转义在内联代码中是无效的,那么我们该如何在内联代码中转义 `
呢?
答案就是通过不同数量的反引号 `
来实现反引号自身的转义。假设内联代码中有最多连续反引号 m 个,最少连续反引号 n 个,则我们可以通过连续使用 m+1 个反引号,或者 n-1 个(n>1)反引号来包裹内联代码实现代码中的反引号转义。
强调与强烈强调 ¶
强调 *
与强烈强调 **
在具体使用中并不像你想象的那么简单,我们通过几个例子来看看:
强调和强烈强调会在你意想不到的地方出现错误,而且当你查阅 CommonMark 的时候就会发现,它们的语法真的很复杂且不自然。我的建议是,如果你的强调和强烈强调无法成功渲染,请用 HTML 标签 <em>
和 <strong>
代替它们。
用 HTML 完善 Markdown 语法 ¶
Markdown 的定位决定了它只会有很少一部分简单常用的语法,但是我们可以通过 HTML 来丰富 Markdown 的语法,这为 Markdown 提供了非常好的扩展功能。
这里笔者推荐一些好用的 HTML 语法。
Warning
值得一提的是,在 Markdown 中使用 HTML 语法,理论上来说没有任何问题。但具体到渲染和发布过程,比如以 Material for MkDocs 为例,有时候 HTML 语法在 Markdown 中确实会渲染出问题。不过这种情况很少发生,具体问题具体分析就好。
更好地插入图片 ¶
<div style="text-align: center;"><!-- (1)! -->
<img src="url" alt="alternatetext" style="width: 60%;"><!-- (2)! -->
</div>
- 这里的
center
也可以改成left
和right
;不建议使用<center>
或者<div align>
,它们不被 HTML5 支持。 - 建议像这样用
style
来保护width
和height
,以免被 CSS 干扰。
Markdown 语法不支持图片的大小缩放和位置调整,我们可以通过 HTML 来实现。
文本居中与居右 ¶
- 这里的这里的
center
也可以改成left
和right
。
Markdown 语法也不支持文本的居中与居右,我们可以通过 HTML 来实现。
更多的内联标记 ¶
渲染效果:
这里用于测试删除线。
这里用于测试下划线。
更多的空格与空行 ¶
Markdown(以及浏览器)会折叠连续空格与空行,但有的时候我们真的想要更多的空格与空行。
- 更多空格:HTML 转义字符
 
(半角空格,en-space) 
(全角空格,em-space) 
(通常为全角空格的 $\frac{1}{5}$ 或 $\frac{1}{6}$,thin-space)
- 更多空行:HTML 标签
<br>
注释 ¶
可以用 HTML 语法 <!-- -->
,在 Markdown 中书写注释。
值得注意的是,这种注释写法不仅在 Markdown 源文件中可见,在渲染出的 HTML 文件中也可见,只是不呈现在浏览器上而已;
MkDocs 杂记 ¶
笔者的工具版本
Material for MkDocs:9.4.6 (MkDocs:1.5.3)
GitHub Actions 与版本隐患 ¶
很多人会用 GitHub Actions 来自动化笔记网站的部署,但是很可能忽略了其中的版本隐患。我们首先来看看 .github/workflows/xxx.yml
中的这部分内容:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.x
- run: pip install mkdocs-material # (1)!
- run: mkdocs gh-deploy --force
- 这句命令通过 pip 下载了最新版本的 Material for MkDocs
这里存在的隐患在于,每次运行 GitHub Actions 部署网站的时候,使用的都是最新版本的 Material for MkDocs,但我们在本地通过 mkdocs serve
构建网站的时候所使用的却是本地的 Material for MkDocs,这就意味着如果本地版本没有更新到最新版本,那么本地构建和实际部署就可能出现差异。如果你发现自己的本地构建和实际部署出来的网站有出入,不妨考虑一下可能是版本差异问题导致的。
是否要把软件保持最新版本这个问题,见仁见智。而我们也有相应的两种解决方案:
- 在本地通过
pip install --upgrade --force-reinstall mkdocs-material
来把本地版本保持最新。 - 在
.github/workflows/xxx.yaml
中将相应代码修改为run: pip install mkdocs-material==9.4.6
,从而确保 GitHub Actions 用 9.4.6 版本来部署网站。
提醒一下
Material for MkDocs 中已经包含了相应版本的 MkDocs,如果忘记了这件事的话,上文看起来可能会有些奇怪。
文件路径应该怎么写 ¶
MkDocs 在 1.5.0 的版本中更新了对链接有效性的约束规则,一些不规范的路径写法会被报告出来,比如:
INFO - Doc file 'example.md' contains an absolute link '/foo/bar/', it was left as is. Did you mean 'foo/bar.md'?
INFO - Doc file 'example.md' contains an absolute link '/assets/images/1.jpg', it was left as is. Did you mean '../../assets/images/1.jpg'?
这两个例子之所以报错,按照 MkDocs 的说法,是因为这样的以 /
开头或者结尾的路径写法是 fragile 的。
- 对于以
/
结尾的路径,我觉得确实应该改为.md
结尾,指定文件类型不是一件坏事。 - 对于以
/
开头的路径,我想我会继续这样写。/
在这里代表根目录docs/
,我认为从根目录往下找文件,要比从当前文件所在目录往上找文件更加自然。
比如在插入一张图片的时候,纠结到底是../
还是../../
真的是件很麻烦的事情。
如果你不想看到这些 INFO 提示,可以在 mkdocs.yml
中添加以下内容(尽管 MkDocs 并不推荐这么做
heti 与中西文混排优化 ¶
原则上,汉字与西文字母、数字间使用不多于四分之一个汉字宽的字距或空白。
———— CLReq(中文排版需求)
笔者使用 mkdocs-heti-plugin 来优化中西文混排,在使用这个插件的过程中遇到了一些值得关注的情况:
-
如何让 heti 忽略某部分文本?
可以通过对<span>
、<p>
、<div>
等标签设置style="heti-skip"
实现相应效果。 -
heti 无法渲染页面左右两侧的目录和索引(nav 和 table of contents
) ?
插件本身暂时无法解决该问题,可以手动在中西文混排处插入 
凑合用。
更美观的标题样式 ¶
Material for MkDocs 的标题字重设计得不够合理(对于中文排版而言
在标题前面添加编号是一种很不错的优化方案。手动添加比较繁琐,而且会导致索引(table of contents)也出现编号,影响观感。可以通过设置 CSS 来实现自动编号:
h1 {
counter-reset: h2;
}
h2 {
counter-reset: h3;
}
h3 {
counter-reset: h4;
}
h4 {
counter-reset: h5;
}
h5 {
counter-reset: h6;
}
h2:before {
counter-increment: h2;
content: counter(h2);
margin-right: 0.8rem;
}
h3:before {
counter-increment: h3;
content: counter(h2) "." counter(h3);
margin-right: 0.8rem;
}
h4:before {
counter-increment: h4;
content: counter(h2) "." counter(h3) "." counter(h4);
margin-right: 0.8rem;
}
h5:before {
counter-increment: h5;
content: counter(h2) "." counter(h3) "." counter(h4) "." counter(h5);
margin-right: 0.8rem;
}
h6:before {
counter-increment: h6;
content: counter(h2) "." counter(h3) "." counter(h4) "." counter(h5) "." counter(h6);
margin-right: 0.8rem;
}
表格居中有那么难吗 ¶
暂时没想到合适的实现。安心写纯 HTML 吧。
KaTeX¶
KaTeX 语法可以到官网查询:https://katex.org/docs/supported.html
这里记录一些我使用 KaTeX 时遇到的渲染问题,仅针对个人;如果你使用了和我一样的 KaTeX 配置,或许也能参考:
- 换行符只支持
\cr
,经典的\\
会渲染失败。 - 上标
^
在使用时尽量前后都添加空格,否则在多个上标同时出现时可能会渲染失败;下标_
会更稳定些,但有时候也需要添加空格才能渲染成功。
参考资料 ¶
创建日期: 2024年2月8日 16:17:32