174 lines
6.1 KiB
Python
174 lines
6.1 KiB
Python
|
# Authors: David Goodger, Ueli Schlaepfer, Dmitry Jemerov
|
||
|
# Contact: goodger@users.sourceforge.net
|
||
|
# Revision: $Revision$
|
||
|
# Date: $Date$
|
||
|
# 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', sys.maxint)
|
||
|
self.startnode.parent.remove(self.startnode)
|
||
|
self.update_section_numbers(self.document)
|
||
|
|
||
|
def update_section_numbers(self, node, prefix=(), depth=0):
|
||
|
depth += 1
|
||
|
sectnum = 1
|
||
|
for child in node:
|
||
|
if isinstance(child, nodes.section):
|
||
|
numbers = prefix + (str(sectnum),)
|
||
|
title = child[0]
|
||
|
# Use for spacing:
|
||
|
generated = nodes.generated(
|
||
|
'', '.'.join(numbers) + u'\u00a0' * 3, CLASS='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". 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 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):
|
||
|
topic = nodes.topic(CLASS='contents')
|
||
|
title = self.startnode.details['title']
|
||
|
if self.startnode.details.has_key('local'):
|
||
|
startnode = self.startnode.parent
|
||
|
# @@@ generate an error if the startnode (directive) not at
|
||
|
# section/document top-level? Drag it up until it is?
|
||
|
while not isinstance(startnode, nodes.Structural):
|
||
|
startnode = startnode.parent
|
||
|
else:
|
||
|
startnode = self.document
|
||
|
if not title:
|
||
|
title = nodes.title('', self.language.labels['contents'])
|
||
|
if title:
|
||
|
name = title.astext()
|
||
|
topic += title
|
||
|
else:
|
||
|
name = self.language.labels['contents']
|
||
|
name = utils.normalize_name(name)
|
||
|
if not self.document.has_name(name):
|
||
|
topic['name'] = name
|
||
|
self.document.note_implicit_target(topic)
|
||
|
self.toc_id = topic['id']
|
||
|
if self.startnode.details.has_key('backlinks'):
|
||
|
self.backlinks = self.startnode.details['backlinks']
|
||
|
else:
|
||
|
self.backlinks = self.document.settings.toc_backlinks
|
||
|
contents = self.build_contents(startnode)
|
||
|
if len(contents):
|
||
|
topic += contents
|
||
|
self.startnode.parent.replace(self.startnode, topic)
|
||
|
else:
|
||
|
self.startnode.parent.remove(self.startnode)
|
||
|
|
||
|
def build_contents(self, node, level=0):
|
||
|
level += 1
|
||
|
sections = []
|
||
|
i = len(node) - 1
|
||
|
while i >= 0 and isinstance(node[i], nodes.section):
|
||
|
sections.append(node[i])
|
||
|
i -= 1
|
||
|
sections.reverse()
|
||
|
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['id'],
|
||
|
*entrytext)
|
||
|
ref_id = self.document.set_id(reference)
|
||
|
entry = nodes.paragraph('', '', reference)
|
||
|
item = nodes.list_item('', entry)
|
||
|
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.set_class('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().get_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
|