| 1 | # Authors: David Goodger, Ueli Schlaepfer, Dmitry Jemerov | 
|---|
| 2 | # Contact: goodger@users.sourceforge.net | 
|---|
| 3 | # Revision: $Revision: 3909 $ | 
|---|
| 4 | # Date: $Date: 2005-09-26 20:17:31 +0200 (Mon, 26 Sep 2005) $ | 
|---|
| 5 | # Copyright: This module has been placed in the public domain. | 
|---|
| 6 |  | 
|---|
| 7 | """ | 
|---|
| 8 | Transforms related to document parts. | 
|---|
| 9 | """ | 
|---|
| 10 |  | 
|---|
| 11 | __docformat__ = 'reStructuredText' | 
|---|
| 12 |  | 
|---|
| 13 |  | 
|---|
| 14 | import re | 
|---|
| 15 | import sys | 
|---|
| 16 | from docutils import nodes, utils | 
|---|
| 17 | from docutils.transforms import TransformError, Transform | 
|---|
| 18 |  | 
|---|
| 19 |  | 
|---|
| 20 | class SectNum(Transform): | 
|---|
| 21 |  | 
|---|
| 22 | """ | 
|---|
| 23 | Automatically assigns numbers to the titles of document sections. | 
|---|
| 24 |  | 
|---|
| 25 | It is possible to limit the maximum section level for which the numbers | 
|---|
| 26 | are added.  For those sections that are auto-numbered, the "autonum" | 
|---|
| 27 | attribute is set, informing the contents table generator that a different | 
|---|
| 28 | form of the TOC should be used. | 
|---|
| 29 | """ | 
|---|
| 30 |  | 
|---|
| 31 | default_priority = 710 | 
|---|
| 32 | """Should be applied before `Contents`.""" | 
|---|
| 33 |  | 
|---|
| 34 | def apply(self): | 
|---|
| 35 | self.maxdepth = self.startnode.details.get('depth', sys.maxint) | 
|---|
| 36 | self.startvalue = self.startnode.details.get('start', 1) | 
|---|
| 37 | self.prefix = self.startnode.details.get('prefix', '') | 
|---|
| 38 | self.suffix = self.startnode.details.get('suffix', '') | 
|---|
| 39 | self.startnode.parent.remove(self.startnode) | 
|---|
| 40 | if self.document.settings.sectnum_xform: | 
|---|
| 41 | self.update_section_numbers(self.document) | 
|---|
| 42 |  | 
|---|
| 43 | def update_section_numbers(self, node, prefix=(), depth=0): | 
|---|
| 44 | depth += 1 | 
|---|
| 45 | if prefix: | 
|---|
| 46 | sectnum = 1 | 
|---|
| 47 | else: | 
|---|
| 48 | sectnum = self.startvalue | 
|---|
| 49 | for child in node: | 
|---|
| 50 | if isinstance(child, nodes.section): | 
|---|
| 51 | numbers = prefix + (str(sectnum),) | 
|---|
| 52 | title = child[0] | 
|---|
| 53 | # Use   for spacing: | 
|---|
| 54 | generated = nodes.generated( | 
|---|
| 55 | '', (self.prefix + '.'.join(numbers) + self.suffix | 
|---|
| 56 | +  u'\u00a0' * 3), | 
|---|
| 57 | classes=['sectnum']) | 
|---|
| 58 | title.insert(0, generated) | 
|---|
| 59 | title['auto'] = 1 | 
|---|
| 60 | if depth < self.maxdepth: | 
|---|
| 61 | self.update_section_numbers(child, numbers, depth) | 
|---|
| 62 | sectnum += 1 | 
|---|
| 63 |  | 
|---|
| 64 |  | 
|---|
| 65 | class Contents(Transform): | 
|---|
| 66 |  | 
|---|
| 67 | """ | 
|---|
| 68 | This transform generates a table of contents from the entire document tree | 
|---|
| 69 | or from a single branch.  It locates "section" elements and builds them | 
|---|
| 70 | into a nested bullet list, which is placed within a "topic" created by the | 
|---|
| 71 | contents directive.  A title is either explicitly specified, taken from | 
|---|
| 72 | the appropriate language module, or omitted (local table of contents). | 
|---|
| 73 | The depth may be specified.  Two-way references between the table of | 
|---|
| 74 | contents and section titles are generated (requires Writer support). | 
|---|
| 75 |  | 
|---|
| 76 | This transform requires a startnode, which which contains generation | 
|---|
| 77 | options and provides the location for the generated table of contents (the | 
|---|
| 78 | startnode is replaced by the table of contents "topic"). | 
|---|
| 79 | """ | 
|---|
| 80 |  | 
|---|
| 81 | default_priority = 720 | 
|---|
| 82 |  | 
|---|
| 83 | def apply(self): | 
|---|
| 84 | details = self.startnode.details | 
|---|
| 85 | if details.has_key('local'): | 
|---|
| 86 | startnode = self.startnode.parent.parent | 
|---|
| 87 | while not (isinstance(startnode, nodes.section) | 
|---|
| 88 | or isinstance(startnode, nodes.document)): | 
|---|
| 89 | # find the ToC root: a direct ancestor of startnode | 
|---|
| 90 | startnode = startnode.parent | 
|---|
| 91 | else: | 
|---|
| 92 | startnode = self.document | 
|---|
| 93 | self.toc_id = self.startnode.parent['ids'][0] | 
|---|
| 94 | if details.has_key('backlinks'): | 
|---|
| 95 | self.backlinks = details['backlinks'] | 
|---|
| 96 | else: | 
|---|
| 97 | self.backlinks = self.document.settings.toc_backlinks | 
|---|
| 98 | contents = self.build_contents(startnode) | 
|---|
| 99 | if len(contents): | 
|---|
| 100 | self.startnode.replace_self(contents) | 
|---|
| 101 | else: | 
|---|
| 102 | self.startnode.parent.parent.remove(self.startnode.parent) | 
|---|
| 103 |  | 
|---|
| 104 | def build_contents(self, node, level=0): | 
|---|
| 105 | level += 1 | 
|---|
| 106 | sections = [] | 
|---|
| 107 | i = len(node) - 1 | 
|---|
| 108 | while i >= 0 and isinstance(node[i], nodes.section): | 
|---|
| 109 | sections.append(node[i]) | 
|---|
| 110 | i -= 1 | 
|---|
| 111 | sections.reverse() | 
|---|
| 112 | entries = [] | 
|---|
| 113 | autonum = 0 | 
|---|
| 114 | depth = self.startnode.details.get('depth', sys.maxint) | 
|---|
| 115 | for section in sections: | 
|---|
| 116 | title = section[0] | 
|---|
| 117 | auto = title.get('auto')    # May be set by SectNum. | 
|---|
| 118 | entrytext = self.copy_and_filter(title) | 
|---|
| 119 | reference = nodes.reference('', '', refid=section['ids'][0], | 
|---|
| 120 | *entrytext) | 
|---|
| 121 | ref_id = self.document.set_id(reference) | 
|---|
| 122 | entry = nodes.paragraph('', '', reference) | 
|---|
| 123 | item = nodes.list_item('', entry) | 
|---|
| 124 | if ( self.backlinks in ('entry', 'top') | 
|---|
| 125 | and title.next_node(nodes.reference) is None): | 
|---|
| 126 | if self.backlinks == 'entry': | 
|---|
| 127 | title['refid'] = ref_id | 
|---|
| 128 | elif self.backlinks == 'top': | 
|---|
| 129 | title['refid'] = self.toc_id | 
|---|
| 130 | if level < depth: | 
|---|
| 131 | subsects = self.build_contents(section, level) | 
|---|
| 132 | item += subsects | 
|---|
| 133 | entries.append(item) | 
|---|
| 134 | if entries: | 
|---|
| 135 | contents = nodes.bullet_list('', *entries) | 
|---|
| 136 | if auto: | 
|---|
| 137 | contents['classes'].append('auto-toc') | 
|---|
| 138 | return contents | 
|---|
| 139 | else: | 
|---|
| 140 | return [] | 
|---|
| 141 |  | 
|---|
| 142 | def copy_and_filter(self, node): | 
|---|
| 143 | """Return a copy of a title, with references, images, etc. removed.""" | 
|---|
| 144 | visitor = ContentsFilter(self.document) | 
|---|
| 145 | node.walkabout(visitor) | 
|---|
| 146 | return visitor.get_entry_text() | 
|---|
| 147 |  | 
|---|
| 148 |  | 
|---|
| 149 | class ContentsFilter(nodes.TreeCopyVisitor): | 
|---|
| 150 |  | 
|---|
| 151 | def get_entry_text(self): | 
|---|
| 152 | return self.get_tree_copy().children | 
|---|
| 153 |  | 
|---|
| 154 | def visit_citation_reference(self, node): | 
|---|
| 155 | raise nodes.SkipNode | 
|---|
| 156 |  | 
|---|
| 157 | def visit_footnote_reference(self, node): | 
|---|
| 158 | raise nodes.SkipNode | 
|---|
| 159 |  | 
|---|
| 160 | def visit_image(self, node): | 
|---|
| 161 | if node.hasattr('alt'): | 
|---|
| 162 | self.parent.append(nodes.Text(node['alt'])) | 
|---|
| 163 | raise nodes.SkipNode | 
|---|
| 164 |  | 
|---|
| 165 | def ignore_node_but_process_children(self, node): | 
|---|
| 166 | raise nodes.SkipDeparture | 
|---|
| 167 |  | 
|---|
| 168 | visit_interpreted = ignore_node_but_process_children | 
|---|
| 169 | visit_problematic = ignore_node_but_process_children | 
|---|
| 170 | visit_reference = ignore_node_but_process_children | 
|---|
| 171 | visit_target = ignore_node_but_process_children | 
|---|