[3] | 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 |
---|