☰
Quarto 自定义交叉引用(custom crossref)与 LaTeX 浮动环境:基于 8711 回归测试的源码级解析
2026/10/11 12:16:31 网站建设 项目流程
  • 开发工具
  • 文档

【免费下载链接】quarto-cli

Open-source scientific and technical publishing system built on Pandoc.

项目地址:https://gitcode.com/gh_mirrors/qu/quarto-cli
点击查看免费下载

在 Quarto 中,crossref.custom允许你定义自己的浮动对象类别(float environment),为任意内容块分配编号、题注(caption)与引用前缀,并在 PDF/LaTeX 输出中自动生成对应的\newfloat浮动环境。本文以仓库中针对 GitHub issue #8711 编写的 smoke-all 回归测试文档 tests/docs/smoke-all/2024/02/12/8711.pdf.md 为主体,完整拆解其 YAML 配置、正文标记语法、LaTeX 代码注入机制与回归验证方式,帮助读者在自己的文档中安全地使用自定义交叉引用,并理解latex-env命名上的关键限制。

一、文档定位:一份 smoke-all 回归测试用例

该文档位于仓库的冒烟测试目录tests/docs/smoke-all/下,其路径命名遵循"日期 / issue 编号 / 目标格式"的约定:2024/02/12/8711.pdf.md表示这是 2024 年 2 月 12 日针对 issue #8711 的、目标输出为 PDF 的测试用例。该测试由 tests/smoke/smoke-all.test.ts 驱动渲染(tests/timing-for-ci.txt中记录了./smoke/smoke-all.test.ts -- docs/smoke-all/2024/02/12/8711.pdf.md这一调用方式)。

关于 #8711:该 issue 反映的问题是,用户为自定义交叉引用类别命名latex-env: output时,会与 LaTeXlongtable宏包发生命名冲突,导致渲染失败。本测试文档正是围绕这一 Bug 的回归验证:它在同一篇文档中同时放置了knitr::kable(mtcars)渲染出的长表格,以及一个使用自定义 float 类别的输出块,并特意将自定义环境命名为notoutput以避开冲突。

文档开头声明了format: pdf与keep-md: true,即渲染 PDF 的同时保留中间 Markdown,因此这份.pdf.md文件本身就是渲染产物,完整记录了测试输入(YAML 前置元数据 + 正文)的结构。

二、YAML 前置元数据解析:crossref.custom 配置

该测试文档的核心配置如下:

--- title: "Untitled" format: pdf: keep-md: true crossref: custom: - kind: float key: out latex-env: notoutput reference-prefix: Output ---

crossref.custom是一个数组,每一项声明一种自定义交叉引用类别。其字段定义可在 src/resources/schema/document-crossref.yml 中找到,Schema 明确规定kind、reference-prefix、key三个字段为必填项(required: ["kind", "reference-prefix", "key"]),其余字段可选:

字段类型默认值说明
kind枚举float—(必填)交叉引用类别,目前仅支持float
key字符串—(必填)引用标签的前缀,如fig、tbl、lst,本文为out
reference-prefix字符串—(必填)渲染引用时的前缀文本,本文为Output
caption-prefix字符串取reference-prefix题注中使用的前缀;省略时复用reference-prefix
latex-env字符串无LaTeX 输出中自定义 float 环境的名称,本文为notoutput
caption-locationtop/bottom/marginbottom题注相对浮动内容的位置
latex-list-of-file-extension字符串lo+ref-typeLaTeX 收集"列表"条目用的辅助文件扩展名
latex-list-of-description字符串取reference-prefix自定义"列表"标题中对对象的描述文本
space-before-numbering布尔true为false时前缀与编号之间不留空格

