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