| # $Id: parts.py 6073 2009-08-06 12:21:10Z milde $ |
| # Authors: David Goodger <[email protected]>; 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 |