测试中只使用了四个字段:kind: float声明浮动类别;key: out使标签以out-开头(正文中对应#out-2);reference-prefix: Output决定引用文本为 "Output";latex-env: notoutput指定 LaTeX 环境名。关于这些字段的完整语义与默认值,Schema 文件 src/resources/schema/document-crossref.yml 是权威依据。

三、正文结构与关键写法

配置之外,正文展示了三种内容形态,分别对应表格、代码输出和自定义浮动块:

1. R 代码单元与长表格

knitr::kable(mtcars)

该单元由 knitr 引擎执行,输出为一个 32 行 × 11 列的管道表格(pipe table),包含mpg、cyl、disp、hp、drat、wt、qsec、vs、am、gear、carb等变量(mtcars是 R 内置数据集,测试仅使用其前几行作为样例)。在 LaTeX 输出中,这份宽表格会由 Pandoc 渲染为longtable环境,这正是与latex-env: output冲突的对象。

2. 自定义浮动块与交叉引用

::: {#out-2} ::: {.cell} ::: {.cell-output .cell-output-stdout}

Call: aov(formula = yield ~ block + N * P + K, data = npk)

Terms: block N P K N:P Residuals Sum of Squares 343.2950 189.2817 8.4017 95.2017 21.2817 218.9033 Deg. of Freedom 5 1 1 1 1 14

Residual standard error: 3.954232 Estimated effects may be unbalanced

::: ::: Sample ANOVA output ::: See @out-2

要点如下:

  • 块引用目标:::: {#out-2}是一个带 ID 的 fenced div,out-前缀对应 YAML 中key: out,2是自动分配或手动指定的编号,最终引用形式为@out-2;
  • 代码输出单元格:内部嵌套.cell与.cell-output .cell-output-stdout,承载一次aov()方差分析的 stdout 输出;
  • 题注文本:div 内 "Sample ANOVA output" 一行作为该浮动块的题注;
  • 引用语法:正文末行See @out-2使用@key语法产生交叉引用,渲染后在 PDF 中呈现为 "Output 2"(前缀取自reference-prefix,编号取自out-2)。

四、源码级原理:crossref.custom 如何被解析

理解上述配置如何生效,需要追踪渲染管线中负责自定义交叉引用的 Lua 过滤器。核心实现在 src/resources/filters/crossref/custom.lua 的initialize_custom_crossref_categories(meta)函数中:

  1. 读取文档元数据meta["crossref"]["custom"],若存在则设置全局标志flags.has_custom_crossrefs = true;
  2. 遍历数组中的每一项,通过映射表把 YAML 字段(kebab-case)转换为内部对象字段(snake_case),例如reference-prefix→name、caption-prefix→prefix、key→ref_type、latex-env→latex_env;
  3. caption-location缺省时补为bottom;prefix缺省时复用name;
  4. 调用add_crossref_category(obj_entry)注册该类别。该函数定义在 src/resources/filters/mainstateinit.lua 中,它将类别插入crossref.categories.all,并重建按ref_type与按name索引的两个查找表(by_ref_type/by_name)。

引用解析阶段由 src/resources/filters/crossref/refs.lua 的resolveRefs()处理:遇到@out-2这类引用时,先从标签前缀反查类别(refType(label)),再按输出格式生成引用文本——LaTeX 输出注入\ref{label}(若类别定义了custom_ref_command则注入对应自定义命令),AsciiDoc 输出生成<<label>>,Typst 输出生成#ref(<label>, ...),其余格式则在 HTML/渲染层手工拼接前缀与编号。前缀文本的格式化逻辑位于 src/resources/filters/crossref/format.lua,其titlePrefix()依据类别的space_before_numbering决定前缀与编号之间是否插入不间断空格。

五、LaTeX 注入细节:newfloat、floatstyle 与题注位置

当输出格式为 PDF/LaTeX 时(src/resources/filters/crossref/custom.lua 中quarto.doc.isFormat("pdf")分支),Quarto 会向生成文档的导言区注入一段 LaTeX 代码,为每个自定义类别声明一个真正的浮动环境:

\usepackage{float} \floatstyle{plain} \@ifundefined{c@chapter}{\newfloat{notoutput}{h}{loout}}{\newfloat{notoutput}{h}{loout}[chapter]} \floatname{notoutput}{Output}

其中:

  • \newfloat{<env>}{h}{<aux>}借助float宏包声明新浮动环境,辅助文件扩展名缺省为lo+ref_type(即loout);若文档有\chapter结构(如 book 项目),则追加[chapter]使编号按章节重置;
  • \floatname{<env>}{Output}设置浮动环境的显示名称,其文本取自reference-prefix(或caption-prefix);
  • 每个类别还会生成一个\listof<env>s命令,用于输出该对象的列表("List of Outputs")。

caption-location选项会改变浮动样式:默认bottom使用\floatstyle{plain};当配置为top时,过滤器额外注入\floatstyle{plaintop}与\restylefloat{<env>},把题注移动到浮动块顶部(src/resources/filters/crossref/custom.lua 中cap_location == "top"分支)。

此外,当space-before-numbering: false且前缀文本含空格时(典型场景如reference-prefix: Table S),过滤器会定义\quarto<reftype>ref这样的自定义引用命令、引入caption宏包并声明\DeclareCaptionLabelFormat,确保题注与引用中前缀和编号之间都不留空格。

六、关键限制:latex-env 不得命名为 "output"

这是 #8711 测试文档最核心的验证点。src/resources/filters/crossref/custom.lua 中有一段专门注释引用 issue 讨论并强制校验:

-- https://github.com/quarto-dev/quarto-cli/issues/8711#issuecomment-1946763141 -- using the name 'output' for a new float environment -- very specifically causes problems with the longtable package, so we disallow it here. if env_name == "output" then fail("The value 'output' is not allowed for the latex-env entry in a custom float environment, as it conflicts with the longtable package. Please choose a different value.") return end

即:longtable宏包内部使用了名为output的环境/计数器,若用户自定义的 float 环境也叫output,生成的 LaTeX 将无法编译。由于 Schema 目前不支持否定式断言,这个限制无法在 src/resources/schema/document-crossref.yml 中静态表达,因此以运行时校验的形式实现在过滤器代码里——遇到该值会直接报错并终止。

这也解释了测试文档为何刻意选择latex-env: notoutput:既验证了自定义 float 类别与longtable表格在同一文档中共存的能力,又规避了被禁用的保留名称。

七、回归验证机制:keep-md 与 ensureFileRegexMatches

同一 issue 在仓库中留有三个验证变体,可对照学习:

文件输出格式验证方式
tests/docs/smoke-all/2024/02/12/8711.pdf.mdpdf+keep-md: true渲染 PDF 并保留中间 Markdown
tests/docs/smoke-all/2024/02/13/8711.pdf.mdlatexensureFileRegexMatches断言存在\begin{longtable}
tests/docs/smoke-all/2024/02/16/8711.pdf.mdlatex+keep-md: true断言同时存在\begin{longtable}与\begin{tabular}

后两个变体在 YAML 中通过_quarto.tests.latex.ensureFileRegexMatches声明了对渲染产物的正则断言:

_quarto: tests: latex: ensureFileRegexMatches: - ["\\\\begin\\{longtable"] - []

第一组正则必须命中,第二组必须为空(不允许匹配)。其含义是:即使文档中存在自定义 float 环境notoutput,knitr::kable(mtcars)生成的宽表仍必须以longtable环境正常输出,不能被自定义浮动环境或float宏包破坏。这正是一份"回归测试"应有的姿态——验证修复(禁用output)之后,原有长表格能力不受影响。同时,02/16 变体还验证了longtable与普通tabular表格在同一文档中并存。

八、实战:在自己的文档中定义自定义 float 类别

参考仓库内更完整的示例 tests/docs/crossrefs/v1.4/custom-categories/diagrams.qmd,一篇文档可以同时注册多个自定义类别:

crossref: custom: - kind: float key: dia reference-prefix: Diagram latex-env: diagram latex-list-of-file-extension: lod - kind: float key: vid reference-prefix: Video latex-env: video latex-list-of-file-extension: lov - kind: float key: supptbl reference-prefix: Table S space-before-numbering: false latex-env: supptbl latex-list-of-file-extension: lost

随后用::: {#dia-1}、::: {#vid-1}、::: {#supptbl-1}包裹任意内容(Mermaid 图、视频 shortcode、表格等),并在正文中用@dia-1、@vid-1、@supptbl-1引用。该示例还演示了space-before-numbering: false与自定义latex-list-of-file-extension的配合用法。

实战中请特别注意以下约束:

  1. kind目前只能是float,Schema 未开放其他类型;
  2. latex-env禁止命名为output(与longtable宏包冲突),命名时避开longtable内部使用的保留字;
  3. 引用标签前缀要全局唯一,key决定了 div ID 与引用标签的命名空间(如dia、vid、supptbl),避免与内置的fig、tbl、lst、eq、sec等冲突;
  4. caption-location、space-before-numbering等选项只在 LaTeX 输出中产生对应的浮动样式调整,HTML 等其他格式的行为以过滤器默认逻辑为准。

总结

crossref.custom是 Quarto 交叉引用体系中面向高级用户的扩展点,它把"自定义对象类别"从 YAML 配置一路打通到 LaTeX 浮动环境的生成与引用解析。本文所依托的 #8711 测试文档不仅演示了完整的配置与正文写法,更以回归测试的形式固化了latex-env命名的边界条件——理解这一限制,能帮助你在实际项目中避免踩坑,并借助ensureFileRegexMatches等测试机制为自己的文档建立可验证的渲染保障。

  • 开发工具
  • 文档

【免费下载链接】quarto-cli

Open-source scientific and technical publishing system built on Pandoc.

项目地址:https://gitcode.com/gh_mirrors/qu/quarto-cli
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询