# .Qmd 和 .Md 的语法差异


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

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

Quarto 官方文档： [Quarto Guide](https://quarto.org/docs/guide/)

## 1. YAML 不只写元信息

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

```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` 的代码块可以真的执行，渲染时把结果塞回文档。

````markdown
```{python}
import pandas as pd

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

如果是 R：

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

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

```yaml
---
engine: jupyter
---
```

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

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

## 3. `#|` 写代码块选项

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

````markdown
```{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 里，单个代码块再用 `#|` 覆盖：

```yaml
---
execute:
  echo: false
  warning: false
---
```

## 4. 行内代码也能执行

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

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

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

渲染后会变成：

```text
圆的半径是 5。
```

R 也是类似写法：

```markdown
圆的半径是 `{r} radius`。
```

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

## 5. Callout 提示框

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

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

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

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

## 展开后查看

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

Quarto 内置的类型有 `note`、`tip`、`warning`、`caution`、`important`。

外观可以在 YAML 里调：

```yaml
---
callout-appearance: simple
callout-icon: false
---
```

## 6. Panel tabset 选项卡

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

````markdown
::: {.panel-tabset}

## Python

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

## R

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

## Bash

```bash
echo "hello"
```

:::
````

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

## 7. 交叉引用

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

图片引用：

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

见 @fig-demo。
```

表格引用：

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

: 成绩表 {#tbl-score}

见 @tbl-score。
```

公式引用：

```markdown
欧拉公式见 @eq-euler。

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

章节引用需要给标题加 `#sec-` ID，并开启章节编号：

```yaml
---
number-sections: true
---
```

```markdown
## 数据处理 {#sec-data}

见 @sec-data。
```

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

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

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

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

import matplotlib.pyplot as plt

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

见 @fig-line。
````

表格也一样：

````markdown
```{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](https://quarto.org/docs/authoring/diagrams.html)

### I. Mermaid

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

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

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

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

时序图也是同样的写法：

````markdown
```{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 选项写成 `//|`。

````markdown
```{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](https://github.com/pandoc-ext/diagram)

先安装 Quarto extension：

```bash
quarto install extension pandoc-ext/diagram
```

然后在 YAML 里启用 filter：

```yaml
---
filters:
  - diagram
---
```

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

```bash
export PLANTUML_BIN=/usr/local/bin/plantuml
```

也可以在 YAML 里写死路径：

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

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

````markdown
如 @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：

````markdown
```{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 的图注和交叉引用。上面的 `label` 以 `fig-` 开头，所以正文里可以写 `@fig-login-puml`。

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

```yaml
---
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` 引入。

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

Mermaid 也可以这样写：

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

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

### V. 尺寸和输出格式

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

需要指定大小时，用 `fig-width` 和 `fig-height`：

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

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

```yaml
---
fig-responsive: false
---
```

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

```bash
quarto install chrome-headless-shell
```

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

```yaml
---
format:
  html:
    mermaid-format: svg
---
```

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

### VI. 主题和源码显示

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

```yaml
---
format:
  html:
    mermaid:
      theme: forest
---
```

Mermaid 内置主题包括 `default`、`dark`、`forest`、`neutral`。

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

````markdown
```{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](https://quarto.org/docs/authoring/citations.html)

先准备一个 BibTeX 文件，比如 `references.bib`：

```bibtex
@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}
}
```

然后在文档头部挂上它：

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

正文里可以这样写：

```markdown
这是括号引用 [@knuth1984]。

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

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

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

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

```markdown
## References

::: {#refs}
:::
```

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

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

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

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

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

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

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

## 11. Div 和 Span 属性

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

块级内容：

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

行内内容：

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

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

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

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

## 11. Shortcode

Quarto 有自己的 shortcode，形式和 Hugo 有点像：

```markdown
{{</* 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`：

````markdown
```{shortcodes=false}
{{</* var version */>}}
```
````

或者加一层花括号转义：

```markdown
{{{</* var version */>}}}
```

## 12. include 复用内容

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

```markdown
{{</* include _intro.qmd */>}}
```

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

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

````markdown
```python
{{</* include _demo.py */>}}
```
````

有两个细节容易踩：

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

## 13. 原始内容块

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

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

LaTeX：

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

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

## 14. 扩展和过滤器

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

```yaml
---
filters:
  - diagram
---
```

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


---

> 作者: Nite  
> URL: https://www.nite07.com/zh-cn/posts/quarto-qmd-syntax/  

