2 # Authors: David Goodger <goodger@python.org>; Ueli Schlaepfer
3 # Copyright: This module has been placed in the public domain.
6 Transforms needed by most or all documents:
8 - `Decorations`: Generate a document's header & footer.
9 - `Messages`: Placement of system messages stored in
10 `nodes.document.transform_messages`.
11 - `TestMessages`: Like `Messages`, used on test runs.
12 - `FinalReferences`: Resolve remaining references.
15 __docformat__
= 'reStructuredText'
20 from docutils
import nodes
, utils
21 from docutils
.transforms
import TransformError
, Transform
22 from docutils
.utils
import smartquotes
24 class Decorations(Transform
):
27 Populate a document's decoration element (header, footer).
30 default_priority
= 820
33 header_nodes
= self
.generate_header()
35 decoration
= self
.document
.get_decoration()
36 header
= decoration
.get_header()
37 header
.extend(header_nodes
)
38 footer_nodes
= self
.generate_footer()
40 decoration
= self
.document
.get_decoration()
41 footer
= decoration
.get_footer()
42 footer
.extend(footer_nodes
)
44 def generate_header(self
):
47 def generate_footer(self
):
48 # @@@ Text is hard-coded for now.
49 # Should be made dynamic (language-dependent).
50 settings
= self
.document
.settings
51 if settings
.generator
or settings
.datestamp
or settings
.source_link \
52 or settings
.source_url
:
54 if settings
.source_link
and settings
._source \
55 or settings
.source_url
:
56 if settings
.source_url
:
57 source
= settings
.source_url
59 source
= utils
.relative_path(settings
._destination
,
62 nodes
.reference('', 'View document source',
65 if settings
.datestamp
:
66 datestamp
= time
.strftime(settings
.datestamp
, time
.gmtime())
67 text
.append(nodes
.Text('Generated on: ' + datestamp
+ '.\n'))
68 if settings
.generator
:
70 nodes
.Text('Generated by '),
71 nodes
.reference('', 'Docutils', refuri
=
72 'http://docutils.sourceforge.net/'),
74 nodes
.reference('', 'reStructuredText', refuri
='http://'
75 'docutils.sourceforge.net/rst.html'),
76 nodes
.Text(' source.\n')])
77 return [nodes
.paragraph('', '', *text
)]
82 class ExposeInternals(Transform
):
85 Expose internal attributes if ``expose_internals`` setting is set.
88 default_priority
= 840
90 def not_Text(self
, node
):
91 return not isinstance(node
, nodes
.Text
)
94 if self
.document
.settings
.expose_internals
:
95 for node
in self
.document
.traverse(self
.not_Text
):
96 for att
in self
.document
.settings
.expose_internals
:
97 value
= getattr(node
, att
, None)
99 node
['internal:' + att
] = value
102 class Messages(Transform
):
105 Place any system messages generated after parsing into a dedicated section
109 default_priority
= 860
112 unfiltered
= self
.document
.transform_messages
113 threshold
= self
.document
.reporter
.report_level
115 for msg
in unfiltered
:
116 if msg
['level'] >= threshold
and not msg
.parent
:
119 section
= nodes
.section(classes
=['system-messages'])
120 # @@@ get this from the language module?
121 section
+= nodes
.title('', 'Docutils System Messages')
123 self
.document
.transform_messages
[:] = []
124 self
.document
+= section
127 class FilterMessages(Transform
):
130 Remove system messages below verbosity threshold.
133 default_priority
= 870
136 for node
in self
.document
.traverse(nodes
.system_message
):
137 if node
['level'] < self
.document
.reporter
.report_level
:
138 node
.parent
.remove(node
)
141 class TestMessages(Transform
):
144 Append all post-parse system messages to the end of the document.
146 Used for testing purposes.
149 default_priority
= 880
152 for msg
in self
.document
.transform_messages
:
157 class StripComments(Transform
):
160 Remove comment elements from the document tree (only if the
161 ``strip_comments`` setting is enabled).
164 default_priority
= 740
167 if self
.document
.settings
.strip_comments
:
168 for node
in self
.document
.traverse(nodes
.comment
):
169 node
.parent
.remove(node
)
172 class StripClassesAndElements(Transform
):
175 Remove from the document tree all elements with classes in
176 `self.document.settings.strip_elements_with_classes` and all "classes"
177 attribute values in `self.document.settings.strip_classes`.
180 default_priority
= 420
183 if not (self
.document
.settings
.strip_elements_with_classes
184 or self
.document
.settings
.strip_classes
):
186 # prepare dicts for lookup (not sets, for Python 2.2 compatibility):
187 self
.strip_elements
= dict(
189 for key
in (self
.document
.settings
.strip_elements_with_classes
191 self
.strip_classes
= dict(
192 [(key
, None) for key
in (self
.document
.settings
.strip_classes
194 for node
in self
.document
.traverse(self
.check_classes
):
195 node
.parent
.remove(node
)
197 def check_classes(self
, node
):
198 if isinstance(node
, nodes
.Element
):
199 for class_value
in node
['classes'][:]:
200 if class_value
in self
.strip_classes
:
201 node
['classes'].remove(class_value
)
202 if class_value
in self
.strip_elements
:
205 class SmartQuotes(Transform
):
208 Replace ASCII quotation marks with typographic form.
210 Also replace multiple dashes with em-dash/en-dash characters.
213 default_priority
= 850
215 texttype
= {True: 'literal',
219 if self
.document
.settings
.smart_quotes
is False:
222 # "Educate" quotes in normal text. Handle each block of text
223 # (TextElement node) as a unit to keep context around inline nodes:
224 for node
in self
.document
.traverse(nodes
.TextElement
):
225 # skip preformatted text blocks and special elements:
226 if isinstance(node
, (nodes
.FixedTextElement
, nodes
.Special
)):
228 # nested TextElements are not "block-level" elements:
229 if isinstance(node
.parent
, nodes
.TextElement
):
232 # list of text nodes in the "text block":
233 txtnodes
= [txtnode
for txtnode
in node
.traverse(nodes
.Text
)
234 if not isinstance(txtnode
.parent
,
235 nodes
.option_string
)]
236 # smartquotes.educate_tokens() iterates over
237 # ``(texttype, nodetext)`` tuples. `texttype` is "literal"
238 # or "plain" where "literal" text is not changed:
239 tokens
= [(self
.texttype
[isinstance(txtnode
.parent
,
244 nodes
.problematic
))],
245 txtnode
.astext()) for txtnode
in txtnodes
]
247 # Iterator educating quotes in plain text
248 # 2 : set all, using old school en- and em- dash shortcuts
249 teacher
= smartquotes
.educate_tokens(tokens
, attr
='2')
251 for txtnode
, newtext
in zip(txtnodes
, teacher
):
252 txtnode
.parent
.replace(txtnode
, nodes
.Text(newtext
))