123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- # $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
|