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 |
---|