comparison env/lib/python3.9/site-packages/docutils/transforms/universal.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
1 # $Id: universal.py 8393 2019-09-18 10:13:00Z milde $
2 # -*- coding: utf-8 -*-
3 # Authors: David Goodger <goodger@python.org>; Ueli Schlaepfer; Günter Milde
4 # Maintainer: docutils-develop@lists.sourceforge.net
5 # Copyright: This module has been placed in the public domain.
6
7 """
8 Transforms needed by most or all documents:
9
10 - `Decorations`: Generate a document's header & footer.
11 - `Messages`: Placement of system messages stored in
12 `nodes.document.transform_messages`.
13 - `TestMessages`: Like `Messages`, used on test runs.
14 - `FinalReferences`: Resolve remaining references.
15 """
16
17 __docformat__ = 'reStructuredText'
18
19 import re
20 import sys
21 import time
22 from docutils import nodes, utils
23 from docutils.transforms import TransformError, Transform
24 from docutils.utils import smartquotes
25
26
27 if sys.version_info >= (3, 0):
28 unicode = str # noqa
29
30
31 class Decorations(Transform):
32
33 """
34 Populate a document's decoration element (header, footer).
35 """
36
37 default_priority = 820
38
39 def apply(self):
40 header_nodes = self.generate_header()
41 if header_nodes:
42 decoration = self.document.get_decoration()
43 header = decoration.get_header()
44 header.extend(header_nodes)
45 footer_nodes = self.generate_footer()
46 if footer_nodes:
47 decoration = self.document.get_decoration()
48 footer = decoration.get_footer()
49 footer.extend(footer_nodes)
50
51 def generate_header(self):
52 return None
53
54 def generate_footer(self):
55 # @@@ Text is hard-coded for now.
56 # Should be made dynamic (language-dependent).
57 # @@@ Use timestamp from the `SOURCE_DATE_EPOCH`_ environment variable
58 # for the datestamp?
59 # See https://sourceforge.net/p/docutils/patches/132/
60 # and https://reproducible-builds.org/specs/source-date-epoch/
61 settings = self.document.settings
62 if settings.generator or settings.datestamp or settings.source_link \
63 or settings.source_url:
64 text = []
65 if settings.source_link and settings._source \
66 or settings.source_url:
67 if settings.source_url:
68 source = settings.source_url
69 else:
70 source = utils.relative_path(settings._destination,
71 settings._source)
72 text.extend([
73 nodes.reference('', 'View document source',
74 refuri=source),
75 nodes.Text('.\n')])
76 if settings.datestamp:
77 datestamp = time.strftime(settings.datestamp, time.gmtime())
78 text.append(nodes.Text('Generated on: ' + datestamp + '.\n'))
79 if settings.generator:
80 text.extend([
81 nodes.Text('Generated by '),
82 nodes.reference('', 'Docutils', refuri=
83 'http://docutils.sourceforge.net/'),
84 nodes.Text(' from '),
85 nodes.reference('', 'reStructuredText', refuri='http://'
86 'docutils.sourceforge.net/rst.html'),
87 nodes.Text(' source.\n')])
88 return [nodes.paragraph('', '', *text)]
89 else:
90 return None
91
92
93 class ExposeInternals(Transform):
94
95 """
96 Expose internal attributes if ``expose_internals`` setting is set.
97 """
98
99 default_priority = 840
100
101 def not_Text(self, node):
102 return not isinstance(node, nodes.Text)
103
104 def apply(self):
105 if self.document.settings.expose_internals:
106 for node in self.document.traverse(self.not_Text):
107 for att in self.document.settings.expose_internals:
108 value = getattr(node, att, None)
109 if value is not None:
110 node['internal:' + att] = value
111
112
113 class Messages(Transform):
114
115 """
116 Place any system messages generated after parsing into a dedicated section
117 of the document.
118 """
119
120 default_priority = 860
121
122 def apply(self):
123 unfiltered = self.document.transform_messages
124 threshold = self.document.reporter.report_level
125 messages = []
126 for msg in unfiltered:
127 if msg['level'] >= threshold and not msg.parent:
128 messages.append(msg)
129 if messages:
130 section = nodes.section(classes=['system-messages'])
131 # @@@ get this from the language module?
132 section += nodes.title('', 'Docutils System Messages')
133 section += messages
134 self.document.transform_messages[:] = []
135 self.document += section
136
137
138 class FilterMessages(Transform):
139
140 """
141 Remove system messages below verbosity threshold.
142 """
143
144 default_priority = 870
145
146 def apply(self):
147 for node in tuple(self.document.traverse(nodes.system_message)):
148 if node['level'] < self.document.reporter.report_level:
149 node.parent.remove(node)
150
151
152 class TestMessages(Transform):
153
154 """
155 Append all post-parse system messages to the end of the document.
156
157 Used for testing purposes.
158 """
159
160 default_priority = 880
161
162 def apply(self):
163 for msg in self.document.transform_messages:
164 if not msg.parent:
165 self.document += msg
166
167
168 class StripComments(Transform):
169
170 """
171 Remove comment elements from the document tree (only if the
172 ``strip_comments`` setting is enabled).
173 """
174
175 default_priority = 740
176
177 def apply(self):
178 if self.document.settings.strip_comments:
179 for node in tuple(self.document.traverse(nodes.comment)):
180 node.parent.remove(node)
181
182
183 class StripClassesAndElements(Transform):
184
185 """
186 Remove from the document tree all elements with classes in
187 `self.document.settings.strip_elements_with_classes` and all "classes"
188 attribute values in `self.document.settings.strip_classes`.
189 """
190
191 default_priority = 420
192
193 def apply(self):
194 if self.document.settings.strip_elements_with_classes:
195 self.strip_elements = set(
196 self.document.settings.strip_elements_with_classes)
197 # Iterate over a tuple as removing the current node
198 # corrupts the iterator returned by `traverse`:
199 for node in tuple(self.document.traverse(self.check_classes)):
200 node.parent.remove(node)
201
202 if not self.document.settings.strip_classes:
203 return
204 strip_classes = self.document.settings.strip_classes
205 for node in self.document.traverse(nodes.Element):
206 for class_value in strip_classes:
207 try:
208 node['classes'].remove(class_value)
209 except ValueError:
210 pass
211
212 def check_classes(self, node):
213 if not isinstance(node, nodes.Element):
214 return False
215 for class_value in node['classes'][:]:
216 if class_value in self.strip_elements:
217 return True
218 return False
219
220
221 class SmartQuotes(Transform):
222
223 """
224 Replace ASCII quotation marks with typographic form.
225
226 Also replace multiple dashes with em-dash/en-dash characters.
227 """
228
229 default_priority = 850
230
231 nodes_to_skip = (nodes.FixedTextElement, nodes.Special)
232 """Do not apply "smartquotes" to instances of these block-level nodes."""
233
234 literal_nodes = (nodes.FixedTextElement, nodes.Special,
235 nodes.image, nodes.literal, nodes.math,
236 nodes.raw, nodes.problematic)
237 """Do apply smartquotes to instances of these inline nodes."""
238
239 smartquotes_action = 'qDe'
240 """Setting to select smartquote transformations.
241
242 The default 'qDe' educates normal quote characters: (", '),
243 em- and en-dashes (---, --) and ellipses (...).
244 """
245
246 def __init__(self, document, startnode):
247 Transform.__init__(self, document, startnode=startnode)
248 self.unsupported_languages = set()
249
250 def get_tokens(self, txtnodes):
251 # A generator that yields ``(texttype, nodetext)`` tuples for a list
252 # of "Text" nodes (interface to ``smartquotes.educate_tokens()``).
253 for node in txtnodes:
254 if (isinstance(node.parent, self.literal_nodes)
255 or isinstance(node.parent.parent, self.literal_nodes)):
256 yield ('literal', unicode(node))
257 else:
258 # SmartQuotes uses backslash escapes instead of null-escapes
259 txt = re.sub('(?<=\x00)([-\\\'".`])', r'\\\1', unicode(node))
260 yield ('plain', txt)
261
262 def apply(self):
263 smart_quotes = self.document.settings.smart_quotes
264 if not smart_quotes:
265 return
266 try:
267 alternative = smart_quotes.startswith('alt')
268 except AttributeError:
269 alternative = False
270
271 document_language = self.document.settings.language_code
272 lc_smartquotes = self.document.settings.smartquotes_locales
273 if lc_smartquotes:
274 smartquotes.smartchars.quotes.update(dict(lc_smartquotes))
275
276 # "Educate" quotes in normal text. Handle each block of text
277 # (TextElement node) as a unit to keep context around inline nodes:
278 for node in self.document.traverse(nodes.TextElement):
279 # skip preformatted text blocks and special elements:
280 if isinstance(node, self.nodes_to_skip):
281 continue
282 # nested TextElements are not "block-level" elements:
283 if isinstance(node.parent, nodes.TextElement):
284 continue
285
286 # list of text nodes in the "text block":
287 txtnodes = [txtnode for txtnode in node.traverse(nodes.Text)
288 if not isinstance(txtnode.parent,
289 nodes.option_string)]
290
291 # language: use typographical quotes for language "lang"
292 lang = node.get_language_code(document_language)
293 # use alternative form if `smart-quotes` setting starts with "alt":
294 if alternative:
295 if '-x-altquot' in lang:
296 lang = lang.replace('-x-altquot', '')
297 else:
298 lang += '-x-altquot'
299 # drop unsupported subtags:
300 for tag in utils.normalize_language_tag(lang):
301 if tag in smartquotes.smartchars.quotes:
302 lang = tag
303 break
304 else: # language not supported: (keep ASCII quotes)
305 if lang not in self.unsupported_languages:
306 self.document.reporter.warning('No smart quotes '
307 'defined for language "%s".'%lang, base_node=node)
308 self.unsupported_languages.add(lang)
309 lang = ''
310
311 # Iterator educating quotes in plain text:
312 # (see "utils/smartquotes.py" for the attribute setting)
313 teacher = smartquotes.educate_tokens(self.get_tokens(txtnodes),
314 attr=self.smartquotes_action, language=lang)
315
316 for txtnode, newtext in zip(txtnodes, teacher):
317 txtnode.parent.replace(txtnode, nodes.Text(newtext,
318 rawsource=txtnode.rawsource))
319
320 self.unsupported_languages = set() # reset