.Qmd 和 .Md 的语法差异

.qmd 是 Quarto 使用的 Markdown 文件。普通 .md 主要负责排版文本,.qmd 在这个基础上加了可执行代码、输出控制、交叉引用、提示框、短代码等能力。

Quarto 底层用的是 Pandoc Markdown,所以大部分 Markdown 写法都能继续用,比如标题、列表、表格、脚注、公式。真正拉开差距的是下面这些语法。

Quarto 官方文档: Quarto Guide

1. YAML 不只写元信息

普通 .md 里的 front matter 多半只放标题、日期、标签。.qmd 的 YAML 还会控制渲染方式、输出格式和代码执行行为。

---
title: "Quarto 示例"
format:
  html:
    toc: true
    code-fold: true
  pdf:
    documentclass: report
execute:
  echo: true
  warning: false
jupyter: python3
---

这里的 format 决定输出成什么,execute 控制代码块怎么跑。jupyter: python3 会指定 Jupyter kernel。

同一个 .qmd 可以渲染成 HTML、PDF、Word、Reveal.js 幻灯片。普通 .md 通常只是一份静态文本,最多交给外部工具转换。

2. 代码块可以执行

.md 里的代码块大多只是展示。.qmd 的代码块可以真的执行,渲染时把结果塞回文档。

```{python}
import pandas as pd

df = pd.DataFrame({"name": ["Alice", "Bob"], "score": [95, 88]})
df
```

如果是 R:

```{r}
summary(cars)
```

Quarto 会根据代码块自动绑定执行引擎:发现 {r} 通常走 knitr,发现 {python}{julia}{bash} 等一般走 Jupyter。也可以手动指定:

---
engine: jupyter
---

不想执行,只想展示 Quarto 代码块语法,可以用双花括号:

```{{python}}
print("只展示,不执行")
```

3. #| 写代码块选项

.qmd 里最常见的特殊语法是 #|。它放在可执行代码块顶部,用来控制当前 cell 的行为。

```{python}
#| label: fig-sales
#| fig-cap: "月销售额趋势"
#| echo: false
#| warning: false

import matplotlib.pyplot as plt

plt.plot([12, 18, 15, 22])
plt.show()
```

几个常用选项:

选项 用途
echo 是否显示源代码
eval 是否执行代码
output 是否显示执行结果,也可以设为 asis 输出原始 Markdown
warning 是否显示 warning
error 是否把 error 写进文档
include 是否包含代码和结果
label 给代码单元命名,方便引用和调试
fig-cap 图表标题
tbl-cap 表格标题

文档级默认值可以写在 YAML 里,单个代码块再用 #| 覆盖:

---
execute:
  echo: false
  warning: false
---

4. 行内代码也能执行

普通 Markdown 的行内代码只是等宽文本,比如 `code`.qmd 可以在正文里执行表达式。

```{python}
radius = 5
```

圆的半径是 `{python} radius`。

渲染后会变成:

圆的半径是 5。

R 也是类似写法:

圆的半径是 `{r} radius`

这适合把计算结果写进叙述文本。最好提前在代码块里算好变量,行内表达式只取值,别在里面塞复杂逻辑。

5. Callout 提示框

.qmd 支持 callout,用 ::: 包起来。它比普通引用块更适合写提示、警告、补充说明、容易踩坑的地方。

::: {.callout-note}
这是一段普通提示。
:::

::: {.callout-tip title="写作习惯"}
同一份说明可以在 HTML 和 PDF 里保持类似的提示框样式。
:::

::: {.callout-caution collapse="true"}

### 展开后查看

这里放比较长的说明,默认折叠。
:::

Quarto 内置的类型有 notetipwarningcautionimportant

外观可以在 YAML 里调:

---
callout-appearance: simple
callout-icon: false
---

6. Panel tabset 选项卡

写教程时经常要同时给不同语言或不同系统的命令。.qmd 可以用 .panel-tabset 做选项卡。

::: {.panel-tabset}

### Python

```python
print("hello")
```

### R

```r
print("hello")
```

### Bash

```bash
echo "hello"
```

:::

渲染成 HTML 后,读者可以点选不同 tab。普通 .md 也能写多个标题,但没有这种内置交互结构。

