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'&nbsp;'
205
206 # use character reference for dash (not valid in HTML5)
207 attribution_formats = {'dash': ('&mdash;', ''),
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('&nbsp;')
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>&nbsp;</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('&nbsp;' * (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>&nbsp;</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