You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
180 lines
6.8 KiB
180 lines
6.8 KiB
# $Id: parts.py 6073 2009-08-06 12:21:10Z 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 re |
|
import sys |
|
from docutils import nodes, utils |
|
from docutils.transforms import TransformError, 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.maxint |
|
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 |
|
+ u'\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): |
|
try: # let the writer (or output software) build the contents list? |
|
toc_by_writer = self.document.settings.use_latex_toc |
|
except AttributeError: |
|
toc_by_writer = 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 = [] |
|
autonum = 0 |
|
depth = self.startnode.details.get('depth', sys.maxint) |
|
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) |
|
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: |
|
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_interpreted = ignore_node_but_process_children |
|
visit_problematic = ignore_node_but_process_children |
|
visit_reference = ignore_node_but_process_children |
|
visit_target = ignore_node_but_process_children
|
|
|