如何在Sphinx中为不同内容区域配置自定义章节编号?
First off, I get why you're frustrated—Sphinx's default section numbering is pretty rigid, and the toctree system doesn't make it obvious how to customize per-section number formats. Let's break down how to extend the TocTreeCollector properly to get that i/ii, 1.1, A/B numbering setup you want.
Core Problem Recap
Your initial attempt with a _to_alpha function in _walk_toc only modified page-level section numbers, not the numbered entries in the toctree itself. That's because the assign_section_numbers method in TocTreeCollector handles the global number stack for all toctree entries, and we need to hook into that logic to apply format-specific rules per toctree.
Step-by-Step Solution
1. Create a Custom Sphinx Extension
We'll build a small extension that:
- Adds a new
:format:option to thetoctreedirective - Replaces the default
TocTreeCollectorwith a custom version that respects the format option - Implements number conversion functions for roman numerals, arabic, and uppercase letters
Create a file named custom_toc_numbering.py in your Sphinx project root:
import string from sphinx.environment.collectors.toctree import TocTreeCollector from sphinx.util.roman import toRoman from sphinx.application import Sphinx from sphinx.directives.other import TocTree as BaseTocTree # Number format conversion functions def _to_lower_roman(numstack): return [toRoman(num).lower() for num in numstack] def _to_arabic(numstack): return [str(num) for num in numstack] def _to_upper_letters(numstack): # For appendices: convert top-level numbers to letters, keep sub-levels as arabic # Example: [1, 2] → ['A', '2'] → "A.2" return [ string.ascii_uppercase[num - 1] if i == 0 else str(num) for i, num in enumerate(numstack) ] # Custom TocTreeCollector with format-aware numbering class CustomTocTreeCollector(TocTreeCollector): def assign_section_numbers(self, app: Sphinx, doctree): # First run the default numbering logic to build the base number stack super().assign_section_numbers(app, doctree) # Track which documents belong to which toctree format doc_format_map = {} # Parse all toctree nodes to map docs to their format for toctree_node in doctree.findall(lambda n: n.tagname == 'toctree'): toc_format = toctree_node.get('format', 'arabic') # Map each entry in the toctree to its format for entry in toctree_node['entries']: docname = entry[1] if isinstance(entry, tuple) else entry if docname: doc_format_map[docname] = toc_format # Update section numbers for all documents based on their format for docname, toc in app.env.tocs.items(): if docname not in doc_format_map: continue # Skip docs not in a formatted toctree target_format = doc_format_map[docname] # Traverse all sections in the document's TOC for section in toc.findall(lambda n: n.tagname == 'section'): if 'number' not in section: continue numstack = section['number'] # Apply the correct format conversion if target_format == 'lowercase-roman': section['number'] = _to_lower_roman(numstack) elif target_format == 'uppercase-letters': section['number'] = _to_upper_letters(numstack) # Arabic is default, no change needed # Update the display text for the number section['number_text'] = '.'.join(section['number']) # Update the global toc_numbers registry (used for sidebar/refs) for docname, numstack in app.env.toc_numbers.items(): if docname in doc_format_map: target_format = doc_format_map[docname] if target_format == 'lowercase-roman': app.env.toc_numbers[docname] = _to_lower_roman(numstack) elif target_format == 'uppercase-letters': app.env.toc_numbers[docname] = _to_upper_letters(numstack) # Extend the default TocTree directive to accept the :format: option class CustomTocTree(BaseTocTree): option_spec = dict(BaseTocTree.option_spec) option_spec['format'] = str def setup(app: Sphinx): # Replace the default TocTreeCollector with our custom one app.env.collectors['toctree'] = CustomTocTreeCollector(app) # Register our extended TocTree directive app.add_directive('toctree', CustomTocTree) return { 'version': '0.1', 'parallel_read_safe': True, 'parallel_write_safe': True, }
2. Enable the Extension in conf.py
Add the extension to your Sphinx configuration:
extensions = [ # ... your existing extensions 'custom_toc_numbering', ]
3. Use the New :format: Option in Your Toctrees
Now you can define different number formats for each section of your docs:
.. toctree:: :caption: 前言 :format: lowercase-roman :numbered: foreword/intro foreword/overview .. toctree:: :caption: 主内容 :format: arabic :numbered: chapter1/index chapter2/index .. toctree:: :caption: 附录 :format: uppercase-letters :numbered: appendix/a appendix/b
Key Notes
- Roman Numerals: We use Sphinx's built-in
toRomanutility to avoid reinventing the wheel. - Multi-Level Numbering: The
_to_upper_lettersfunction keeps sub-level numbers as arabic (e.g., A.1, A.2) which is standard for appendices. Adjust this function if you need a different behavior. - Sidebar/Reference Updates: The custom collector updates both the document section numbers and the global
toc_numbersregistry, so numbered references and sidebar TOCs will show the correct formatted numbers.
内容的提问来源于stack exchange,提问作者nowox

