在带有数学公式的markdown文档里的交叉引用

用Markdown对数学概念做笔记最有效的地方就是交叉引用. 一个数学概念或者定理往往是建立在一大堆其他数学概念的基础上. 而这些概念又是由其他更基础的概念定义. 可以说数学之美很大一部分是不管多复杂的概念都可以逆向追溯到特别基础的一大堆概念上. 犹如用简单的钢管和螺丝造出极其复杂的机器一样.

但这种特性导致数学学习如果不连贯, 新概念就几乎没法读懂. 因为任何一个更基础的信息忘记了都没法理解新读到的内容. 但交叉引用可以帮助自己快速找到前面的基础概念. 大大加速了我这种不连贯读书的速度. 这篇就是介绍一下我是如何实现交叉引用.

我的环境用的hexo + mathjax + hexo-renderer-pandoc.

交叉引用包括两个部分, 一个是为被引用的文本添加锚点(即跳转的目的地), 另一个是在引用的地方指定想要引用的锚点(即跳转的源位置, 显示为超链接).

添加锚点

为标题的加锚点

在标题后面直接加\(\{\)#Anchor\(\}\), 比如:

1
### 标题1 {#Anchor1}

注意: 普通文本中直接连着输入{#, 渲染器会报错, 除非花括号前面是方括号. 见后面一节为普通文本加锚点.

上面的markdown脚本被渲染成html时, 锚点变为html节点的id:

1
<h3 id="Anchor1">标题1</h3>

在最新版的hexo+pandoc这个方法已经不能直接用了, 参考hexo 5.0更新引起的渲染错误.

但pandoc会为标题自动添加锚点, 锚点名字就是标题内容, 空格用横杠-代替. 如果想自定义锚点名, 可以参考用HTML插入锚点

为普通文本加锚点

用方括号加上大括号的格式: \([\)文本内容\(]\{\)#Anchor2\(\}\). 比如下面一段话:

1
It's called the [*universe*]{#Universe} or domain.

实际渲染的html脚本如下.

1
<p>It's called the <span id="Universe"><em>universe</em></span> or domain.</p>

这里要注意, 非标题的普通文本里面如果前面没有方括号, 直接连着写{#,渲染器会报错.

在最新版的hexo这个方法已经不能直接用, 参考hexo 5.0更新引起的渲染错误, 解决方案参考用HTML插入锚点

数学公式的符号加锚点

数学公式中加锚点需要使用\cssId #1 #2, 例如下面的公式:

1
$$\cssId{Overlinev}{\overline{v}}:\overline{S}\to\{F,T\}$$

渲染成: \[\cssId{Overlinev}{\overline{v}}:\overline{S}\to\{F,T\}\]

实际的html脚本如下

1
<mjx-container class="MathJax CtxtMenu_Attached_0" jax="CHTML" display="true" role="presentation" tabindex="0" ctxtmenu_counter="5" style="font-size: 122.1%; position: relative;"><mjx-math display="true" class="MJX-TEX" aria-hidden="true" style="margin-left: 0px; margin-right: 0px;"><mjx-mover id="Overlinev"><mjx-over style="padding-bottom: 0.2em; margin-bottom: -0.385em;"><mjx-mo class="mjx-n" size="s"><mjx-stretchy-h class="mjx-cAF" style="width: 0.686em;"><mjx-ext><mjx-c></mjx-c></mjx-ext></mjx-stretchy-h></mjx-mo></mjx-over><mjx-base><mjx-mi class="mjx-i"><mjx-c class="mjx-c1D463 TEX-I"></mjx-c></mjx-mi></mjx-base></mjx-mover><mjx-mo class="mjx-n" space="4"><mjx-c class="mjx-c3A"></mjx-c></mjx-mo><mjx-mover space="4"><mjx-over style="padding-bottom: 0.2em; padding-left: 0.024em; margin-bottom: -0.385em;"><mjx-mo class="mjx-n" size="s"><mjx-stretchy-h class="mjx-cAF" style="width: 0.912em;"><mjx-ext><mjx-c></mjx-c></mjx-ext></mjx-stretchy-h></mjx-mo></mjx-over><mjx-base><mjx-mi class="mjx-i"><mjx-c class="mjx-c1D446 TEX-I"></mjx-c></mjx-mi></mjx-base></mjx-mover><mjx-mo class="mjx-n" space="4"><mjx-c class="mjx-c2192"></mjx-c></mjx-mo><mjx-mo class="mjx-n" space="4"><mjx-c class="mjx-c7B"></mjx-c></mjx-mo><mjx-mi class="mjx-i"><mjx-c class="mjx-c1D439 TEX-I"></mjx-c></mjx-mi><mjx-mo class="mjx-n"><mjx-c class="mjx-c2C"></mjx-c></mjx-mo><mjx-mi class="mjx-i" space="2"><mjx-c class="mjx-c1D447 TEX-I"></mjx-c></mjx-mi><mjx-mo class="mjx-n"><mjx-c class="mjx-c7D"></mjx-c></mjx-mo></mjx-math><mjx-assistive-mml role="presentation" unselectable="on" display="block"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><mover id="Overlinev"><mi>v</mi><mo accent="false">¯</mo></mover><mo>:</mo><mover><mi>S</mi><mo accent="false">¯</mo></mover><mo accent="false" stretchy="false"></mo><mo fence="false" stretchy="false">{</mo><mi>F</mi><mo>,</mo><mi>T</mi><mo fence="false" stretchy="false">}</mo></math></mjx-assistive-mml></mjx-container>
注意其中的<mjx-mover id="Overlinev">.

引用锚点

文本引用

使用标准的markdown超链接语法即可: 文本.

注意如果引用同一文本中的锚点, 可以在括号中直接写(#Anchor), 比如下面的markdown:

1
[点击这里](#Universe), 会跳转到本页前面的一个锚点.

被渲染成如下的超链接:

点击这里, 会跳转到本页前面的一个锚点.

如果引用同一网站的其他网页的锚点, 可以用相对地址, 比如:

1
[点击这里](/9cbcca7a/#image), 可以跳转到本博客的其他页的一个锚点.

被渲染成如下的超链接:

点击这里, 可以跳转到本博客的其他页的一个锚点.

公式中的引用

使用\href{ <url> } #1

这里有个陷阱, 如果url也想引用同一文本中的锚点, 不能直接在{后面直接接#, 需要加一个空格.比如

1
\href{ #Overlinev}{\overline{v}}=F(x)

将被渲染成: \[ \href{ #Overlinev}{\overline{v}}=F(x) \]

注意其中的\(\overline{v}\)是可以点击跳转的.\(\dashv\)

hexo 5.0 更新引起的渲染问题

在把hexo更新成5.0+以后, 我在渲染过去的Post时, 遇到了下面的问题.

1
2
3
4
5
6
7
8
Error: expected end of comment, got end of file
at Object._prettifyError (C:\Users\86186\lab\nodejs\blog_example\node_modules\nunjucks\src\lib.js:36:11)
at Template.render (C:\Users\86186\lab\nodejs\blog_example\node_modules\nunjucks\src\environment.js:538:21)
at Environment.renderString (C:\Users\86186\lab\nodejs\blog_example\node_modules\nunjucks\src\environment.js:380:17)
at C:\Users\86186\lab\nodejs\blog_example\node_modules\hexo\lib\extend\tag.js:236:16
at tryCatcher (C:\Users\86186\lab\nodejs\blog_example\node_modules\bluebird\js\release\util.js:16:23)
at Function.Promise.fromNode.Promise.fromCallback (C:\Users\86186\lab\nodejs\blog_example\node_modules\bluebird\js\release\promise.js:209:30)
....

这个问题是由于nunjucks模板会把{# ... }当成模板注释来渲染, 根据这个官方回答 -- issue4658

有几个个解决方案:

  1. 对于单页, 可以添加Front-matter disableNunjucks: true
  2. 对于整个blog, 可以在基于hexo-renderer-marked的配置, 在_config.yml里添加:
    1
    2
    marked:
    disableNunjucks: false
  3. 当然也可以修改代码node_modules/hexo/lib/hexo/post.js的390行:
    1
    if (typeof data.disableNunjucks === 'boolean') disableNunjucks = data.disableNunjucks;
    改成:
    1
    disableNunjucks = true;

用HTML插入锚点

Nunjucks被禁掉以后, 有个副作用, 就是Hexo提供的Tag Plugin都无法使用了. 所以实在需要这些额外的标签, 可以用加一句HTML来打锚点.

1
<span id="anchor_name"></span>