177 lines
6.8 KiB
Python
177 lines
6.8 KiB
Python
# $Id: parts.py 9038 2022-03-05 23:31:46Z milde $
|
|
# Authors: David Goodger <goodger@python.org>; Ueli Schlaepfer; Dmitry Jemerov
|
|
# Copyright: This module has been placed in the public domain.
|
|
|
|
"""
|
|
Transforms related to document parts.
|
|
"""
|
|
|
|
__docformat__ = 'reStructuredText'
|
|
|
|
|
|
import sys
|
|
from docutils import nodes
|
|
from docutils.transforms import Transform
|
|
|
|
|
|
class SectNum(Transform):
|
|
|
|
"""
|
|
Automatically assigns numbers to the titles of document sections.
|
|
|
|
It is possible to limit the maximum section level for which the numbers
|
|
are added. For those sections that are auto-numbered, the "autonum"
|
|
attribute is set, informing the contents table generator that a different
|
|
form of the TOC should be used.
|
|
"""
|
|
|
|
default_priority = 710
|
|
"""Should be applied before `Contents`."""
|
|
|
|
def apply(self):
|
|
self.maxdepth = self.startnode.details.get('depth', None)
|
|
self.startvalue = self.startnode.details.get('start', 1)
|
|
self.prefix = self.startnode.details.get('prefix', '')
|
|
self.suffix = self.startnode.details.get('suffix', '')
|
|
self.startnode.parent.remove(self.startnode)
|
|
if self.document.settings.sectnum_xform:
|
|
if self.maxdepth is None:
|
|
self.maxdepth = sys.maxsize
|
|
self.update_section_numbers(self.document)
|
|
else: # store details for eventual section numbering by the writer
|
|
self.document.settings.sectnum_depth = self.maxdepth
|
|
self.document.settings.sectnum_start = self.startvalue
|
|
self.document.settings.sectnum_prefix = self.prefix
|
|
self.document.settings.sectnum_suffix = self.suffix
|
|
|
|
def update_section_numbers(self, node, prefix=(), depth=0):
|
|
depth += 1
|
|
if prefix:
|
|
sectnum = 1
|
|
else:
|
|
sectnum = self.startvalue
|
|
for child in node:
|
|
if isinstance(child, nodes.section):
|
|
numbers = prefix + (str(sectnum),)
|
|
title = child[0]
|
|
# Use for spacing:
|
|
generated = nodes.generated(
|
|
'', (self.prefix + '.'.join(numbers) + self.suffix
|
|
+ '\u00a0' * 3),
|
|
classes=['sectnum'])
|
|
title.insert(0, generated)
|
|
title['auto'] = 1
|
|
if depth < self.maxdepth:
|
|
self.update_section_numbers(child, numbers, depth)
|
|
sectnum += 1
|
|
|
|
|
|
class Contents(Transform):
|
|
|
|
"""
|
|
This transform generates a table of contents from the entire document tree
|
|
or from a single branch. It locates "section" elements and builds them
|
|
into a nested bullet list, which is placed within a "topic" created by the
|
|
contents directive. A title is either explicitly specified, taken from
|
|
the appropriate language module, or omitted (local table of contents).
|
|
The depth may be specified. Two-way references between the table of
|
|
contents and section titles are generated (requires Writer support).
|
|
|
|
This transform requires a startnode, which contains generation
|
|
options and provides the location for the generated table of contents (the
|
|
startnode is replaced by the table of contents "topic").
|
|
"""
|
|
|
|
default_priority = 720
|
|
|
|
def apply(self):
|
|
# let the writer (or output software) build the contents list?
|
|
toc_by_writer = getattr(self.document.settings, 'use_latex_toc', False)
|
|
details = self.startnode.details
|
|
if 'local' in details:
|
|
startnode = self.startnode.parent.parent
|
|
while not (isinstance(startnode, nodes.section)
|
|
or isinstance(startnode, nodes.document)):
|
|
# find the ToC root: a direct ancestor of startnode
|
|
startnode = startnode.parent
|
|
else:
|
|
startnode = self.document
|
|
self.toc_id = self.startnode.parent['ids'][0]
|
|
if 'backlinks' in details:
|
|
self.backlinks = details['backlinks']
|
|
else:
|
|
self.backlinks = self.document.settings.toc_backlinks
|
|
if toc_by_writer:
|
|
# move customization settings to the parent node
|
|
self.startnode.parent.attributes.update(details)
|
|
self.startnode.parent.remove(self.startnode)
|
|
else:
|
|
contents = self.build_contents(startnode)
|
|
if len(contents):
|
|
self.startnode.replace_self(contents)
|
|
else:
|
|
self.startnode.parent.parent.remove(self.startnode.parent)
|
|
|
|
def build_contents(self, node, level=0):
|
|
level += 1
|
|
sections = [sect for sect in node if isinstance(sect, nodes.section)]
|
|
entries = []
|
|
depth = self.startnode.details.get('depth', sys.maxsize)
|
|
for section in sections:
|
|
title = section[0]
|
|
auto = title.get('auto') # May be set by SectNum.
|
|
entrytext = self.copy_and_filter(title)
|
|
reference = nodes.reference('', '', refid=section['ids'][0],
|
|
*entrytext)
|
|
ref_id = self.document.set_id(reference,
|
|
suggested_prefix='toc-entry')
|
|
entry = nodes.paragraph('', '', reference)
|
|
item = nodes.list_item('', entry)
|
|
if (self.backlinks in ('entry', 'top')
|
|
and title.next_node(nodes.reference) is None):
|
|
if self.backlinks == 'entry':
|
|
title['refid'] = ref_id
|
|
elif self.backlinks == 'top':
|
|
title['refid'] = self.toc_id
|
|
if level < depth:
|
|
subsects = self.build_contents(section, level)
|
|
item += subsects
|
|
entries.append(item)
|
|
if entries:
|
|
contents = nodes.bullet_list('', *entries)
|
|
if auto: # auto-numbered sections
|
|
contents['classes'].append('auto-toc')
|
|
return contents
|
|
else:
|
|
return []
|
|
|
|
def copy_and_filter(self, node):
|
|
"""Return a copy of a title, with references, images, etc. removed."""
|
|
visitor = ContentsFilter(self.document)
|
|
node.walkabout(visitor)
|
|
return visitor.get_entry_text()
|
|
|
|
|
|
class ContentsFilter(nodes.TreeCopyVisitor):
|
|
|
|
def get_entry_text(self):
|
|
return self.get_tree_copy().children
|
|
|
|
def visit_citation_reference(self, node):
|
|
raise nodes.SkipNode
|
|
|
|
def visit_footnote_reference(self, node):
|
|
raise nodes.SkipNode
|
|
|
|
def visit_image(self, node):
|
|
if node.hasattr('alt'):
|
|
self.parent.append(nodes.Text(node['alt']))
|
|
raise nodes.SkipNode
|
|
|
|
def ignore_node_but_process_children(self, node):
|
|
raise nodes.SkipDeparture
|
|
|
|
visit_problematic = ignore_node_but_process_children
|
|
visit_reference = ignore_node_but_process_children
|
|
visit_target = ignore_node_but_process_children
|