ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

深入解析 Sphinx 图表自动编号(numfig)机制:从测试用例到源码实现

深入解析 Sphinx 图表自动编号(numfig)机制:从测试用例到源码实现 文档开发工具【免费下载链接】sphinxThe Sphinx documentation generator项目地址https://gitcode.com/gh_mirrors/sp/sphinx点击查看免费下载本文以 Sphinx 仓库中的 numfig 功能测试文档 tests/roots/test-numfig/foo.rst 为核心骨架系统讲解numfig、numfig_format、numfig_secnum_depth三个配置项如何为插图figure、表格table和代码块code-block自动生成章节号.序号格式的编号以及:numref:角色如何将这些编号引入正文交叉引用。读完本文你将掌握 numfig 的完整配置方法、编号规律、引用格式含%s旧风格与{number}/{name}新风格、常见告警的成因以及编号在 Sphinx 源码中的生成链路。numfig 功能概览让图表和代码块自动编号在长篇技术文档中如图 X 所示见表格 X这类交叉引用必须与插图/表格的实际编号保持一致。手动维护编号容易出错而 Sphinx 的 numfignumbering of figures图表编号功能可以自动完成这项工作当配置开启后所有**带标题caption**的figure、table和code-block节点会被自动编号编号随文档章节结构展开正文中则通过:numref:角色引用这些编号。从仓库中的功能演进记录doc/changes/1.3.rst可以看到numfig 功能自 Sphinx 1.3 引入最初就定义了numfig、numfig_secnum_depth、numfig_format三个配置项和numref角色后续版本又为其补充了自定义可枚举节点支持add_enumerable_node()、LaTeX 构建器对编号深度的支持、方程编号math_numfig等能力。在官方配置文档 doc/usage/configuration.rst 中这一组配置被统一归入 Options for figure numbering 小节。测试场景全景test-numfig 目录中的文档结构在分析 foo.rst 之前先看它所处的测试环境。目录 tests/roots/test-numfig 是一个完整的 Sphinx 测试项目包含index.rst项目首页包含一个带:numbered:选项的 toctree以及直接位于首页的图表和大量:numref:引用foo.rst本文的主角作为 toctree 中的第 1 章bar.rst第 2 章并嵌套引用 bazbaz.rst位于 bar 下的子文档conf.py仅配置了exclude_patterns [_build]其余均用测试代码在运行时通过confoverrides注入。toctree 采用:numbered:选项意味着文档章节会获得数字编号index 的首页本身不参与章节编号foo 是第 1 章含 1.1、1.2 等小节bar 是第 2 章。这正是 numfig 生成1.1、2.3这类复合编号的前提——章节编号体系必须先存在图表编号才能挂靠到章节号上。三个核心配置项numfig / numfig_format / numfig_secnum_depth这三个配置项的默认值定义在 sphinx/config.py 中第 275277 行numfig: _Opt(False, env, frozenset((bool,))), numfig_secnum_depth: _Opt(1, env, frozenset((int, types.NoneType))), # numfig_format will be initialized in init_numfig_format() numfig_format: _Opt({}, env, frozenset((dict,))),numfig总开关类型bool默认值False。当为True时带标题的 figures、tables、code-blocks 会被自动编号同时启用:numref:角色。目前只有 HTML 与 LaTeX 构建器遵循该选项详见 doc/usage/configuration.rst 中.. confval:: numfig一节。需要特别注意的是LaTeX 构建器无论该选项是否开启都会分配编号。numfig_format编号文本的显示格式类型dict[str, str]默认值{}。字典的键为figure、table、code-block和section值为格式化字符串其中的%s会被替换为具体编号。默认值并非{}本身而是在配置初始化阶段填充的。在 sphinx/config.py 的init_numfig_format()中可以看到完整默认格式def init_numfig_format(app: Sphinx, config: Config) - None: Initialize :confval:numfig_format. numfig_format { section: _(Section %s), figure: _(Fig. %s), table: _(Table %s), code-block: _(Listing %s), } # override default labels by configuration numfig_format.update(config.numfig_format) config.numfig_format numfig_format也就是说最终生效的numfig_format是默认字典 用户覆盖。你只需要在 conf.py 中写出想改写的键即可例如numfig_format { figure: Figure %s, table: Table %s, code-block: Listing %s, }注意%s与字符串格式化使用同一套机制因此格式串中若出现两个及以上%s如Fig %s %s或缺失%s/{number}占位符都会在构建时触发invalid numfig_format告警详见下文源码分析。numfig_secnum_depth编号跟随章节的深度类型int默认值1。取0时图表按全局顺序从1开始连续编号1、2、3…不挂靠章节号取1时编号为x.1、x.2…其中x是一级章节号若文档没有顶层章节则不添加前缀取2时编号为x.y.1、x.y.2…其中x为章节号、y为子章节号若图表直接位于某个一级章节之下不嵌套在二级标题中则不出现y.前缀若根本没有顶层章节则不加任何前缀其他更大的正整数依此类推。该深度仅对参与 toctree:numbered:编号体系的章节有意义从 Sphinx 1.7 起LaTeX 构建器在numfig True时也遵守此设置。逐行剖析 foo.rst编号如何随章节展开现在进入核心文档 tests/roots/test-numfig/foo.rst。它的正文结构如下Foo第 1 章标签fooFoo A小节 1.1标签foo_aFoo A1三级标题 1.1.1标签foo_a1Foo B小节 1.2标签foo_bFoo B1三级标题 1.2.1标签foo_b1文档在每一级标题下都放置了带标题caption的figure、csv-table和code-block并用注释标注了预期的编号.. _foo: Foo .. figure:: rimg.png should be Fig.1.1 .. csv-table:: should be Table 1.1 :header-rows: 0 hello,world .. code-block:: python :caption: should be List 1.1 print(hello world)当numfig True且numfig_secnum_depth 1默认值时编号规则如下位于Foo章节下的 figure 编号为Fig. 1.1章节号 1 该章内图表序号 1表格为Table 1.1代码块为Listing 1.1注意numfig_format中code-block的默认文案是Listingfoo.rst 注释里的 List 只是测试意图描述实际渲染为 ListingFoo A小节下的两个 figure 依次为Fig. 1.2、Fig. 1.3小节不改变前缀因为numfig_secnum_depth 1只挂靠一级章节号表格为Table 1.2、Table 1.3代码块为Listing 1.2、Listing 1.3Foo B1下的 figure、表格、代码块分别编号为Fig. 1.4、Table 1.4、Listing 1.4。同样的规律在 bar.rst第 2 章中表现为Fig. 2.1、Table 2.1、Listing 2.1等而 baz.rst 中的图表格代码块编号为Fig. 2.2、Table 2.2、Listing 2.2。关键点编号并不以文档文件为单位而是以toctree 编号体系中的章节为单位。bar.rst 中位于:toctree:指令之后的 figure即 bar 章节内但处于嵌套 toctree 之后的那个编号为 Fig. 2.3紧接其后的Bar B小节下的为 Fig. 2.4中间的 2.2 由嵌套的 baz.rst 占据。:numref: 角色在正文中交叉引用编号:numref:角色用于在正文中引用这些自动编号的对象其语法为:numref:标签或带显式文案的 :numref:自定义文案 标签。完整的用法样例集中在 tests/roots/test-numfig/index.rst* Fig.1 is :numref:fig1 * Fig.2.2 is :numref:Figure%s fig22 * Table.1 is :numref:table-1 * Table.2.2 is :numref:Table:%s table22 * List.1 is :numref:CODE_1 * List.2.2 is :numref:Code-%s CODE22 * Section.1 is :numref:foo * Section.2.1 is :numref:bar_a * Unnumbered section is :numref:index * Invalid numfig_format 01: :numref:invalid fig1 * Invalid numfig_format 02: :numref:Fig %s %s fig1 * Fig.1 is :numref:Fig.{number} {name} fig1 * Section.1 is :numref:Sect.{number} {name} foo对应测试tests/test_builders/test_build_html_numfig.py中的 XPath 断言给出了每种写法的实际输出写法输出说明:numref:fig1Fig. 1使用figure键的默认格式Fig. %s:numref:Figure%s Figure2.2显式文案 %s占位符:numref:table-1Table 1使用table键的默认格式:numref:Table:%s Table:2.2显式文案 %s:numref:CODE_1Listing 1使用code-block键的默认格式:numref:Code-%s Code-2.2显式文案 %s:numref:fooSection 1章节引用使用section键的默认格式:numref:bar_aSection 2.1二级章节的编号:numref:Fig.{number} {name} Fig.1 should be Fig.1新风格{number}/{name}模板:numref:Sect.{number} {name} Sect.1 Foo{name}替换为章节标题 Foo其中值得展开说明的两点旧风格%s与新风格{number}/{name}。源码 sphinx/domains/std/init.py 的_resolve_numref_xref()中格式化逻辑同时支持两种风格若标题中包含{name}或{number}则按新风格用str.format()展开{number}为编号、{name}为目标对象的标题或 caption 文本否则回退到%s旧风格。这解释了为什么:numref:Fig.{number} {name} 会渲染成 Fig.1 should be Fig.1——{name}取的是 index.rst 中 fig1 这个 figure 的 caption 文本 should be Fig.1。对 section 的引用不要求开启 numfig。源码中有一行关键判断if figtype ! section and env.config.numfig is False——只有非章节对象在 numfig 关闭时才会告警numfig is disabled. :numref: is ignored.章节引用不受此限制这与 doc/changes/1.7.rst 中 Dont require numfig to use :numref: on sections 的变更记录一致。两种典型失败场景引用未参与编号的文档如首页 index 本身由于章节编号体系来自 toctree 的:numbered:首页并没有章节号此时构建会产生告警Failed to create a cross reference. Any number is not assigned: index使用不合法的numfig_format如invalid缺少%s/{number}或Fig %s %s含两个占位符构建会输出invalid numfig_format告警。源码级原理编号从哪里来AutoNumbering 变换为图表节点登记隐式目标在 sphinx/transforms/init.py 中定义了AutoNumbering变换优先级 210class AutoNumbering(SphinxTransform): Register IDs of tables, figures and literal_blocks to assign numbers. default_priority 210 def apply(self, **kwargs: Any) - None: domain: StandardDomain self.env.domains.standard_domain for node in self.document.findall(nodes.Element): if ( domain.is_enumerable_node(node) and domain.get_numfig_title(node) is not None and node[ids] [] ): self.document.note_implicit_target(node)它遍历文档树凡是可枚举节点figure、带 caption 的 literal_block 等且带有标题、但没有显式 ID 的都会自动登记一个隐式目标implicit target。这就是为什么 foo.rst 中那些没有写.. _label:的 figure/csv-table/code-block 也能被后续编号和引用机制识别。需要给某个图表一个人类可读的引用名时可在指令前显式添加标签如 baz.rst 中的.. _fig22:、.. _table22:、.. _CODE22:。StandardDomain解析 :numref: 的完整链路:numref:角色的解析入口同样位于 sphinx/domains/std/init.py 的_resolve_numref_xref()核心流程为在labels/anonlabels中查找目标标签得到所在文档与节点 ID通过get_enumerable_node_type()判定目标节点类型section、figure、table、code-block之一若不可枚举则放弃若非章节且numfig关闭输出numfig is disabled告警并原样返回调用get_fignumber()从环境中读取编号若拿不到编号则输出Failed to create a cross reference. Any number is not assigned告警按numfig_format中的格式串%s旧风格或{number}/{name}新风格生成最终引用文本格式不合法时输出invalid numfig_format告警。get_fignumber编号的读取与回退编号本身来自构建环境。在get_fignumber()中可以看到章节编号从env.toc_secnumberstoctree 收集器生成的章节编号表读取图表编号则从env.toc_fignumbers[docname][figtype][figure_id]读取。值得注意的一个回退逻辑当目标章节没有显式锚点时会尝试使用env.toc_secnumbers[docname].get()取该文档第一个标题的编号而若目标节点存在于孤立文档orphaned document中KeyError/IndexError会被包装为ValueError抛出最终表现为上述未能创建交叉引用告警。编号表从何而来toctree 收集器toc_fignumbers与toc_secnumbers由 sphinx/environment/collectors/toctree.py 在构建阶段填充。这也再次印证了一个使用要点只有被:numbered:toctree 纳入编号体系的章节其下的图表才能获得章节号.序号格式的编号首页或孤立文档中的图表要么拿不到章节前缀要么无法被:numref:正确引用。测试如何验证这些行为tests/test_builders/test_build_html_numfig.py 是针对 numfig 的专项测试覆盖了 foo.rst 所在项目的多种构建变体是理解该功能行为的最佳佐证默认关闭时的告警test_numfig_disabled_warn断言构建输出包含index.rst:47: WARNING: numfig is disabled. :numref: is ignored.启用后的编号输出test_numfig_with_numbered_toctree通过 XPath 断言foo.html中caption-number依次为Fig. 1.1、Fig. 1.2、Fig. 1.3、Fig. 1.4表格为Table 1.1Table 1.4代码块为Listing 1.1Listing 1.4与 foo.rst 注释中的预期完全对应自定义 numfig_formattest_numfig_with_prefix通过confoverrides注入{figure: Figure:%s, table: Tab_%s, code-block: Code-%s, section: SECTION-%s}断言输出变为Figure:2.1、Tab_2.1、Code-2.1等numfig_secnum_depth 2test_numfig_with_secnum_depth断言foo.html中出现Fig. 1.1.1、Fig. 1.1.2、Fig. 1.2.1Foo A1 下的图表多出一级前缀而index.html首页图表仍是Fig. 1去掉:numbered:后test_numfig_without_numbered_toctree通过正则删除 toctree 的:numbered:选项后重建验证了章节引用失效告警的出现singlehtml 构建器test_numfig_with_singlehtml验证单页 HTML 输出下编号行为保持一致。常见问题与边界情况小结综合 foo.rst、index.rst、相关源码与测试可以把 numfig 使用中的要点归纳如下忘记开启numfig True图表不会自动编号正文中的:numref:会产生numfig is disabled. :numref: is ignored.告警章节引用除外章节没有参与:numbered:toctree图表只能获得全局连续编号numfig_secnum_depth 0的效果章节级:numref:无法生成编号numfig_format格式串必须恰好包含一个%s或{number}缺失或多占位符都会触发invalid numfig_format告警{name}占位符仅当引用的目标有标题/caption 时才可用否则会告警the link has no captionLaTeX 构建器始终为图表分配编号且从 1.7 起遵守numfig_secnum_depth自定义可枚举类型如需让自定义指令参与编号可借助app.add_enumerable_node()注册见 tests/roots/test-add_enumerable_node/enumerable_node.py 的示例。掌握以上规律后你可以在自己的 Sphinx 项目中通过 conf.py 中的三个配置项与:numref:角色构建一套全自动的图表编号与交叉引用体系彻底告别手工维护图 1.1 / 表 2.3这类编号的烦恼。赞分享文档开发工具【免费下载链接】sphinxThe Sphinx documentation generator项目地址https://gitcode.com/gh_mirrors/sp/sphinx点击查看免费下载相关推荐Easy-Vibe 负载均衡与网关原理从单机瓶颈到高可用架构的完整工程实践Easy Vibe 负载均衡与网关原理从单机瓶颈到高可用架构的完整工程实践 本指南源自 Easy Vibe 项目 基础设施与运维附录 https://link文档开发工具Sphinx LaTeX 输出中的图/表/代码块自动编号numfig机制与 manual/howto 测试验证Sphinx LaTeX 输出中的图/表/代码块自动编号numfig机制与 manual/howto 测试验证 Sphinx 从 1.3 版本起通过 num文档开发工具Sphinx 图表编号机制深度解析numfig 配置、:numref: 交叉引用与章节前缀编号实战Sphinx 图表编号机制深度解析 numfig 配置、 :numref: 交叉引用与章节前缀编号实战 导读 本文围绕 Sphinx 文档生成器的自动编号体系文档开发工具上一篇Agent Zero 工具系统开发契约与 DOX 文档规范以 tools/AGENTS.md 为纲解读工具实现、响应契约与验证流程下一篇Bytebase Schema Editor 前端架构迁移从 Vue 3 到 React 的增量迁移定义与设计创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表