Provide fallbacks for parser config settings.
[docutils.git] / docutils / docutils / transforms / parts.py
blobc077becafd95a9d9a549261bfb8578d66f8deeb5
1 # $Id$
2 # Authors: David Goodger <goodger@python.org>; Ueli Schlaepfer; Dmitry Jemerov
3 # Copyright: This module has been placed in the public domain.
5 """
6 Transforms related to document parts.
7 """
9 __docformat__ = 'reStructuredText'
12 import re
13 import sys
14 from docutils import nodes, utils
15 from docutils.transforms import TransformError, Transform
18 class SectNum(Transform):
20 """
21 Automatically assigns numbers to the titles of document sections.
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 """
29 default_priority = 710
30 """Should be applied before `Contents`."""
32 def apply(self):
33 self.maxdepth = self.startnode.details.get('depth', None)
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 if self.maxdepth is None:
40 self.maxdepth = sys.maxsize
41 self.update_section_numbers(self.document)
42 else: # store details for eventual section numbering by the writer
43 self.document.settings.sectnum_depth = self.maxdepth
44 self.document.settings.sectnum_start = self.startvalue
45 self.document.settings.sectnum_prefix = self.prefix
46 self.document.settings.sectnum_suffix = self.suffix
48 def update_section_numbers(self, node, prefix=(), depth=0):
49 depth += 1
50 if prefix:
51 sectnum = 1
52 else:
53 sectnum = self.startvalue
54 for child in node:
55 if isinstance(child, nodes.section):
56 numbers = prefix + (str(sectnum),)
57 title = child[0]
58 # Use &nbsp; for spacing:
59 generated = nodes.generated(
60 '', (self.prefix + '.'.join(numbers) + self.suffix
61 + u'\u00a0' * 3),
62 classes=['sectnum'])
63 title.insert(0, generated)
64 title['auto'] = 1
65 if depth < self.maxdepth:
66 self.update_section_numbers(child, numbers, depth)
67 sectnum += 1
70 class Contents(Transform):
72 """
73 This transform generates a table of contents from the entire document tree
74 or from a single branch. It locates "section" elements and builds them
75 into a nested bullet list, which is placed within a "topic" created by the
76 contents directive. A title is either explicitly specified, taken from
77 the appropriate language module, or omitted (local table of contents).
78 The depth may be specified. Two-way references between the table of
79 contents and section titles are generated (requires Writer support).
81 This transform requires a startnode, which contains generation
82 options and provides the location for the generated table of contents (the
83 startnode is replaced by the table of contents "topic").
84 """
86 default_priority = 720
88 def apply(self):
89 # let the writer (or output software) build the contents list?
90 toc_by_writer = getattr(self.document.settings, 'use_latex_toc', False)
91 details = self.startnode.details
92 if 'local' in details:
93 startnode = self.startnode.parent.parent
94 while not (isinstance(startnode, nodes.section)
95 or isinstance(startnode, nodes.document)):
96 # find the ToC root: a direct ancestor of startnode
97 startnode = startnode.parent
98 else:
99 startnode = self.document
100 self.toc_id = self.startnode.parent['ids'][0]
101 if 'backlinks' in details:
102 self.backlinks = details['backlinks']
103 else:
104 self.backlinks = self.document.settings.toc_backlinks
105 if toc_by_writer:
106 # move customization settings to the parent node
107 self.startnode.parent.attributes.update(details)
108 self.startnode.parent.remove(self.startnode)
109 else:
110 contents = self.build_contents(startnode)
111 if len(contents):
112 self.startnode.replace_self(contents)
113 else:
114 self.startnode.parent.parent.remove(self.startnode.parent)
116 def build_contents(self, node, level=0):
117 level += 1
118 sections = [sect for sect in node if isinstance(sect, nodes.section)]
119 entries = []
120 autonum = 0
121 depth = self.startnode.details.get('depth', sys.maxsize)
122 for section in sections:
123 title = section[0]
124 auto = title.get('auto') # May be set by SectNum.
125 entrytext = self.copy_and_filter(title)
126 reference = nodes.reference('', '', refid=section['ids'][0],
127 *entrytext)
128 ref_id = self.document.set_id(reference,
129 suggested_prefix='toc-entry')
130 entry = nodes.paragraph('', '', reference)
131 item = nodes.list_item('', entry)
132 if ( self.backlinks in ('entry', 'top')
133 and title.next_node(nodes.reference) is None):
134 if self.backlinks == 'entry':
135 title['refid'] = ref_id
136 elif self.backlinks == 'top':
137 title['refid'] = self.toc_id
138 if level < depth:
139 subsects = self.build_contents(section, level)
140 item += subsects
141 entries.append(item)
142 if entries:
143 contents = nodes.bullet_list('', *entries)
144 if auto:
145 contents['classes'].append('auto-toc')
146 return contents
147 else:
148 return []
150 def copy_and_filter(self, node):
151 """Return a copy of a title, with references, images, etc. removed."""
152 visitor = ContentsFilter(self.document)
153 node.walkabout(visitor)
154 return visitor.get_entry_text()
157 class ContentsFilter(nodes.TreeCopyVisitor):
159 def get_entry_text(self):
160 return self.get_tree_copy().children
162 def visit_citation_reference(self, node):
163 raise nodes.SkipNode
165 def visit_footnote_reference(self, node):
166 raise nodes.SkipNode
168 def visit_image(self, node):
169 if node.hasattr('alt'):
170 self.parent.append(nodes.Text(node['alt']))
171 raise nodes.SkipNode
173 def ignore_node_but_process_children(self, node):
174 raise nodes.SkipDeparture
176 visit_problematic = ignore_node_but_process_children
177 visit_reference = ignore_node_but_process_children
178 visit_target = ignore_node_but_process_children