You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
825 lines
33 KiB
825 lines
33 KiB
# $Id: __init__.py 8035 2017-02-13 22:01:47Z milde $ |
|
# Author: David Goodger |
|
# Maintainer: docutils-develop@lists.sourceforge.net |
|
# Copyright: This module has been placed in the public domain. |
|
|
|
""" |
|
Simple HyperText Markup Language document tree Writer. |
|
|
|
The output conforms to the XHTML version 1.0 Transitional DTD |
|
(*almost* strict). The output contains a minimum of formatting |
|
information. The cascading style sheet "html4css1.css" is required |
|
for proper viewing with a modern graphical browser. |
|
""" |
|
|
|
__docformat__ = 'reStructuredText' |
|
|
|
import os.path |
|
import docutils |
|
from docutils import frontend, nodes, writers, io |
|
from docutils.transforms import writer_aux |
|
from docutils.writers import _html_base |
|
|
|
class Writer(writers._html_base.Writer): |
|
|
|
supported = ('html', 'html4', 'html4css1', 'xhtml', 'xhtml10') |
|
"""Formats this writer supports.""" |
|
|
|
default_stylesheets = ['html4css1.css'] |
|
default_stylesheet_dirs = ['.', |
|
os.path.abspath(os.path.dirname(__file__)), |
|
# for math.css |
|
os.path.abspath(os.path.join( |
|
os.path.dirname(os.path.dirname(__file__)), 'html5_polyglot')) |
|
] |
|
|
|
default_template = 'template.txt' |
|
default_template_path = os.path.join( |
|
os.path.dirname(os.path.abspath(__file__)), default_template) |
|
|
|
settings_spec = ( |
|
'HTML-Specific Options', |
|
None, |
|
(('Specify the template file (UTF-8 encoded). Default is "%s".' |
|
% default_template_path, |
|
['--template'], |
|
{'default': default_template_path, 'metavar': '<file>'}), |
|
('Comma separated list of stylesheet URLs. ' |
|
'Overrides previous --stylesheet and --stylesheet-path settings.', |
|
['--stylesheet'], |
|
{'metavar': '<URL[,URL,...]>', 'overrides': 'stylesheet_path', |
|
'validator': frontend.validate_comma_separated_list}), |
|
('Comma separated list of stylesheet paths. ' |
|
'Relative paths are expanded if a matching file is found in ' |
|
'the --stylesheet-dirs. With --link-stylesheet, ' |
|
'the path is rewritten relative to the output HTML file. ' |
|
'Default: "%s"' % ','.join(default_stylesheets), |
|
['--stylesheet-path'], |
|
{'metavar': '<file[,file,...]>', 'overrides': 'stylesheet', |
|
'validator': frontend.validate_comma_separated_list, |
|
'default': default_stylesheets}), |
|
('Embed the stylesheet(s) in the output HTML file. The stylesheet ' |
|
'files must be accessible during processing. This is the default.', |
|
['--embed-stylesheet'], |
|
{'default': 1, 'action': 'store_true', |
|
'validator': frontend.validate_boolean}), |
|
('Link to the stylesheet(s) in the output HTML file. ' |
|
'Default: embed stylesheets.', |
|
['--link-stylesheet'], |
|
{'dest': 'embed_stylesheet', 'action': 'store_false'}), |
|
('Comma-separated list of directories where stylesheets are found. ' |
|
'Used by --stylesheet-path when expanding relative path arguments. ' |
|
'Default: "%s"' % default_stylesheet_dirs, |
|
['--stylesheet-dirs'], |
|
{'metavar': '<dir[,dir,...]>', |
|
'validator': frontend.validate_comma_separated_list, |
|
'default': default_stylesheet_dirs}), |
|
('Specify the initial header level. Default is 1 for "<h1>". ' |
|
'Does not affect document title & subtitle (see --no-doc-title).', |
|
['--initial-header-level'], |
|
{'choices': '1 2 3 4 5 6'.split(), 'default': '1', |
|
'metavar': '<level>'}), |
|
('Specify the maximum width (in characters) for one-column field ' |
|
'names. Longer field names will span an entire row of the table ' |
|
'used to render the field list. Default is 14 characters. ' |
|
'Use 0 for "no limit".', |
|
['--field-name-limit'], |
|
{'default': 14, 'metavar': '<level>', |
|
'validator': frontend.validate_nonnegative_int}), |
|
('Specify the maximum width (in characters) for options in option ' |
|
'lists. Longer options will span an entire row of the table used ' |
|
'to render the option list. Default is 14 characters. ' |
|
'Use 0 for "no limit".', |
|
['--option-limit'], |
|
{'default': 14, 'metavar': '<level>', |
|
'validator': frontend.validate_nonnegative_int}), |
|
('Format for footnote references: one of "superscript" or ' |
|
'"brackets". Default is "brackets".', |
|
['--footnote-references'], |
|
{'choices': ['superscript', 'brackets'], 'default': 'brackets', |
|
'metavar': '<format>', |
|
'overrides': 'trim_footnote_reference_space'}), |
|
('Format for block quote attributions: one of "dash" (em-dash ' |
|
'prefix), "parentheses"/"parens", or "none". Default is "dash".', |
|
['--attribution'], |
|
{'choices': ['dash', 'parentheses', 'parens', 'none'], |
|
'default': 'dash', 'metavar': '<format>'}), |
|
('Remove extra vertical whitespace between items of "simple" bullet ' |
|
'lists and enumerated lists. Default: enabled.', |
|
['--compact-lists'], |
|
{'default': 1, 'action': 'store_true', |
|
'validator': frontend.validate_boolean}), |
|
('Disable compact simple bullet and enumerated lists.', |
|
['--no-compact-lists'], |
|
{'dest': 'compact_lists', 'action': 'store_false'}), |
|
('Remove extra vertical whitespace between items of simple field ' |
|
'lists. Default: enabled.', |
|
['--compact-field-lists'], |
|
{'default': 1, 'action': 'store_true', |
|
'validator': frontend.validate_boolean}), |
|
('Disable compact simple field lists.', |
|
['--no-compact-field-lists'], |
|
{'dest': 'compact_field_lists', 'action': 'store_false'}), |
|
('Added to standard table classes. ' |
|
'Defined styles: "borderless". Default: ""', |
|
['--table-style'], |
|
{'default': ''}), |
|
('Math output format, one of "MathML", "HTML", "MathJax" ' |
|
'or "LaTeX". Default: "HTML math.css"', |
|
['--math-output'], |
|
{'default': 'HTML math.css'}), |
|
('Omit the XML declaration. Use with caution.', |
|
['--no-xml-declaration'], |
|
{'dest': 'xml_declaration', 'default': 1, 'action': 'store_false', |
|
'validator': frontend.validate_boolean}), |
|
('Obfuscate email addresses to confuse harvesters while still ' |
|
'keeping email links usable with standards-compliant browsers.', |
|
['--cloak-email-addresses'], |
|
{'action': 'store_true', 'validator': frontend.validate_boolean}),)) |
|
|
|
config_section = 'html4css1 writer' |
|
|
|
def __init__(self): |
|
self.parts = {} |
|
self.translator_class = HTMLTranslator |
|
|
|
|
|
class HTMLTranslator(writers._html_base.HTMLTranslator): |
|
""" |
|
The html4css1 writer has been optimized to produce visually compact |
|
lists (less vertical whitespace). HTML's mixed content models |
|
allow list items to contain "<li><p>body elements</p></li>" or |
|
"<li>just text</li>" or even "<li>text<p>and body |
|
elements</p>combined</li>", each with different effects. It would |
|
be best to stick with strict body elements in list items, but they |
|
affect vertical spacing in older browsers (although they really |
|
shouldn't). |
|
The html5_polyglot writer solves this using CSS2. |
|
|
|
Here is an outline of the optimization: |
|
|
|
- Check for and omit <p> tags in "simple" lists: list items |
|
contain either a single paragraph, a nested simple list, or a |
|
paragraph followed by a nested simple list. This means that |
|
this list can be compact: |
|
|
|
- Item 1. |
|
- Item 2. |
|
|
|
But this list cannot be compact: |
|
|
|
- Item 1. |
|
|
|
This second paragraph forces space between list items. |
|
|
|
- Item 2. |
|
|
|
- In non-list contexts, omit <p> tags on a paragraph if that |
|
paragraph is the only child of its parent (footnotes & citations |
|
are allowed a label first). |
|
|
|
- Regardless of the above, in definitions, table cells, field bodies, |
|
option descriptions, and list items, mark the first child with |
|
'class="first"' and the last child with 'class="last"'. The stylesheet |
|
sets the margins (top & bottom respectively) to 0 for these elements. |
|
|
|
The ``no_compact_lists`` setting (``--no-compact-lists`` command-line |
|
option) disables list whitespace optimization. |
|
""" |
|
|
|
# The following definitions are required for display in browsers limited |
|
# to CSS1 or backwards compatible behaviour of the writer: |
|
|
|
doctype = ( |
|
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"' |
|
' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n') |
|
|
|
content_type = ('<meta http-equiv="Content-Type"' |
|
' content="text/html; charset=%s" />\n') |
|
content_type_mathml = ('<meta http-equiv="Content-Type"' |
|
' content="application/xhtml+xml; charset=%s" />\n') |
|
|
|
# encode also non-breaking space |
|
special_characters = dict(_html_base.HTMLTranslator.special_characters) |
|
special_characters[0xa0] = u' ' |
|
|
|
# use character reference for dash (not valid in HTML5) |
|
attribution_formats = {'dash': ('—', ''), |
|
'parentheses': ('(', ')'), |
|
'parens': ('(', ')'), |
|
'none': ('', '')} |
|
|
|
# ersatz for first/last pseudo-classes missing in CSS1 |
|
def set_first_last(self, node): |
|
self.set_class_on_child(node, 'first', 0) |
|
self.set_class_on_child(node, 'last', -1) |
|
|
|
# add newline after opening tag |
|
def visit_address(self, node): |
|
self.visit_docinfo_item(node, 'address', meta=False) |
|
self.body.append(self.starttag(node, 'pre', CLASS='address')) |
|
|
|
|
|
# ersatz for first/last pseudo-classes |
|
def visit_admonition(self, node): |
|
node['classes'].insert(0, 'admonition') |
|
self.body.append(self.starttag(node, 'div')) |
|
self.set_first_last(node) |
|
|
|
# author, authors: use <br> instead of paragraphs |
|
def visit_author(self, node): |
|
if isinstance(node.parent, nodes.authors): |
|
if self.author_in_authors: |
|
self.body.append('\n<br />') |
|
else: |
|
self.visit_docinfo_item(node, 'author') |
|
|
|
def depart_author(self, node): |
|
if isinstance(node.parent, nodes.authors): |
|
self.author_in_authors = True |
|
else: |
|
self.depart_docinfo_item() |
|
|
|
def visit_authors(self, node): |
|
self.visit_docinfo_item(node, 'authors') |
|
self.author_in_authors = False # initialize |
|
|
|
def depart_authors(self, node): |
|
self.depart_docinfo_item() |
|
|
|
# use "width" argument insted of "style: 'width'": |
|
def visit_colspec(self, node): |
|
self.colspecs.append(node) |
|
# "stubs" list is an attribute of the tgroup element: |
|
node.parent.stubs.append(node.attributes.get('stub')) |
|
# |
|
def depart_colspec(self, node): |
|
# write out <colgroup> when all colspecs are processed |
|
if isinstance(node.next_node(descend=False, siblings=True), |
|
nodes.colspec): |
|
return |
|
if 'colwidths-auto' in node.parent.parent['classes'] or ( |
|
'colwidths-auto' in self.settings.table_style and |
|
('colwidths-given' not in node.parent.parent['classes'])): |
|
return |
|
total_width = sum(node['colwidth'] for node in self.colspecs) |
|
self.body.append(self.starttag(node, 'colgroup')) |
|
for node in self.colspecs: |
|
colwidth = int(node['colwidth'] * 100.0 / total_width + 0.5) |
|
self.body.append(self.emptytag(node, 'col', |
|
width='%i%%' % colwidth)) |
|
self.body.append('</colgroup>\n') |
|
|
|
# Compact lists: |
|
# exclude definition lists and field lists (non-compact by default) |
|
|
|
def is_compactable(self, node): |
|
return ('compact' in node['classes'] |
|
or (self.settings.compact_lists |
|
and 'open' not in node['classes'] |
|
and (self.compact_simple |
|
or self.topic_classes == ['contents'] |
|
# TODO: self.in_contents |
|
or self.check_simple_list(node)))) |
|
|
|
# citations: Use table for bibliographic references. |
|
def visit_citation(self, node): |
|
self.body.append(self.starttag(node, 'table', |
|
CLASS='docutils citation', |
|
frame="void", rules="none")) |
|
self.body.append('<colgroup><col class="label" /><col /></colgroup>\n' |
|
'<tbody valign="top">\n' |
|
'<tr>') |
|
self.footnote_backrefs(node) |
|
|
|
def depart_citation(self, node): |
|
self.body.append('</td></tr>\n' |
|
'</tbody>\n</table>\n') |
|
|
|
# insert classifier-delimiter (not required with CSS2) |
|
def visit_classifier(self, node): |
|
self.body.append(' <span class="classifier-delimiter">:</span> ') |
|
self.body.append(self.starttag(node, 'span', '', CLASS='classifier')) |
|
|
|
# ersatz for first/last pseudo-classes |
|
def visit_definition(self, node): |
|
self.body.append('</dt>\n') |
|
self.body.append(self.starttag(node, 'dd', '')) |
|
self.set_first_last(node) |
|
|
|
# don't add "simple" class value |
|
def visit_definition_list(self, node): |
|
self.body.append(self.starttag(node, 'dl', CLASS='docutils')) |
|
|
|
# use a table for description lists |
|
def visit_description(self, node): |
|
self.body.append(self.starttag(node, 'td', '')) |
|
self.set_first_last(node) |
|
|
|
def depart_description(self, node): |
|
self.body.append('</td>') |
|
|
|
# use table for docinfo |
|
def visit_docinfo(self, node): |
|
self.context.append(len(self.body)) |
|
self.body.append(self.starttag(node, 'table', |
|
CLASS='docinfo', |
|
frame="void", rules="none")) |
|
self.body.append('<col class="docinfo-name" />\n' |
|
'<col class="docinfo-content" />\n' |
|
'<tbody valign="top">\n') |
|
self.in_docinfo = True |
|
|
|
def depart_docinfo(self, node): |
|
self.body.append('</tbody>\n</table>\n') |
|
self.in_docinfo = False |
|
start = self.context.pop() |
|
self.docinfo = self.body[start:] |
|
self.body = [] |
|
|
|
def visit_docinfo_item(self, node, name, meta=True): |
|
if meta: |
|
meta_tag = '<meta name="%s" content="%s" />\n' \ |
|
% (name, self.attval(node.astext())) |
|
self.add_meta(meta_tag) |
|
self.body.append(self.starttag(node, 'tr', '')) |
|
self.body.append('<th class="docinfo-name">%s:</th>\n<td>' |
|
% self.language.labels[name]) |
|
if len(node): |
|
if isinstance(node[0], nodes.Element): |
|
node[0]['classes'].append('first') |
|
if isinstance(node[-1], nodes.Element): |
|
node[-1]['classes'].append('last') |
|
|
|
def depart_docinfo_item(self): |
|
self.body.append('</td></tr>\n') |
|
|
|
# add newline after opening tag |
|
def visit_doctest_block(self, node): |
|
self.body.append(self.starttag(node, 'pre', CLASS='doctest-block')) |
|
|
|
# insert an NBSP into empty cells, ersatz for first/last |
|
def visit_entry(self, node): |
|
writers._html_base.HTMLTranslator.visit_entry(self, node) |
|
if len(node) == 0: # empty cell |
|
self.body.append(' ') |
|
self.set_first_last(node) |
|
|
|
# ersatz for first/last pseudo-classes |
|
def visit_enumerated_list(self, node): |
|
""" |
|
The 'start' attribute does not conform to HTML 4.01's strict.dtd, but |
|
cannot be emulated in CSS1 (HTML 5 reincludes it). |
|
""" |
|
atts = {} |
|
if 'start' in node: |
|
atts['start'] = node['start'] |
|
if 'enumtype' in node: |
|
atts['class'] = node['enumtype'] |
|
# @@@ To do: prefix, suffix. How? Change prefix/suffix to a |
|
# single "format" attribute? Use CSS2? |
|
old_compact_simple = self.compact_simple |
|
self.context.append((self.compact_simple, self.compact_p)) |
|
self.compact_p = None |
|
self.compact_simple = self.is_compactable(node) |
|
if self.compact_simple and not old_compact_simple: |
|
atts['class'] = (atts.get('class', '') + ' simple').strip() |
|
self.body.append(self.starttag(node, 'ol', **atts)) |
|
|
|
def depart_enumerated_list(self, node): |
|
self.compact_simple, self.compact_p = self.context.pop() |
|
self.body.append('</ol>\n') |
|
|
|
# use table for field-list: |
|
def visit_field(self, node): |
|
self.body.append(self.starttag(node, 'tr', '', CLASS='field')) |
|
|
|
def depart_field(self, node): |
|
self.body.append('</tr>\n') |
|
|
|
def visit_field_body(self, node): |
|
self.body.append(self.starttag(node, 'td', '', CLASS='field-body')) |
|
self.set_class_on_child(node, 'first', 0) |
|
field = node.parent |
|
if (self.compact_field_list or |
|
isinstance(field.parent, nodes.docinfo) or |
|
field.parent.index(field) == len(field.parent) - 1): |
|
# If we are in a compact list, the docinfo, or if this is |
|
# the last field of the field list, do not add vertical |
|
# space after last element. |
|
self.set_class_on_child(node, 'last', -1) |
|
|
|
def depart_field_body(self, node): |
|
self.body.append('</td>\n') |
|
|
|
def visit_field_list(self, node): |
|
self.context.append((self.compact_field_list, self.compact_p)) |
|
self.compact_p = None |
|
if 'compact' in node['classes']: |
|
self.compact_field_list = True |
|
elif (self.settings.compact_field_lists |
|
and 'open' not in node['classes']): |
|
self.compact_field_list = True |
|
if self.compact_field_list: |
|
for field in node: |
|
field_body = field[-1] |
|
assert isinstance(field_body, nodes.field_body) |
|
children = [n for n in field_body |
|
if not isinstance(n, nodes.Invisible)] |
|
if not (len(children) == 0 or |
|
len(children) == 1 and |
|
isinstance(children[0], |
|
(nodes.paragraph, nodes.line_block))): |
|
self.compact_field_list = False |
|
break |
|
self.body.append(self.starttag(node, 'table', frame='void', |
|
rules='none', |
|
CLASS='docutils field-list')) |
|
self.body.append('<col class="field-name" />\n' |
|
'<col class="field-body" />\n' |
|
'<tbody valign="top">\n') |
|
|
|
def depart_field_list(self, node): |
|
self.body.append('</tbody>\n</table>\n') |
|
self.compact_field_list, self.compact_p = self.context.pop() |
|
|
|
def visit_field_name(self, node): |
|
atts = {} |
|
if self.in_docinfo: |
|
atts['class'] = 'docinfo-name' |
|
else: |
|
atts['class'] = 'field-name' |
|
if ( self.settings.field_name_limit |
|
and len(node.astext()) > self.settings.field_name_limit): |
|
atts['colspan'] = 2 |
|
self.context.append('</tr>\n' |
|
+ self.starttag(node.parent, 'tr', '', |
|
CLASS='field') |
|
+ '<td> </td>') |
|
else: |
|
self.context.append('') |
|
self.body.append(self.starttag(node, 'th', '', **atts)) |
|
|
|
def depart_field_name(self, node): |
|
self.body.append(':</th>') |
|
self.body.append(self.context.pop()) |
|
|
|
# use table for footnote text |
|
def visit_footnote(self, node): |
|
self.body.append(self.starttag(node, 'table', |
|
CLASS='docutils footnote', |
|
frame="void", rules="none")) |
|
self.body.append('<colgroup><col class="label" /><col /></colgroup>\n' |
|
'<tbody valign="top">\n' |
|
'<tr>') |
|
self.footnote_backrefs(node) |
|
|
|
def footnote_backrefs(self, node): |
|
backlinks = [] |
|
backrefs = node['backrefs'] |
|
if self.settings.footnote_backlinks and backrefs: |
|
if len(backrefs) == 1: |
|
self.context.append('') |
|
self.context.append('</a>') |
|
self.context.append('<a class="fn-backref" href="#%s">' |
|
% backrefs[0]) |
|
else: |
|
# Python 2.4 fails with enumerate(backrefs, 1) |
|
for (i, backref) in enumerate(backrefs): |
|
backlinks.append('<a class="fn-backref" href="#%s">%s</a>' |
|
% (backref, i+1)) |
|
self.context.append('<em>(%s)</em> ' % ', '.join(backlinks)) |
|
self.context += ['', ''] |
|
else: |
|
self.context.append('') |
|
self.context += ['', ''] |
|
# If the node does not only consist of a label. |
|
if len(node) > 1: |
|
# If there are preceding backlinks, we do not set class |
|
# 'first', because we need to retain the top-margin. |
|
if not backlinks: |
|
node[1]['classes'].append('first') |
|
node[-1]['classes'].append('last') |
|
|
|
def depart_footnote(self, node): |
|
self.body.append('</td></tr>\n' |
|
'</tbody>\n</table>\n') |
|
|
|
# insert markers in text as pseudo-classes are not supported in CSS1: |
|
def visit_footnote_reference(self, node): |
|
href = '#' + node['refid'] |
|
format = self.settings.footnote_references |
|
if format == 'brackets': |
|
suffix = '[' |
|
self.context.append(']') |
|
else: |
|
assert format == 'superscript' |
|
suffix = '<sup>' |
|
self.context.append('</sup>') |
|
self.body.append(self.starttag(node, 'a', suffix, |
|
CLASS='footnote-reference', href=href)) |
|
|
|
def depart_footnote_reference(self, node): |
|
self.body.append(self.context.pop() + '</a>') |
|
|
|
# just pass on generated text |
|
def visit_generated(self, node): |
|
pass |
|
|
|
# Image types to place in an <object> element |
|
# SVG not supported by IE up to version 8 |
|
# (html4css1 strives for IE6 compatibility) |
|
object_image_types = {'.svg': 'image/svg+xml', |
|
'.swf': 'application/x-shockwave-flash'} |
|
|
|
# use table for footnote text, |
|
# context added in footnote_backrefs. |
|
def visit_label(self, node): |
|
self.body.append(self.starttag(node, 'td', '%s[' % self.context.pop(), |
|
CLASS='label')) |
|
|
|
def depart_label(self, node): |
|
self.body.append(']%s</td><td>%s' % (self.context.pop(), self.context.pop())) |
|
|
|
|
|
# ersatz for first/last pseudo-classes |
|
def visit_list_item(self, node): |
|
self.body.append(self.starttag(node, 'li', '')) |
|
if len(node): |
|
node[0]['classes'].append('first') |
|
|
|
# use <tt> (not supported by HTML5), |
|
# cater for limited styling options in CSS1 using hard-coded NBSPs |
|
def visit_literal(self, node): |
|
# special case: "code" role |
|
classes = node.get('classes', []) |
|
if 'code' in classes: |
|
# filter 'code' from class arguments |
|
node['classes'] = [cls for cls in classes if cls != 'code'] |
|
self.body.append(self.starttag(node, 'code', '')) |
|
return |
|
self.body.append( |
|
self.starttag(node, 'tt', '', CLASS='docutils literal')) |
|
text = node.astext() |
|
for token in self.words_and_spaces.findall(text): |
|
if token.strip(): |
|
# Protect text like "--an-option" and the regular expression |
|
# ``[+]?(\d+(\.\d*)?|\.\d+)`` from bad line wrapping |
|
if self.in_word_wrap_point.search(token): |
|
self.body.append('<span class="pre">%s</span>' |
|
% self.encode(token)) |
|
else: |
|
self.body.append(self.encode(token)) |
|
elif token in ('\n', ' '): |
|
# Allow breaks at whitespace: |
|
self.body.append(token) |
|
else: |
|
# Protect runs of multiple spaces; the last space can wrap: |
|
self.body.append(' ' * (len(token) - 1) + ' ') |
|
self.body.append('</tt>') |
|
# Content already processed: |
|
raise nodes.SkipNode |
|
|
|
# add newline after opening tag, don't use <code> for code |
|
def visit_literal_block(self, node): |
|
self.body.append(self.starttag(node, 'pre', CLASS='literal-block')) |
|
|
|
# add newline |
|
def depart_literal_block(self, node): |
|
self.body.append('\n</pre>\n') |
|
|
|
# use table for option list |
|
def visit_option_group(self, node): |
|
atts = {} |
|
if ( self.settings.option_limit |
|
and len(node.astext()) > self.settings.option_limit): |
|
atts['colspan'] = 2 |
|
self.context.append('</tr>\n<tr><td> </td>') |
|
else: |
|
self.context.append('') |
|
self.body.append( |
|
self.starttag(node, 'td', CLASS='option-group', **atts)) |
|
self.body.append('<kbd>') |
|
self.context.append(0) # count number of options |
|
|
|
def depart_option_group(self, node): |
|
self.context.pop() |
|
self.body.append('</kbd></td>\n') |
|
self.body.append(self.context.pop()) |
|
|
|
def visit_option_list(self, node): |
|
self.body.append( |
|
self.starttag(node, 'table', CLASS='docutils option-list', |
|
frame="void", rules="none")) |
|
self.body.append('<col class="option" />\n' |
|
'<col class="description" />\n' |
|
'<tbody valign="top">\n') |
|
|
|
def depart_option_list(self, node): |
|
self.body.append('</tbody>\n</table>\n') |
|
|
|
def visit_option_list_item(self, node): |
|
self.body.append(self.starttag(node, 'tr', '')) |
|
|
|
def depart_option_list_item(self, node): |
|
self.body.append('</tr>\n') |
|
|
|
# Omit <p> tags to produce visually compact lists (less vertical |
|
# whitespace) as CSS styling requires CSS2. |
|
def should_be_compact_paragraph(self, node): |
|
""" |
|
Determine if the <p> tags around paragraph ``node`` can be omitted. |
|
""" |
|
if (isinstance(node.parent, nodes.document) or |
|
isinstance(node.parent, nodes.compound)): |
|
# Never compact paragraphs in document or compound. |
|
return False |
|
for key, value in node.attlist(): |
|
if (node.is_not_default(key) and |
|
not (key == 'classes' and value in |
|
([], ['first'], ['last'], ['first', 'last']))): |
|
# Attribute which needs to survive. |
|
return False |
|
first = isinstance(node.parent[0], nodes.label) # skip label |
|
for child in node.parent.children[first:]: |
|
# only first paragraph can be compact |
|
if isinstance(child, nodes.Invisible): |
|
continue |
|
if child is node: |
|
break |
|
return False |
|
parent_length = len([n for n in node.parent if not isinstance( |
|
n, (nodes.Invisible, nodes.label))]) |
|
if ( self.compact_simple |
|
or self.compact_field_list |
|
or self.compact_p and parent_length == 1): |
|
return True |
|
return False |
|
|
|
def visit_paragraph(self, node): |
|
if self.should_be_compact_paragraph(node): |
|
self.context.append('') |
|
else: |
|
self.body.append(self.starttag(node, 'p', '')) |
|
self.context.append('</p>\n') |
|
|
|
def depart_paragraph(self, node): |
|
self.body.append(self.context.pop()) |
|
|
|
# ersatz for first/last pseudo-classes |
|
def visit_sidebar(self, node): |
|
self.body.append( |
|
self.starttag(node, 'div', CLASS='sidebar')) |
|
self.set_first_last(node) |
|
self.in_sidebar = True |
|
|
|
# <sub> not allowed in <pre> |
|
def visit_subscript(self, node): |
|
if isinstance(node.parent, nodes.literal_block): |
|
self.body.append(self.starttag(node, 'span', '', |
|
CLASS='subscript')) |
|
else: |
|
self.body.append(self.starttag(node, 'sub', '')) |
|
|
|
def depart_subscript(self, node): |
|
if isinstance(node.parent, nodes.literal_block): |
|
self.body.append('</span>') |
|
else: |
|
self.body.append('</sub>') |
|
|
|
# Use <h*> for subtitles (deprecated in HTML 5) |
|
def visit_subtitle(self, node): |
|
if isinstance(node.parent, nodes.sidebar): |
|
self.body.append(self.starttag(node, 'p', '', |
|
CLASS='sidebar-subtitle')) |
|
self.context.append('</p>\n') |
|
elif isinstance(node.parent, nodes.document): |
|
self.body.append(self.starttag(node, 'h2', '', CLASS='subtitle')) |
|
self.context.append('</h2>\n') |
|
self.in_document_title = len(self.body) |
|
elif isinstance(node.parent, nodes.section): |
|
tag = 'h%s' % (self.section_level + self.initial_header_level - 1) |
|
self.body.append( |
|
self.starttag(node, tag, '', CLASS='section-subtitle') + |
|
self.starttag({}, 'span', '', CLASS='section-subtitle')) |
|
self.context.append('</span></%s>\n' % tag) |
|
|
|
def depart_subtitle(self, node): |
|
self.body.append(self.context.pop()) |
|
if self.in_document_title: |
|
self.subtitle = self.body[self.in_document_title:-1] |
|
self.in_document_title = 0 |
|
self.body_pre_docinfo.extend(self.body) |
|
self.html_subtitle.extend(self.body) |
|
del self.body[:] |
|
|
|
# <sup> not allowed in <pre> in HTML 4 |
|
def visit_superscript(self, node): |
|
if isinstance(node.parent, nodes.literal_block): |
|
self.body.append(self.starttag(node, 'span', '', |
|
CLASS='superscript')) |
|
else: |
|
self.body.append(self.starttag(node, 'sup', '')) |
|
|
|
def depart_superscript(self, node): |
|
if isinstance(node.parent, nodes.literal_block): |
|
self.body.append('</span>') |
|
else: |
|
self.body.append('</sup>') |
|
|
|
# <tt> element deprecated in HTML 5 |
|
def visit_system_message(self, node): |
|
self.body.append(self.starttag(node, 'div', CLASS='system-message')) |
|
self.body.append('<p class="system-message-title">') |
|
backref_text = '' |
|
if len(node['backrefs']): |
|
backrefs = node['backrefs'] |
|
if len(backrefs) == 1: |
|
backref_text = ('; <em><a href="#%s">backlink</a></em>' |
|
% backrefs[0]) |
|
else: |
|
i = 1 |
|
backlinks = [] |
|
for backref in backrefs: |
|
backlinks.append('<a href="#%s">%s</a>' % (backref, i)) |
|
i += 1 |
|
backref_text = ('; <em>backlinks: %s</em>' |
|
% ', '.join(backlinks)) |
|
if node.hasattr('line'): |
|
line = ', line %s' % node['line'] |
|
else: |
|
line = '' |
|
self.body.append('System Message: %s/%s ' |
|
'(<tt class="docutils">%s</tt>%s)%s</p>\n' |
|
% (node['type'], node['level'], |
|
self.encode(node['source']), line, backref_text)) |
|
|
|
# "hard coded" border setting |
|
def visit_table(self, node): |
|
self.context.append(self.compact_p) |
|
self.compact_p = True |
|
classes = ['docutils', self.settings.table_style] |
|
if 'align' in node: |
|
classes.append('align-%s' % node['align']) |
|
self.body.append( |
|
self.starttag(node, 'table', CLASS=' '.join(classes), border="1")) |
|
|
|
def depart_table(self, node): |
|
self.compact_p = self.context.pop() |
|
self.body.append('</table>\n') |
|
|
|
# hard-coded vertical alignment |
|
def visit_tbody(self, node): |
|
self.body.append(self.starttag(node, 'tbody', valign='top')) |
|
# |
|
def depart_tbody(self, node): |
|
self.body.append('</tbody>\n') |
|
|
|
# hard-coded vertical alignment |
|
def visit_thead(self, node): |
|
self.body.append(self.starttag(node, 'thead', valign='bottom')) |
|
# |
|
def depart_thead(self, node): |
|
self.body.append('</thead>\n') |
|
|
|
|
|
class SimpleListChecker(writers._html_base.SimpleListChecker): |
|
|
|
""" |
|
Raise `nodes.NodeFound` if non-simple list item is encountered. |
|
|
|
Here "simple" means a list item containing nothing other than a single |
|
paragraph, a simple list, or a paragraph followed by a simple list. |
|
""" |
|
|
|
def visit_list_item(self, node): |
|
children = [] |
|
for child in node.children: |
|
if not isinstance(child, nodes.Invisible): |
|
children.append(child) |
|
if (children and isinstance(children[0], nodes.paragraph) |
|
and (isinstance(children[-1], nodes.bullet_list) |
|
or isinstance(children[-1], nodes.enumerated_list))): |
|
children.pop() |
|
if len(children) <= 1: |
|
return |
|
else: |
|
raise nodes.NodeFound |
|
|
|
# def visit_bullet_list(self, node): |
|
# pass |
|
|
|
# def visit_enumerated_list(self, node): |
|
# pass |
|
|
|
# def visit_paragraph(self, node): |
|
# raise nodes.SkipNode |
|
|
|
def visit_definition_list(self, node): |
|
raise nodes.NodeFound |
|
|
|
def visit_docinfo(self, node): |
|
raise nodes.NodeFound |
|
|
|
def visit_definition_list(self, node): |
|
raise nodes.NodeFound
|
|
|