Mercurial > repos > shellac > sam_consensus_v3
comparison env/lib/python3.9/site-packages/docutils/writers/html4css1/__init__.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: __init__.py 8242 2018-11-29 08:11:18Z milde $ | |
2 # Author: David Goodger | |
3 # Maintainer: docutils-develop@lists.sourceforge.net | |
4 # Copyright: This module has been placed in the public domain. | |
5 | |
6 """ | |
7 Simple HyperText Markup Language document tree Writer. | |
8 | |
9 The output conforms to the XHTML version 1.0 Transitional DTD | |
10 (*almost* strict). The output contains a minimum of formatting | |
11 information. The cascading style sheet "html4css1.css" is required | |
12 for proper viewing with a modern graphical browser. | |
13 """ | |
14 | |
15 __docformat__ = 'reStructuredText' | |
16 | |
17 import os.path | |
18 import docutils | |
19 from docutils import frontend, nodes, writers, io | |
20 from docutils.transforms import writer_aux | |
21 from docutils.writers import _html_base | |
22 | |
23 class Writer(writers._html_base.Writer): | |
24 | |
25 supported = ('html', 'html4', 'html4css1', 'xhtml', 'xhtml10') | |
26 """Formats this writer supports.""" | |
27 | |
28 default_stylesheets = ['html4css1.css'] | |
29 default_stylesheet_dirs = ['.', | |
30 os.path.abspath(os.path.dirname(__file__)), | |
31 # for math.css | |
32 os.path.abspath(os.path.join( | |
33 os.path.dirname(os.path.dirname(__file__)), 'html5_polyglot')) | |
34 ] | |
35 | |
36 default_template = 'template.txt' | |
37 default_template_path = os.path.join( | |
38 os.path.dirname(os.path.abspath(__file__)), default_template) | |
39 | |
40 settings_spec = ( | |
41 'HTML-Specific Options', | |
42 None, | |
43 (('Specify the template file (UTF-8 encoded). Default is "%s".' | |
44 % default_template_path, | |
45 ['--template'], | |
46 {'default': default_template_path, 'metavar': '<file>'}), | |
47 ('Comma separated list of stylesheet URLs. ' | |
48 'Overrides previous --stylesheet and --stylesheet-path settings.', | |
49 ['--stylesheet'], | |
50 {'metavar': '<URL[,URL,...]>', 'overrides': 'stylesheet_path', | |
51 'validator': frontend.validate_comma_separated_list}), | |
52 ('Comma separated list of stylesheet paths. ' | |
53 'Relative paths are expanded if a matching file is found in ' | |
54 'the --stylesheet-dirs. With --link-stylesheet, ' | |
55 'the path is rewritten relative to the output HTML file. ' | |
56 'Default: "%s"' % ','.join(default_stylesheets), | |
57 ['--stylesheet-path'], | |
58 {'metavar': '<file[,file,...]>', 'overrides': 'stylesheet', | |
59 'validator': frontend.validate_comma_separated_list, | |
60 'default': default_stylesheets}), | |
61 ('Embed the stylesheet(s) in the output HTML file. The stylesheet ' | |
62 'files must be accessible during processing. This is the default.', | |
63 ['--embed-stylesheet'], | |
64 {'default': 1, 'action': 'store_true', | |
65 'validator': frontend.validate_boolean}), | |
66 ('Link to the stylesheet(s) in the output HTML file. ' | |
67 'Default: embed stylesheets.', | |
68 ['--link-stylesheet'], | |
69 {'dest': 'embed_stylesheet', 'action': 'store_false'}), | |
70 ('Comma-separated list of directories where stylesheets are found. ' | |
71 'Used by --stylesheet-path when expanding relative path arguments. ' | |
72 'Default: "%s"' % default_stylesheet_dirs, | |
73 ['--stylesheet-dirs'], | |
74 {'metavar': '<dir[,dir,...]>', | |
75 'validator': frontend.validate_comma_separated_list, | |
76 'default': default_stylesheet_dirs}), | |
77 ('Specify the initial header level. Default is 1 for "<h1>". ' | |
78 'Does not affect document title & subtitle (see --no-doc-title).', | |
79 ['--initial-header-level'], | |
80 {'choices': '1 2 3 4 5 6'.split(), 'default': '1', | |
81 'metavar': '<level>'}), | |
82 ('Specify the maximum width (in characters) for one-column field ' | |
83 'names. Longer field names will span an entire row of the table ' | |
84 'used to render the field list. Default is 14 characters. ' | |
85 'Use 0 for "no limit".', | |
86 ['--field-name-limit'], | |
87 {'default': 14, 'metavar': '<level>', | |
88 'validator': frontend.validate_nonnegative_int}), | |
89 ('Specify the maximum width (in characters) for options in option ' | |
90 'lists. Longer options will span an entire row of the table used ' | |
91 'to render the option list. Default is 14 characters. ' | |
92 'Use 0 for "no limit".', | |
93 ['--option-limit'], | |
94 {'default': 14, 'metavar': '<level>', | |
95 'validator': frontend.validate_nonnegative_int}), | |
96 ('Format for footnote references: one of "superscript" or ' | |
97 '"brackets". Default is "brackets".', | |
98 ['--footnote-references'], | |
99 {'choices': ['superscript', 'brackets'], 'default': 'brackets', | |
100 'metavar': '<format>', | |
101 'overrides': 'trim_footnote_reference_space'}), | |
102 ('Format for block quote attributions: one of "dash" (em-dash ' | |
103 'prefix), "parentheses"/"parens", or "none". Default is "dash".', | |
104 ['--attribution'], | |
105 {'choices': ['dash', 'parentheses', 'parens', 'none'], | |
106 'default': 'dash', 'metavar': '<format>'}), | |
107 ('Remove extra vertical whitespace between items of "simple" bullet ' | |
108 'lists and enumerated lists. Default: enabled.', | |
109 ['--compact-lists'], | |
110 {'default': 1, 'action': 'store_true', | |
111 'validator': frontend.validate_boolean}), | |
112 ('Disable compact simple bullet and enumerated lists.', | |
113 ['--no-compact-lists'], | |
114 {'dest': 'compact_lists', 'action': 'store_false'}), | |
115 ('Remove extra vertical whitespace between items of simple field ' | |
116 'lists. Default: enabled.', | |
117 ['--compact-field-lists'], | |
118 {'default': 1, 'action': 'store_true', | |
119 'validator': frontend.validate_boolean}), | |
120 ('Disable compact simple field lists.', | |
121 ['--no-compact-field-lists'], | |
122 {'dest': 'compact_field_lists', 'action': 'store_false'}), | |
123 ('Added to standard table classes. ' | |
124 'Defined styles: "borderless". Default: ""', | |
125 ['--table-style'], | |
126 {'default': ''}), | |
127 ('Math output format, one of "MathML", "HTML", "MathJax" ' | |
128 'or "LaTeX". Default: "HTML math.css"', | |
129 ['--math-output'], | |
130 {'default': 'HTML math.css'}), | |
131 ('Omit the XML declaration. Use with caution.', | |
132 ['--no-xml-declaration'], | |
133 {'dest': 'xml_declaration', 'default': 1, 'action': 'store_false', | |
134 'validator': frontend.validate_boolean}), | |
135 ('Obfuscate email addresses to confuse harvesters while still ' | |
136 'keeping email links usable with standards-compliant browsers.', | |
137 ['--cloak-email-addresses'], | |
138 {'action': 'store_true', 'validator': frontend.validate_boolean}),)) | |
139 | |
140 config_section = 'html4css1 writer' | |
141 | |
142 def __init__(self): | |
143 self.parts = {} | |
144 self.translator_class = HTMLTranslator | |
145 | |
146 | |
147 class HTMLTranslator(writers._html_base.HTMLTranslator): | |
148 """ | |
149 The html4css1 writer has been optimized to produce visually compact | |
150 lists (less vertical whitespace). HTML's mixed content models | |
151 allow list items to contain "<li><p>body elements</p></li>" or | |
152 "<li>just text</li>" or even "<li>text<p>and body | |
153 elements</p>combined</li>", each with different effects. It would | |
154 be best to stick with strict body elements in list items, but they | |
155 affect vertical spacing in older browsers (although they really | |
156 shouldn't). | |
157 The html5_polyglot writer solves this using CSS2. | |
158 | |
159 Here is an outline of the optimization: | |
160 | |
161 - Check for and omit <p> tags in "simple" lists: list items | |
162 contain either a single paragraph, a nested simple list, or a | |
163 paragraph followed by a nested simple list. This means that | |
164 this list can be compact: | |
165 | |
166 - Item 1. | |
167 - Item 2. | |
168 | |
169 But this list cannot be compact: | |
170 | |
171 - Item 1. | |
172 | |
173 This second paragraph forces space between list items. | |
174 | |
175 - Item 2. | |
176 | |
177 - In non-list contexts, omit <p> tags on a paragraph if that | |
178 paragraph is the only child of its parent (footnotes & citations | |
179 are allowed a label first). | |
180 | |
181 - Regardless of the above, in definitions, table cells, field bodies, | |
182 option descriptions, and list items, mark the first child with | |
183 'class="first"' and the last child with 'class="last"'. The stylesheet | |
184 sets the margins (top & bottom respectively) to 0 for these elements. | |
185 | |
186 The ``no_compact_lists`` setting (``--no-compact-lists`` command-line | |
187 option) disables list whitespace optimization. | |
188 """ | |
189 | |
190 # The following definitions are required for display in browsers limited | |
191 # to CSS1 or backwards compatible behaviour of the writer: | |
192 | |
193 doctype = ( | |
194 '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"' | |
195 ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n') | |
196 | |
197 content_type = ('<meta http-equiv="Content-Type"' | |
198 ' content="text/html; charset=%s" />\n') | |
199 content_type_mathml = ('<meta http-equiv="Content-Type"' | |
200 ' content="application/xhtml+xml; charset=%s" />\n') | |
201 | |
202 # encode also non-breaking space | |
203 special_characters = dict(_html_base.HTMLTranslator.special_characters) | |
204 special_characters[0xa0] = u' ' | |
205 | |
206 # use character reference for dash (not valid in HTML5) | |
207 attribution_formats = {'dash': ('—', ''), | |
208 'parentheses': ('(', ')'), | |
209 'parens': ('(', ')'), | |
210 'none': ('', '')} | |
211 | |
212 # ersatz for first/last pseudo-classes missing in CSS1 | |
213 def set_first_last(self, node): | |
214 self.set_class_on_child(node, 'first', 0) | |
215 self.set_class_on_child(node, 'last', -1) | |
216 | |
217 # add newline after opening tag | |
218 def visit_address(self, node): | |
219 self.visit_docinfo_item(node, 'address', meta=False) | |
220 self.body.append(self.starttag(node, 'pre', CLASS='address')) | |
221 | |
222 | |
223 # ersatz for first/last pseudo-classes | |
224 def visit_admonition(self, node): | |
225 node['classes'].insert(0, 'admonition') | |
226 self.body.append(self.starttag(node, 'div')) | |
227 self.set_first_last(node) | |
228 | |
229 # author, authors: use <br> instead of paragraphs | |
230 def visit_author(self, node): | |
231 if isinstance(node.parent, nodes.authors): | |
232 if self.author_in_authors: | |
233 self.body.append('\n<br />') | |
234 else: | |
235 self.visit_docinfo_item(node, 'author') | |
236 | |
237 def depart_author(self, node): | |
238 if isinstance(node.parent, nodes.authors): | |
239 self.author_in_authors = True | |
240 else: | |
241 self.depart_docinfo_item() | |
242 | |
243 def visit_authors(self, node): | |
244 self.visit_docinfo_item(node, 'authors') | |
245 self.author_in_authors = False # initialize | |
246 | |
247 def depart_authors(self, node): | |
248 self.depart_docinfo_item() | |
249 | |
250 # use "width" argument insted of "style: 'width'": | |
251 def visit_colspec(self, node): | |
252 self.colspecs.append(node) | |
253 # "stubs" list is an attribute of the tgroup element: | |
254 node.parent.stubs.append(node.attributes.get('stub')) | |
255 # | |
256 def depart_colspec(self, node): | |
257 # write out <colgroup> when all colspecs are processed | |
258 if isinstance(node.next_node(descend=False, siblings=True), | |
259 nodes.colspec): | |
260 return | |
261 if 'colwidths-auto' in node.parent.parent['classes'] or ( | |
262 'colwidths-auto' in self.settings.table_style and | |
263 ('colwidths-given' not in node.parent.parent['classes'])): | |
264 return | |
265 total_width = sum(node['colwidth'] for node in self.colspecs) | |
266 self.body.append(self.starttag(node, 'colgroup')) | |
267 for node in self.colspecs: | |
268 colwidth = int(node['colwidth'] * 100.0 / total_width + 0.5) | |
269 self.body.append(self.emptytag(node, 'col', | |
270 width='%i%%' % colwidth)) | |
271 self.body.append('</colgroup>\n') | |
272 | |
273 # Compact lists: | |
274 # exclude definition lists and field lists (non-compact by default) | |
275 | |
276 def is_compactable(self, node): | |
277 return ('compact' in node['classes'] | |
278 or (self.settings.compact_lists | |
279 and 'open' not in node['classes'] | |
280 and (self.compact_simple | |
281 or self.topic_classes == ['contents'] | |
282 # TODO: self.in_contents | |
283 or self.check_simple_list(node)))) | |
284 | |
285 # citations: Use table for bibliographic references. | |
286 def visit_citation(self, node): | |
287 self.body.append(self.starttag(node, 'table', | |
288 CLASS='docutils citation', | |
289 frame="void", rules="none")) | |
290 self.body.append('<colgroup><col class="label" /><col /></colgroup>\n' | |
291 '<tbody valign="top">\n' | |
292 '<tr>') | |
293 self.footnote_backrefs(node) | |
294 | |
295 def depart_citation(self, node): | |
296 self.body.append('</td></tr>\n' | |
297 '</tbody>\n</table>\n') | |
298 | |
299 # insert classifier-delimiter (not required with CSS2) | |
300 def visit_classifier(self, node): | |
301 self.body.append(' <span class="classifier-delimiter">:</span> ') | |
302 self.body.append(self.starttag(node, 'span', '', CLASS='classifier')) | |
303 | |
304 # ersatz for first/last pseudo-classes | |
305 def visit_definition(self, node): | |
306 self.body.append('</dt>\n') | |
307 self.body.append(self.starttag(node, 'dd', '')) | |
308 self.set_first_last(node) | |
309 | |
310 # don't add "simple" class value | |
311 def visit_definition_list(self, node): | |
312 self.body.append(self.starttag(node, 'dl', CLASS='docutils')) | |
313 | |
314 # use a table for description lists | |
315 def visit_description(self, node): | |
316 self.body.append(self.starttag(node, 'td', '')) | |
317 self.set_first_last(node) | |
318 | |
319 def depart_description(self, node): | |
320 self.body.append('</td>') | |
321 | |
322 # use table for docinfo | |
323 def visit_docinfo(self, node): | |
324 self.context.append(len(self.body)) | |
325 self.body.append(self.starttag(node, 'table', | |
326 CLASS='docinfo', | |
327 frame="void", rules="none")) | |
328 self.body.append('<col class="docinfo-name" />\n' | |
329 '<col class="docinfo-content" />\n' | |
330 '<tbody valign="top">\n') | |
331 self.in_docinfo = True | |
332 | |
333 def depart_docinfo(self, node): | |
334 self.body.append('</tbody>\n</table>\n') | |
335 self.in_docinfo = False | |
336 start = self.context.pop() | |
337 self.docinfo = self.body[start:] | |
338 self.body = [] | |
339 | |
340 def visit_docinfo_item(self, node, name, meta=True): | |
341 if meta: | |
342 meta_tag = '<meta name="%s" content="%s" />\n' \ | |
343 % (name, self.attval(node.astext())) | |
344 self.add_meta(meta_tag) | |
345 self.body.append(self.starttag(node, 'tr', '')) | |
346 self.body.append('<th class="docinfo-name">%s:</th>\n<td>' | |
347 % self.language.labels[name]) | |
348 if len(node): | |
349 if isinstance(node[0], nodes.Element): | |
350 node[0]['classes'].append('first') | |
351 if isinstance(node[-1], nodes.Element): | |
352 node[-1]['classes'].append('last') | |
353 | |
354 def depart_docinfo_item(self): | |
355 self.body.append('</td></tr>\n') | |
356 | |
357 # add newline after opening tag | |
358 def visit_doctest_block(self, node): | |
359 self.body.append(self.starttag(node, 'pre', CLASS='doctest-block')) | |
360 | |
361 # insert an NBSP into empty cells, ersatz for first/last | |
362 def visit_entry(self, node): | |
363 writers._html_base.HTMLTranslator.visit_entry(self, node) | |
364 if len(node) == 0: # empty cell | |
365 self.body.append(' ') | |
366 self.set_first_last(node) | |
367 | |
368 # ersatz for first/last pseudo-classes | |
369 def visit_enumerated_list(self, node): | |
370 """ | |
371 The 'start' attribute does not conform to HTML 4.01's strict.dtd, but | |
372 cannot be emulated in CSS1 (HTML 5 reincludes it). | |
373 """ | |
374 atts = {} | |
375 if 'start' in node: | |
376 atts['start'] = node['start'] | |
377 if 'enumtype' in node: | |
378 atts['class'] = node['enumtype'] | |
379 # @@@ To do: prefix, suffix. How? Change prefix/suffix to a | |
380 # single "format" attribute? Use CSS2? | |
381 old_compact_simple = self.compact_simple | |
382 self.context.append((self.compact_simple, self.compact_p)) | |
383 self.compact_p = None | |
384 self.compact_simple = self.is_compactable(node) | |
385 if self.compact_simple and not old_compact_simple: | |
386 atts['class'] = (atts.get('class', '') + ' simple').strip() | |
387 self.body.append(self.starttag(node, 'ol', **atts)) | |
388 | |
389 def depart_enumerated_list(self, node): | |
390 self.compact_simple, self.compact_p = self.context.pop() | |
391 self.body.append('</ol>\n') | |
392 | |
393 # use table for field-list: | |
394 def visit_field(self, node): | |
395 self.body.append(self.starttag(node, 'tr', '', CLASS='field')) | |
396 | |
397 def depart_field(self, node): | |
398 self.body.append('</tr>\n') | |
399 | |
400 def visit_field_body(self, node): | |
401 self.body.append(self.starttag(node, 'td', '', CLASS='field-body')) | |
402 self.set_class_on_child(node, 'first', 0) | |
403 field = node.parent | |
404 if (self.compact_field_list or | |
405 isinstance(field.parent, nodes.docinfo) or | |
406 field.parent.index(field) == len(field.parent) - 1): | |
407 # If we are in a compact list, the docinfo, or if this is | |
408 # the last field of the field list, do not add vertical | |
409 # space after last element. | |
410 self.set_class_on_child(node, 'last', -1) | |
411 | |
412 def depart_field_body(self, node): | |
413 self.body.append('</td>\n') | |
414 | |
415 def visit_field_list(self, node): | |
416 self.context.append((self.compact_field_list, self.compact_p)) | |
417 self.compact_p = None | |
418 if 'compact' in node['classes']: | |
419 self.compact_field_list = True | |
420 elif (self.settings.compact_field_lists | |
421 and 'open' not in node['classes']): | |
422 self.compact_field_list = True | |
423 if self.compact_field_list: | |
424 for field in node: | |
425 field_body = field[-1] | |
426 assert isinstance(field_body, nodes.field_body) | |
427 children = [n for n in field_body | |
428 if not isinstance(n, nodes.Invisible)] | |
429 if not (len(children) == 0 or | |
430 len(children) == 1 and | |
431 isinstance(children[0], | |
432 (nodes.paragraph, nodes.line_block))): | |
433 self.compact_field_list = False | |
434 break | |
435 self.body.append(self.starttag(node, 'table', frame='void', | |
436 rules='none', | |
437 CLASS='docutils field-list')) | |
438 self.body.append('<col class="field-name" />\n' | |
439 '<col class="field-body" />\n' | |
440 '<tbody valign="top">\n') | |
441 | |
442 def depart_field_list(self, node): | |
443 self.body.append('</tbody>\n</table>\n') | |
444 self.compact_field_list, self.compact_p = self.context.pop() | |
445 | |
446 def visit_field_name(self, node): | |
447 atts = {} | |
448 if self.in_docinfo: | |
449 atts['class'] = 'docinfo-name' | |
450 else: | |
451 atts['class'] = 'field-name' | |
452 if ( self.settings.field_name_limit | |
453 and len(node.astext()) > self.settings.field_name_limit): | |
454 atts['colspan'] = 2 | |
455 self.context.append('</tr>\n' | |
456 + self.starttag(node.parent, 'tr', '', | |
457 CLASS='field') | |
458 + '<td> </td>') | |
459 else: | |
460 self.context.append('') | |
461 self.body.append(self.starttag(node, 'th', '', **atts)) | |
462 | |
463 def depart_field_name(self, node): | |
464 self.body.append(':</th>') | |
465 self.body.append(self.context.pop()) | |
466 | |
467 # use table for footnote text | |
468 def visit_footnote(self, node): | |
469 self.body.append(self.starttag(node, 'table', | |
470 CLASS='docutils footnote', | |
471 frame="void", rules="none")) | |
472 self.body.append('<colgroup><col class="label" /><col /></colgroup>\n' | |
473 '<tbody valign="top">\n' | |
474 '<tr>') | |
475 self.footnote_backrefs(node) | |
476 | |
477 def footnote_backrefs(self, node): | |
478 backlinks = [] | |
479 backrefs = node['backrefs'] | |
480 if self.settings.footnote_backlinks and backrefs: | |
481 if len(backrefs) == 1: | |
482 self.context.append('') | |
483 self.context.append('</a>') | |
484 self.context.append('<a class="fn-backref" href="#%s">' | |
485 % backrefs[0]) | |
486 else: | |
487 for (i, backref) in enumerate(backrefs, 1): | |
488 backlinks.append('<a class="fn-backref" href="#%s">%s</a>' | |
489 % (backref, i)) | |
490 self.context.append('<em>(%s)</em> ' % ', '.join(backlinks)) | |
491 self.context += ['', ''] | |
492 else: | |
493 self.context.append('') | |
494 self.context += ['', ''] | |
495 # If the node does not only consist of a label. | |
496 if len(node) > 1: | |
497 # If there are preceding backlinks, we do not set class | |
498 # 'first', because we need to retain the top-margin. | |
499 if not backlinks: | |
500 node[1]['classes'].append('first') | |
501 node[-1]['classes'].append('last') | |
502 | |
503 def depart_footnote(self, node): | |
504 self.body.append('</td></tr>\n' | |
505 '</tbody>\n</table>\n') | |
506 | |
507 # insert markers in text as pseudo-classes are not supported in CSS1: | |
508 def visit_footnote_reference(self, node): | |
509 href = '#' + node['refid'] | |
510 format = self.settings.footnote_references | |
511 if format == 'brackets': | |
512 suffix = '[' | |
513 self.context.append(']') | |
514 else: | |
515 assert format == 'superscript' | |
516 suffix = '<sup>' | |
517 self.context.append('</sup>') | |
518 self.body.append(self.starttag(node, 'a', suffix, | |
519 CLASS='footnote-reference', href=href)) | |
520 | |
521 def depart_footnote_reference(self, node): | |
522 self.body.append(self.context.pop() + '</a>') | |
523 | |
524 # just pass on generated text | |
525 def visit_generated(self, node): | |
526 pass | |
527 | |
528 # Image types to place in an <object> element | |
529 # SVG not supported by IE up to version 8 | |
530 # (html4css1 strives for IE6 compatibility) | |
531 object_image_types = {'.svg': 'image/svg+xml', | |
532 '.swf': 'application/x-shockwave-flash'} | |
533 | |
534 # use table for footnote text, | |
535 # context added in footnote_backrefs. | |
536 def visit_label(self, node): | |
537 self.body.append(self.starttag(node, 'td', '%s[' % self.context.pop(), | |
538 CLASS='label')) | |
539 | |
540 def depart_label(self, node): | |
541 self.body.append(']%s</td><td>%s' % (self.context.pop(), self.context.pop())) | |
542 | |
543 | |
544 # ersatz for first/last pseudo-classes | |
545 def visit_list_item(self, node): | |
546 self.body.append(self.starttag(node, 'li', '')) | |
547 if len(node): | |
548 node[0]['classes'].append('first') | |
549 | |
550 # use <tt> (not supported by HTML5), | |
551 # cater for limited styling options in CSS1 using hard-coded NBSPs | |
552 def visit_literal(self, node): | |
553 # special case: "code" role | |
554 classes = node.get('classes', []) | |
555 if 'code' in classes: | |
556 # filter 'code' from class arguments | |
557 node['classes'] = [cls for cls in classes if cls != 'code'] | |
558 self.body.append(self.starttag(node, 'code', '')) | |
559 return | |
560 self.body.append( | |
561 self.starttag(node, 'tt', '', CLASS='docutils literal')) | |
562 text = node.astext() | |
563 for token in self.words_and_spaces.findall(text): | |
564 if token.strip(): | |
565 # Protect text like "--an-option" and the regular expression | |
566 # ``[+]?(\d+(\.\d*)?|\.\d+)`` from bad line wrapping | |
567 if self.in_word_wrap_point.search(token): | |
568 self.body.append('<span class="pre">%s</span>' | |
569 % self.encode(token)) | |
570 else: | |
571 self.body.append(self.encode(token)) | |
572 elif token in ('\n', ' '): | |
573 # Allow breaks at whitespace: | |
574 self.body.append(token) | |
575 else: | |
576 # Protect runs of multiple spaces; the last space can wrap: | |
577 self.body.append(' ' * (len(token) - 1) + ' ') | |
578 self.body.append('</tt>') | |
579 # Content already processed: | |
580 raise nodes.SkipNode | |
581 | |
582 # add newline after opening tag, don't use <code> for code | |
583 def visit_literal_block(self, node): | |
584 self.body.append(self.starttag(node, 'pre', CLASS='literal-block')) | |
585 | |
586 # add newline | |
587 def depart_literal_block(self, node): | |
588 self.body.append('\n</pre>\n') | |
589 | |
590 # use table for option list | |
591 def visit_option_group(self, node): | |
592 atts = {} | |
593 if ( self.settings.option_limit | |
594 and len(node.astext()) > self.settings.option_limit): | |
595 atts['colspan'] = 2 | |
596 self.context.append('</tr>\n<tr><td> </td>') | |
597 else: | |
598 self.context.append('') | |
599 self.body.append( | |
600 self.starttag(node, 'td', CLASS='option-group', **atts)) | |
601 self.body.append('<kbd>') | |
602 self.context.append(0) # count number of options | |
603 | |
604 def depart_option_group(self, node): | |
605 self.context.pop() | |
606 self.body.append('</kbd></td>\n') | |
607 self.body.append(self.context.pop()) | |
608 | |
609 def visit_option_list(self, node): | |
610 self.body.append( | |
611 self.starttag(node, 'table', CLASS='docutils option-list', | |
612 frame="void", rules="none")) | |
613 self.body.append('<col class="option" />\n' | |
614 '<col class="description" />\n' | |
615 '<tbody valign="top">\n') | |
616 | |
617 def depart_option_list(self, node): | |
618 self.body.append('</tbody>\n</table>\n') | |
619 | |
620 def visit_option_list_item(self, node): | |
621 self.body.append(self.starttag(node, 'tr', '')) | |
622 | |
623 def depart_option_list_item(self, node): | |
624 self.body.append('</tr>\n') | |
625 | |
626 # Omit <p> tags to produce visually compact lists (less vertical | |
627 # whitespace) as CSS styling requires CSS2. | |
628 def should_be_compact_paragraph(self, node): | |
629 """ | |
630 Determine if the <p> tags around paragraph ``node`` can be omitted. | |
631 """ | |
632 if (isinstance(node.parent, nodes.document) or | |
633 isinstance(node.parent, nodes.compound)): | |
634 # Never compact paragraphs in document or compound. | |
635 return False | |
636 for key, value in node.attlist(): | |
637 if (node.is_not_default(key) and | |
638 not (key == 'classes' and value in | |
639 ([], ['first'], ['last'], ['first', 'last']))): | |
640 # Attribute which needs to survive. | |
641 return False | |
642 first = isinstance(node.parent[0], nodes.label) # skip label | |
643 for child in node.parent.children[first:]: | |
644 # only first paragraph can be compact | |
645 if isinstance(child, nodes.Invisible): | |
646 continue | |
647 if child is node: | |
648 break | |
649 return False | |
650 parent_length = len([n for n in node.parent if not isinstance( | |
651 n, (nodes.Invisible, nodes.label))]) | |
652 if ( self.compact_simple | |
653 or self.compact_field_list | |
654 or self.compact_p and parent_length == 1): | |
655 return True | |
656 return False | |
657 | |
658 def visit_paragraph(self, node): | |
659 if self.should_be_compact_paragraph(node): | |
660 self.context.append('') | |
661 else: | |
662 self.body.append(self.starttag(node, 'p', '')) | |
663 self.context.append('</p>\n') | |
664 | |
665 def depart_paragraph(self, node): | |
666 self.body.append(self.context.pop()) | |
667 | |
668 # ersatz for first/last pseudo-classes | |
669 def visit_sidebar(self, node): | |
670 self.body.append( | |
671 self.starttag(node, 'div', CLASS='sidebar')) | |
672 self.set_first_last(node) | |
673 self.in_sidebar = True | |
674 | |
675 # <sub> not allowed in <pre> | |
676 def visit_subscript(self, node): | |
677 if isinstance(node.parent, nodes.literal_block): | |
678 self.body.append(self.starttag(node, 'span', '', | |
679 CLASS='subscript')) | |
680 else: | |
681 self.body.append(self.starttag(node, 'sub', '')) | |
682 | |
683 def depart_subscript(self, node): | |
684 if isinstance(node.parent, nodes.literal_block): | |
685 self.body.append('</span>') | |
686 else: | |
687 self.body.append('</sub>') | |
688 | |
689 # Use <h*> for subtitles (deprecated in HTML 5) | |
690 def visit_subtitle(self, node): | |
691 if isinstance(node.parent, nodes.sidebar): | |
692 self.body.append(self.starttag(node, 'p', '', | |
693 CLASS='sidebar-subtitle')) | |
694 self.context.append('</p>\n') | |
695 elif isinstance(node.parent, nodes.document): | |
696 self.body.append(self.starttag(node, 'h2', '', CLASS='subtitle')) | |
697 self.context.append('</h2>\n') | |
698 self.in_document_title = len(self.body) | |
699 elif isinstance(node.parent, nodes.section): | |
700 tag = 'h%s' % (self.section_level + self.initial_header_level - 1) | |
701 self.body.append( | |
702 self.starttag(node, tag, '', CLASS='section-subtitle') + | |
703 self.starttag({}, 'span', '', CLASS='section-subtitle')) | |
704 self.context.append('</span></%s>\n' % tag) | |
705 | |
706 def depart_subtitle(self, node): | |
707 self.body.append(self.context.pop()) | |
708 if self.in_document_title: | |
709 self.subtitle = self.body[self.in_document_title:-1] | |
710 self.in_document_title = 0 | |
711 self.body_pre_docinfo.extend(self.body) | |
712 self.html_subtitle.extend(self.body) | |
713 del self.body[:] | |
714 | |
715 # <sup> not allowed in <pre> in HTML 4 | |
716 def visit_superscript(self, node): | |
717 if isinstance(node.parent, nodes.literal_block): | |
718 self.body.append(self.starttag(node, 'span', '', | |
719 CLASS='superscript')) | |
720 else: | |
721 self.body.append(self.starttag(node, 'sup', '')) | |
722 | |
723 def depart_superscript(self, node): | |
724 if isinstance(node.parent, nodes.literal_block): | |
725 self.body.append('</span>') | |
726 else: | |
727 self.body.append('</sup>') | |
728 | |
729 # <tt> element deprecated in HTML 5 | |
730 def visit_system_message(self, node): | |
731 self.body.append(self.starttag(node, 'div', CLASS='system-message')) | |
732 self.body.append('<p class="system-message-title">') | |
733 backref_text = '' | |
734 if len(node['backrefs']): | |
735 backrefs = node['backrefs'] | |
736 if len(backrefs) == 1: | |
737 backref_text = ('; <em><a href="#%s">backlink</a></em>' | |
738 % backrefs[0]) | |
739 else: | |
740 i = 1 | |
741 backlinks = [] | |
742 for backref in backrefs: | |
743 backlinks.append('<a href="#%s">%s</a>' % (backref, i)) | |
744 i += 1 | |
745 backref_text = ('; <em>backlinks: %s</em>' | |
746 % ', '.join(backlinks)) | |
747 if node.hasattr('line'): | |
748 line = ', line %s' % node['line'] | |
749 else: | |
750 line = '' | |
751 self.body.append('System Message: %s/%s ' | |
752 '(<tt class="docutils">%s</tt>%s)%s</p>\n' | |
753 % (node['type'], node['level'], | |
754 self.encode(node['source']), line, backref_text)) | |
755 | |
756 # "hard coded" border setting | |
757 def visit_table(self, node): | |
758 self.context.append(self.compact_p) | |
759 self.compact_p = True | |
760 atts = {'border': 1} | |
761 classes = ['docutils', self.settings.table_style] | |
762 if 'align' in node: | |
763 classes.append('align-%s' % node['align']) | |
764 if 'width' in node: | |
765 atts['style'] = 'width: %s' % node['width'] | |
766 self.body.append( | |
767 self.starttag(node, 'table', CLASS=' '.join(classes), **atts)) | |
768 | |
769 def depart_table(self, node): | |
770 self.compact_p = self.context.pop() | |
771 self.body.append('</table>\n') | |
772 | |
773 # hard-coded vertical alignment | |
774 def visit_tbody(self, node): | |
775 self.body.append(self.starttag(node, 'tbody', valign='top')) | |
776 # | |
777 def depart_tbody(self, node): | |
778 self.body.append('</tbody>\n') | |
779 | |
780 # hard-coded vertical alignment | |
781 def visit_thead(self, node): | |
782 self.body.append(self.starttag(node, 'thead', valign='bottom')) | |
783 # | |
784 def depart_thead(self, node): | |
785 self.body.append('</thead>\n') | |
786 | |
787 | |
788 class SimpleListChecker(writers._html_base.SimpleListChecker): | |
789 | |
790 """ | |
791 Raise `nodes.NodeFound` if non-simple list item is encountered. | |
792 | |
793 Here "simple" means a list item containing nothing other than a single | |
794 paragraph, a simple list, or a paragraph followed by a simple list. | |
795 """ | |
796 | |
797 def visit_list_item(self, node): | |
798 children = [] | |
799 for child in node.children: | |
800 if not isinstance(child, nodes.Invisible): | |
801 children.append(child) | |
802 if (children and isinstance(children[0], nodes.paragraph) | |
803 and (isinstance(children[-1], nodes.bullet_list) | |
804 or isinstance(children[-1], nodes.enumerated_list))): | |
805 children.pop() | |
806 if len(children) <= 1: | |
807 return | |
808 else: | |
809 raise nodes.NodeFound | |
810 | |
811 # def visit_bullet_list(self, node): | |
812 # pass | |
813 | |
814 # def visit_enumerated_list(self, node): | |
815 # pass | |
816 | |
817 def visit_paragraph(self, node): | |
818 raise nodes.SkipNode | |
819 | |
820 def visit_definition_list(self, node): | |
821 raise nodes.NodeFound | |
822 | |
823 def visit_docinfo(self, node): | |
824 raise nodes.NodeFound |