7. 交叉引用

Quarto 的交叉引用比普通 Markdown 强很多。图、表、代码清单、公式、章节都可以自动编号,然后用 @id 引用。

图片引用:

![示例图片](image.png){#fig-demo}

@fig-demo。

表格引用:

| name  | score |
| ----- | ----: |
| Alice |    95 |
| Bob   |    88 |

: 成绩表 {#tbl-score}

@tbl-score。

公式引用:

欧拉公式见 @eq-euler。

$$
e^{i\pi} + 1 = 0
$$ {#eq-euler}

章节引用需要给标题加 #sec- ID,并开启章节编号:

---
number-sections: true
---
### 数据处理 {#sec-data}

@sec-data。

ID 前缀要对。图用 fig-,表用 tbl-,公式用 eq-,章节用 sec-。这些前缀是 Quarto 用来判断引用类型的。

8. 图表输出可以直接编号

可执行代码生成的图也能编号。只要给 cell 加 labelfig-cap

```{python}
#| label: fig-line
#| fig-cap: "折线图"

import matplotlib.pyplot as plt

plt.plot([1, 2, 3], [1, 4, 9])
plt.show()
```

@fig-line。

表格也一样:

```{r}
#| label: tbl-cars
#| tbl-cap: "cars 数据集前几行"

knitr::kable(head(cars))
```

@tbl-cars。

这里的 label 要以 fig-tbl- 开头,否则 Quarto 不会把它当成可引用对象。

9. 图示代码块

如果要画流程图、时序图、状态图、甘特图这类图示,.qmd 可以直接写 Mermaid 和 Graphviz。普通 .md 能不能渲染这些图,取决于平台;Quarto 把它们当成可执行 cell 处理。

官方文档: Diagrams

I. Mermaid

Mermaid 用 {mermaid} 代码块。它的 cell 选项不是 #|,而是 %%|。选项必须紧跟在代码块开头下面。

如下方 @fig-flow 所示,部署流程直接写在文档里。

```{mermaid}
%%| label: fig-flow
%%| fig-cap: "自动化部署流程"
flowchart LR
  A[提交代码] --> B{CI 检查}
  B -- 通过 --> C[构建镜像]
  B -- 失败 --> D[通知开发者]
  C --> E[部署]
```

这里的 labelfig- 开头,所以可以用 @fig-flow 交叉引用。fig-cap 会变成图注,编号由 Quarto 处理。

时序图也是同样的写法:

```{mermaid}
%%| label: fig-seq
%%| fig-cap: "登录请求时序"
sequenceDiagram
  participant Browser
  participant API
  participant DB
  Browser->>API: POST /login
  API->>DB: 查询用户
  DB-->>API: 返回结果
  API-->>Browser: 返回 token
```

II. Graphviz

Graphviz 用 {dot} 代码块,适合画拓扑和依赖关系,也适合复杂网络结构。它的 cell 选项写成 //|

```{dot}
//| label: fig-service-map
//| fig-cap: "服务依赖关系"
digraph G {
  rankdir=LR;
  node [shape=box];

  "用户" -> "网关";
  "网关" -> "服务 A";
  "网关" -> "服务 B";
  "服务 A" -> "Redis";
  "服务 B" -> "PostgreSQL";
}
```

@fig-service-map。

Mermaid 更像写流程说明,语法短,适合教程和架构草图。Graphviz 对图布局的控制更细,复杂关系图会更稳。

III. PlantUML

PlantUML 不属于 Quarto 原生 diagram 支持。要在 .qmd 里直接写 PlantUML,可以用 pandoc-ext/diagram 这个 Lua filter。

项目地址: pandoc-ext/diagram

先安装 Quarto extension:

quarto install extension pandoc-ext/diagram

然后在 YAML 里启用 filter:

---
filters:
  - diagram
---

这个 filter 会调用外部程序生成图片。PlantUML 需要系统里能运行 plantuml 命令;常见安装方式还需要 Java。找不到命令时,可以用环境变量指定路径:

export PLANTUML_BIN=/usr/local/bin/plantuml

也可以在 YAML 里写死路径:

---
filters:
  - diagram
diagram:
  engine:
    plantuml:
      execpath: /usr/local/bin/plantuml
---

PlantUML 的代码块写成 {plantuml}。这里的 cell 选项用 PlantUML 注释语法,所以是 '|,不是 #|

@fig-login-puml 所示,这是用 PlantUML 画的登录流程。

```{plantuml}
'| label: fig-login-puml
'| fig-cap: "用户登录时序图"
@startuml
actor User
participant "Web Portal" as Web
database "Auth DB" as DB

User -> Web: 输入凭证
Web -> DB: 验证用户
DB --> Web: 返回结果
Web --> User: 登录成功
@enduml
```

类图也适合用 PlantUML:

```{plantuml}
'| label: fig-class-puml
'| fig-cap: "用户模型类图"
@startuml
package "Domain" {
  class User {
    - username: String
    - passwordHash: String
    + login(): boolean
  }

  class Profile {
    - avatar: String
    - bio: String
  }

  User "1" *-- "1" Profile
}
@enduml
```

pandoc-ext/diagram 生成的图也能走 Quarto 的图注和交叉引用。上面的 labelfig- 开头,所以正文里可以写 @fig-login-puml

默认输出会按目标格式选择,常见的是 SVG 或 PNG,也可能是 PDF。如果某个格式在环境里跑不通,可以在 mime-type 里关掉:

---
filters:
  - diagram
diagram:
  engine:
    plantuml:
      mime-type:
        application/pdf: false
---

这个 filter 会执行本机命令,别拿来处理不可信的 .qmd。仓库 README 也专门提醒了这个安全问题。

Mermaid 和 Graphviz 是 Quarto 原生支持,配置轻。PlantUML 要多装 filter 和命令行工具,但它更适合写 UML 建模,比如类图和用例图。

IV. 外部文件

图很长时,不一定要把所有内容塞进 .qmd。可以把 Mermaid 放到 .mmd,或把 Graphviz 放到 .dot,再用 file 引入。

```{dot}
//| label: fig-kernel
//| fig-cap: "Linux kernel 结构图"
//| file: linux-kernel-diagram.dot
```

Mermaid 也可以这样写:

```{mermaid}
%%| label: fig-state
%%| fig-cap: "订单状态机"
%%| file: order-state.mmd
```

这种写法适合复杂图。.qmd 负责叙述和引用,图示源码单独维护。

V. 尺寸和输出格式

默认情况下,Quarto 会按图示的自然尺寸渲染。HTML 输出里图示会自动响应页面宽度,通常不会撑破正文栏。

需要指定大小时,用 fig-widthfig-height

```{mermaid}
%%| fig-width: 6.5
flowchart LR
  A[开始] --> B[处理]
  B --> C[结束]
```

如果不想让 HTML 图示响应式缩放,可以关掉:

---
fig-responsive: false
---

Mermaid 在不同输出格式里的处理方式不一样。HTML 通常用 Mermaid 的 JavaScript 渲染,gfm 会保留 Mermaid 代码块。PDF、docx 这类打印格式会生成 PNG。打印格式渲染图示时会用 Chrome 或 Edge,没有浏览器环境时可以安装 Quarto 的 headless shell:

quarto install chrome-headless-shell

也可以手动指定 Mermaid 输出格式:

---
format:
  html:
    mermaid-format: svg
---

常用值有 jspng,也可以选 svg。如果目标是 LaTeX 生成的 PDF,svg 可能需要额外工具,默认的 png 更省事。

VI. 主题和源码显示

Mermaid 可以跟随 Quarto 的主题,也可以单独指定 Mermaid 内置主题:

---
format:
  html:
    mermaid:
      theme: forest
---

Mermaid 内置主题包括 defaultdarkforestneutral

图示 cell 默认不在结果里显示源码。如果写教程时想同时展示源码和图,可以打开 echo

```{mermaid}
%%| echo: true
flowchart LR
  A --> B
```

```{dot}
//| echo: true
graph G {
  A -- B
}
```

这部分是 .qmd 比普通 .md 更完整的地方:图示不只是代码块,还能参与编号、引用、输出格式转换和主题控制。配合 pandoc-ext/diagram 这类 filter,还能把 PlantUML 接进同一套写作流程。

10. 文献引用

.qmd 可以直接用 Pandoc 的 citation 语法处理参考文献。最常见的是在 YAML 里指定 bibliography,正文里用 @key[@key] 引用。

官方文档: Citations

先准备一个 BibTeX 文件,比如 references.bib

@book{knuth1984,
  author    = {Donald E. Knuth},
  title     = {The TeXbook},
  year      = {1984},
  publisher = {Addison-Wesley}
}

@book{wickham2015,
  author    = {Hadley Wickham},
  title     = {R Packages},
  year      = {2015},
  publisher = {O'Reilly Media}
}

然后在文档头部挂上它:

---
title: "Quarto 引用示例"
bibliography: references.bib
---

正文里可以这样写:

这是括号引用 [@knuth1984]。

这是带定位信息的写法 [@knuth1984, pp. 33-35]。

也可以一次引多个条目 [@knuth1984; @wickham2015]。

@knuth1984 这类写法会直接把作者名带进正文。

如果不手动指定位置,Quarto 会把参考文献列表放到文末。想自己控制位置,可以放一个 refs 容器:

### References

::: {#refs}
:::

样式默认走 Chicago author-date。要改成别的格式,比如 Nature、APA,可以加 CSL 文件:

---
bibliography: references.bib
csl: nature.csl
---

有时候正文不想引用某条文献,但又想把它放进参考文献列表,可以用 nocite

---
bibliography: references.bib
nocite: |
  @knuth1984
---

想把 .bib 里的所有条目都列出来也行:

---
bibliography: references.bib
nocite: |
  @*
---

这套东西普通 .md 一般做不到,除非外部平台自己再接一层 Pandoc 或别的文献处理器。

11. Div 和 Span 属性

.qmd 继承了 Pandoc Markdown 的属性语法,可以给块级内容或行内内容加 class、id、属性。

块级内容:

::: {#example .border}
这段内容有一个 ID 和一个 class。
:::

行内内容:

[这几个字会被标记]{.mark}

这个语法在普通 Markdown 渲染器里不一定能用。Quarto 会把它交给 Pandoc 处理,再根据输出格式生成对应结果。

属性顺序也有要求:先写 ID,再写 class,最后写键值对。

[正确]{#id .class key="value"}
[不稳]{.class key="value" #id}

11. Shortcode

Quarto 有自己的 shortcode,形式和 Hugo 有点像:

{{< meta title >}}
{{< var version >}}
{{< pagebreak >}}
{{< kbd Ctrl-C >}}
{{< video https://www.youtube.com/embed/xxx >}}

常用的几个:

shortcode 用途
meta 读取文档 metadata
var 读取 _variables.yml 里的变量
env 读取环境变量
include 插入另一个文件内容
embed 嵌入 notebook 里的 cell
pagebreak 插入分页
kbd 显示快捷键
video 插入视频

如果文章就是在讲 shortcode,代码块里可能会被 Quarto 误处理。可以用 shortcodes=false

```{shortcodes=false}
{{< var version >}}
```

或者加一层花括号转义:

{{{< var version >}}}

12. include 复用内容

.qmd 可以用 include 把另一个文件插进来,适合复用说明、准备代码、统一声明。

{{< include _intro.qmd >}}

被 include 的文件通常用下划线开头,比如 _intro.qmd。这样项目渲染时不会把它当成独立页面。

也可以把脚本内容塞进代码块:

```python
{{< include _demo.py >}}
```

有两个细节容易踩:

  • include 要单独占一行,前后留空行。
  • include 近似于复制粘贴。被插入文件里的相对路径,会按主文件位置解析。

13. 原始内容块

.qmd 可以直接写某个输出格式的原始内容。比如 HTML:

```{=html}
<iframe src="https://quarto.org/" width="600" height="400"></iframe>
```

LaTeX:

```{=latex}
\newpage
```

这类内容只对对应格式生效。写多格式文档时要小心,HTML 片段放到 PDF 输出里通常没意义。

14. 扩展和过滤器

Quarto 可以挂 Lua filter 或 extension。它们通常写在 YAML 里:

---
filters:
  - diagram
---

扩展可以改 AST、增加输出能力、封装 shortcode。普通 .md 本身没有这种项目级扩展机制,除非外部构建系统另行处理。