#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import re
import sys
from collections import OrderedDict
from docutils import nodes, writers
from docutils.transforms import Transform
from genshi.builder import tag
from genshi.core import Markup
from genshi.output import XHTMLSerializer
from modules.utils import pygmentize_to_tag
__docformat__ = 'reStructuredText'
__version__ = '1.8.0'
try:
# docutils >= 0.10
from docutils.utils.math import pick_math_environment
except ImportError:
from docutils.math import pick_math_environment
if sys.version[0] == '3':
unicode = str
class FooterToBottom(Transform):
'''
The doctree must be adjusted before translation begins
since in-place tree modifications doesn't work.
The footer must be relocated to the bottom of the
doctree in order to be translated at the right position.
.. seealso::
* http://docutils.sourceforge.net/docs/ref/transforms.html
'''
default_priority = 850
def apply(self):
for child in self.document.children:
if isinstance(child, nodes.decoration):
for footer in child:
if isinstance(footer, nodes.footer):
child.remove(footer)
self.document.append(footer)
break
def _script_callback_option_parser(option, opt_str, value, parser):
'''
Store a script's URL or path along to its attribute 'defer', 'async' or None
'''
attr = 'defer' in opt_str and 'defer' or \
'async' in opt_str and 'async' or \
None
parser.values.script = parser.values.script or []
parser.values.script.append((value, attr))
return
class HTML5Writer(writers.Writer):
supported = ('html', 'html5', 'html5css3')
config_section = 'html5writer'
config_section_dependencies = ('writers')
# OptParse see https://docs.python.org/2/library/optparse.html
settings_spec = (
'rst2html5 Specific Options',
'For the rst2html5 writer the default tab width value is 4.', (
("Don't indent output", ['--no-indent'],
{'default': 1, 'action': 'store_false', 'dest': 'indent_output'}),
('Specify a stylesheet URL to be included in the output HTML file. '
'(This option can be used multiple times)',
['--stylesheet'],
{'metavar': '<URL or path>', 'default': None,
'action': 'append', }),
('Specify a script URL to be included in the output HTML file. '
'(This option can be used multiple times)',
['--script'],
{'metavar': '<URL or path>', 'default': None,
'dest': 'script',
'type': 'string',
'action': 'callback',
'callback': _script_callback_option_parser, }),
('Specify a script URL with a defer attribute '
'to be included in the output HTML file. '
'(This option can be used multiple times)',
['--script-defer'],
{'metavar': '<URL or path>',
'dest': 'script',
'type': 'string',
'action': 'callback',
'callback': _script_callback_option_parser, }),
('Specify a script URL with a async attribute '
'to be included in the output HTML file. '
'(This option can be used multiple times)',
['--script-async'],
{'metavar': '<URL or path>',
'dest': 'script',
'type': 'string',
'action': 'callback',
'callback': _script_callback_option_parser, }),
('Specify a html tag attribute. '
'(This option can be used multiple times)',
['--html-tag-attr'],
{'metavar': '<attribute>', 'default': None,
'dest': 'html_tag_attr',
'action': 'append', }),
('Specify a filename or text to be used as the HTML5 output template. '
'The template must have the {head} and {body} placeholders. '
'The "<html{html_attr}>" placeholder is recommended.',
['--template'],
{'metavar': '<filename or text>', 'default': None,
'dest': 'template',
'type': 'string',
'action': 'store', }),
('Define a case insensitive identifier to be used with ifdef and ifndef directives. '
'There is no value associated with an identifier. '
'(This option can be used multiple times)',
['--define'],
{'metavar': '<identifier>',
'dest': 'identifiers',
'default': None,
'action': 'append', }),
)
)
settings_defaults = {
'tab_width': 4,
'syntax_highlight': 'short',
'field_limit': 0,
'option_limit': 0,
}
def __init__(self):
writers.Writer.__init__(self)
self.translator_class = HTML5Translator
return
def translate(self):
self.parts['pseudoxml'] = self.document.pformat() # get pseudoxml before HTML5.translate
self.document.reporter.debug('%s pseudoxml:\n %s' %
(self.__class__.__name__, self.parts['pseudoxml']))
visitor = self.translator_class(self.document)
self.document.walkabout(visitor)
self.output = visitor.output
self.head = visitor.head
self.body = visitor.body
self.title = visitor.title
self.docinfo = visitor.docinfo
return
def assemble_parts(self):
writers.Writer.assemble_parts(self)
self.parts['head'] = self.head
self.parts['body'] = self.body
self.parts['title'] = self.title
self.parts['docinfo'] = self.docinfo
return
def get_transforms(self):
return writers.Writer.get_transforms(self) + [FooterToBottom]
class ElemStack(object):
'''
Helper class to handle nested contexts and indentation
'''
def __init__(self, settings):
self.stack = []
self.indent_level = 0
self.indent_output = settings.indent_output
self.indent_width = settings.tab_width
def _indent_elem(self, element, indent):
result = []
# Indentation schema:
#
# current position
# |
# v
# <tag>|
# | indent |<elem>
# |indent-1|</tag>
# ^
# ends here
if self.indent_output and indent:
indentation = '\n' + self.indent_width * self.indent_level * ' '
result.append(indentation)
result.append(element)
if self.indent_output and indent:
indentation = '\n' + self.indent_width * (self.indent_level - 1) * ' '
result.append(indentation)
return result
def append(self, element, indent=True):
'''
Append to current element
'''
self.stack[-1].append(self._indent_elem(element, indent))
return
def begin_elem(self):
'''
Start a new element context
'''
self.stack.append([])
self.indent_level += 1
return
def commit_elem(self, elem, indent=True):
'''
A new element is create by removing its stack to make a tag.
This tag is pushed back into its parent's stack.
'''
pop = self.stack.pop()
elem(*pop)
self.indent_level -= 1
self.append(elem, indent)
return
def pop(self):
return self.pop_elements(1)[0]
def pop_elements(self, num_elements):
assert num_elements > 0
parent_stack = self.stack[-1]
result = []
for x in range(num_elements):
pop = parent_stack.pop()
elem = pop[0 if len(pop) == 1 else self.indent_output]
result.append(elem)
result.reverse()
return result
dv = 'default_visit'
dp = 'default_departure'
pass_ = 'no_op'
class HTML5Translator(nodes.NodeVisitor):
rst_terms = {
# 'term': ('tag', 'visit_func', 'depart_func', use_term_in_class,
# indent_elem)
# use_term_in_class and indent_elem are optionals.
# If not given, the default is False, True
'Text': (None, 'visit_Text', None),
'abbreviation': ('abbr', dv, dp),
'acronym': (None, dv, dp),
'address': (None, 'visit_address', None),
'admonition': ('aside', 'visit_aside', 'depart_aside', True),
'attention': ('aside', 'visit_aside', 'depart_aside', True),
'attribution': ('p', dv, dp, True),
'author': (None, 'visit_bibliographic_field', None),
'authors': (None, 'visit_authors', None),
'block_quote': ('blockquote', 'visit_blockquote', dp),
'bullet_list': ('ul', dv, dp, False),
'caption': ('figcaption', dv, dp, False),
'caution': ('aside', 'visit_aside', 'depart_aside', True),
'citation': (None, 'visit_citation', 'depart_citation', True),
'citation_reference': ('a', 'visit_citation_reference',
'depart_reference', True, False),
'classifier': (None, 'visit_classifier', None),
'colspec': (None, pass_, 'depart_colspec'),
'comment': (None, 'visit_comment', None),
'compound': ('div', dv, dp),
'contact': (None, 'visit_bibliographic_field', None),
'container': ('div', dv, dp),
'copyright': (None, 'visit_bibliographic_field', None),
'danger': ('aside', 'visit_aside', 'depart_aside', True),
'date': (None, 'visit_bibliographic_field', None),
'decoration': (None, 'do_nothing', None),
'definition': ('dd', dv, dp),
'definition_list': ('dl', dv, dp),
'definition_list_item': (None, 'do_nothing', None),
'description': ('td', dv, dp),
'docinfo': (None, 'do_nothing', None),
'doctest_block': ('pre', 'visit_literal_block', 'depart_literal_block', True),
'document': (None, 'visit_document', 'depart_document'),
'emphasis': ('em', dv, dp, False, False),
'entry': (None, dv, 'depart_entry'),
'enumerated_list': ('ol', dv, 'depart_enumerated_list'),
'error': ('aside', 'visit_aside', 'depart_aside', True),
'field': (None, 'visit_field', None),
'field_body': (None, 'do_nothing', None),
'field_list': (None, 'do_nothing', None),
'field_name': (None, 'do_nothing', None),
'figure': (None, 'visit_figure', dp),
'footer': (None, dv, dp),
'footnote': (None, 'visit_citation', 'depart_citation', True),
'footnote_reference': ('a', 'visit_citation_reference', 'depart_reference', True, False),
'generated': (None, 'do_nothing', None),
'header': (None, dv, dp),
'hint': ('aside', 'visit_aside', 'depart_aside', True),
'image': ('img', dv, dp),
'important': ('aside', 'visit_aside', 'depart_aside', True),
'inline': ('span', dv, dp, False, False),
'label': ('th', 'visit_reference', 'depart_label'),
'legend': ('div', dv, dp, True),
'line': (None, 'visit_line', None),
'line_block': ('pre', 'visit_line_block', 'depart_line_block', True),
'list_item': ('li', dv, dp),
'literal': ('code', 'visit_literal', 'depart_literal', False, False),
'literal_block': ('pre', 'visit_literal_block', 'depart_literal_block'),
'math': (None, 'visit_math_block', None),
'math_block': (None, 'visit_math_block', None),
'meta': (None, 'visit_meta', None),
'note': ('aside', 'visit_aside', 'depart_aside', True),
'option': ('kbd', 'visit_option', dp, False, False),
'option_argument': ('var', 'visit_option_argument', dp, False, False),
'option_group': ('td', 'visit_option_group', 'depart_option_group'),
'option_list': (None, 'visit_option_list', 'depart_option_list', True),
'option_list_item': ('tr', dv, dp),
'option_string': (None, 'do_nothing', None),
'organization': (None, 'visit_bibliographic_field', None),
'paragraph': ('p', 'visit_paragraph', dp),
'pending': (None, dv, dp),
'problematic': ('a', 'visit_problematic', 'depart_reference', True, False),
'raw': (None, 'visit_raw', None),
'reference': ('a', 'visit_reference', 'depart_reference', False, False),
'revision': (None, 'visit_bibliographic_field', None),
'row': ('tr', 'visit_row', 'depart_row'),
'rubric': ('p', dv, 'depart_rubric', True),
'section': ('section', 'visit_section', 'depart_section'),
'sidebar': ('aside', 'visit_aside', 'depart_aside', True),
'status': (None, 'visit_bibliographic_field', None),
'strong': (None, dv, dp, False, False),
'subscript': ('sub', dv, dp, False, False),
'substitution_definition': (None, 'skip_node', None),
'substitution_reference': (None, 'skip_node', None),
'subtitle': (None, 'visit_target', 'depart_subtitle'),
'superscript': ('sup', dv, dp, False, False),
'system_message': ('div', 'visit_system_message', dp),
'table': (None, 'visit_table', 'depart_table'),
'target': ('a', 'visit_target', 'depart_reference', False, False),
'tbody': (None, dv, dp),
'term': ('dt', dv, dp),
'tgroup': (None, 'do_nothing', None),
'thead': (None, 'visit_thead', 'depart_thead'),
'tip': ('aside', 'visit_aside', 'depart_aside', True),
'title': (None, dv, 'depart_title'),
'title_reference': ('cite', dv, dp, False, False),
'topic': ('aside', 'visit_aside', 'depart_aside', True),
'transition': ('hr', dv, dp),
'version': (None, 'visit_bibliographic_field', None),
'warning': ('aside', 'visit_aside', 'depart_aside', True),
}
default_template = '<!DOCTYPE html>\n<html{html_attr}>\n' \
'<head>{head}</head>\n<body>{body}</body>\n</html>'
def _map_terms_to_functions(self):
'''
Map terms to visit and departure functions
'''
for term, spec in self.rst_terms.items():
visit_func = spec[1] and getattr(self, spec[1], self.unknown_visit)
depart_func = spec[2] and getattr(self, spec[2], self.unknown_departure)
if visit_func:
setattr(self, 'visit_' + term, visit_func)
if depart_func:
setattr(self, 'depart_' + term, depart_func)
return
def __init__(self, document):
nodes.NodeVisitor.__init__(self, document)
self.heading_level = int(getattr(self.document.settings, 'initial_header_level', 0))
self.context = ElemStack(document.settings)
self.docinfo = OrderedDict()
self._parse_params()
self._map_terms_to_functions()
return
def _parse_params(self):
self.metatags = [tag.meta(charset=self.document.settings.output_encoding)]
self.stylesheets = []
stylesheets = self.document.settings.stylesheet or []
for href in stylesheets:
self.stylesheets.append(tag.link(rel='stylesheet', href=href))
self.scripts = []
scripts = self.document.settings.script or []
for src, attributes in scripts:
script = tag.script(src=src)
if attributes:
script = script(**{attributes: attributes})
self.scripts.append(script)
return
def indent_head(self):
if self.document.settings.indent_output:
indent = '\n' + ' ' * self.document.settings.tab_width
result = []
for f in self.head:
result.append(tag(indent, f))
result.append('\n')
self.head = result
return
def _get_template(self):
template = self.document.settings.template
if not template:
return self.default_template
import os
if os.path.isfile(template):
from io import open
with open(template, 'r', encoding='utf-8') as template_file:
return template_file.read()
else:
return template
def _get_template_values(self):
html_attrs = self.document.settings.html_tag_attr
html_attrs = html_attrs and ' ' + ' '.join(html_attrs) or ''
self.head = self.metatags + self.stylesheets + self.scripts
for key, value in self.docinfo.items():
self.head.append(tag.meta(content=value, name=key))
self.indent_head()
self.head = ''.join(XHTMLSerializer()(tag(*self.head)))
self.body = ''.join(XHTMLSerializer()(tag(*self.context.stack)))
values = {}
values['html_attr'] = html_attrs
values['head'] = self.head
values['body'] = self.body
return values
@property
def output(self):
template = self._get_template()
values = self._get_template_values()
return template.format(**values)
def parse(self, node):
'''
Get tag name, indentantion and correct attributes of a node according
to its class
'''
node_class_name = node.__class__.__name__
spec = self.rst_terms[node_class_name]
tag_name = spec[0] or node_class_name
use_name_in_class = len(spec) > 3 and spec[3]
indent = spec[4] if len(spec) > 4 else True
if use_name_in_class:
node['classes'].insert(0, node_class_name)
replacements = {
'refuri': 'href', 'uri': 'src', 'refid': 'href',
'morerows': 'rowspan', 'morecols': 'colspan', 'classes': 'class',
'ids': 'id',
}
ignores = (
'names', 'dupnames', 'bullet', 'enumtype', 'colwidth', 'stub',
'backrefs', 'auto', 'anonymous',
)
attributes = {}
for k, v in node.attributes.items():
if k in ignores or not v:
continue
if k in replacements:
k = replacements[k]
if isinstance(v, list):
v = ' '.join(v)
attributes[k] = v
return tag_name, indent, attributes
def once_attr(self, name, default=None):
'''
The attribute is used once and then it is deleted
'''
if hasattr(self, name):
result = getattr(self, name)
delattr(self, name)
return result
else:
return default
[docs] def default_visit(self, node):
'''
Initiate a new context to store inner HTML5 elements.
'''
if 'ids' in node and self.once_attr('expand_id_to_anchor', default=True):
# create an anchor <a id=id></a> for each id found before the
# current element.
for id in node['ids'][1:]:
self.context.begin_elem()
self.context.commit_elem(tag.a(id=id))
node.attributes['ids'] = node.attributes['ids'][0:1]
self.context.begin_elem()
return
[docs] def default_departure(self, node):
'''
Create the node's corresponding HTML5 element and combine it with its
stored context.
'''
tag_name, indent, attributes = self.parse(node)
elem = getattr(tag, tag_name)(**attributes)
self.context.commit_elem(elem, indent)
return
def _compacted_paragraph(self, node):
"""
Determine if the <p> tags around paragraph ``node`` can be omitted.
Based on :func:`docutils.writers.html4css1.HTMLTranslator.should_be_compact_paragraph`
"""
# extra parenthesis for pep8 alignment conformity
if ((isinstance(node.parent, (nodes.document, nodes.compound,
nodes.block_quote, nodes.system_message, )) or
node['classes'] or 'paragraph' != node.__class__.__name__)):
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
if isinstance(node.parent, nodes.list_item):
# first child of a list_item
return True
parent_length = len([n for n in node.parent if not isinstance(
n, (nodes.Invisible, nodes.label))])
return parent_length == 1
def visit_paragraph(self, node):
if self._compacted_paragraph(node):
raise nodes.SkipDeparture
self.default_visit(node)
def _strip_spaces(self, text):
return re.sub(r'\s+', ' ', text)
[docs] def visit_Text(self, node):
text = node.astext()
if not getattr(self, 'preserve_space', None):
text = self._strip_spaces(text)
self.context.append(text, indent=False)
raise nodes.SkipDeparture
def visit_section(self, node):
self.heading_level += 1
self.default_visit(node)
def depart_section(self, node):
self.heading_level -= 1
self.default_departure(node)
def depart_title(self, node):
spec, indent, attr = self.parse(node)
if isinstance(node.parent, nodes.table):
elem = tag.caption(**attr)
else:
assert self.heading_level >= 0
if self.heading_level == 0:
self.heading_level = 1
if 'href' in attr:
# backref to toc entry
del attr['href']
elem = getattr(tag, 'h' + unicode(self.heading_level))(**attr)
self.context.commit_elem(elem, indent)
def depart_subtitle(self, node):
# mount the subtitle heading
subheading_level = getattr(tag, 'h' + unicode(self.heading_level + 1))
self.context.commit_elem(subheading_level)
self.heading_level -= 1
def depart_enumerated_list(self, node):
'''
Ordered list.
It may have a preffix and suffix that must be handled by CSS3 and
javascript to be presented as intended.
.. seealso::
* `Automatic numbering with CSS Counters <http://goo.gl/nXq02>`_
* `How to add brackets to an ordered list? <http://goo.gl/Tdzmf>`_
'''
if 'enumtype' in node:
enumtypes = {
'arabic': '1',
'loweralpha': 'a',
'upperalpha': 'A',
'lowerroman': 'i',
'upperroman': 'I'
}
node['type'] = enumtypes.get(node['enumtype'], '1')
if node.get('suffix') == '.' and 'preffix' not in node:
# default suffix doesn't need special treatment
del node['suffix']
self.default_departure(node)
def skip_node(self, node):
"""Internal only"""
raise nodes.SkipNode
def no_op(self, node):
pass
def do_nothing(self, node):
'''
equivalent to visit: pass and depart: pass
'''
raise nodes.SkipDeparture
def visit_classifier(self, node):
'''
Classifier should remain beside the previous element
'''
term = self.context.pop()
term(' ', tag.span(':', class_='classifier-delimiter'), ' ',
tag.span(node.astext(), class_='classifier'))
self.context.append(term)
raise nodes.SkipNode
#
# table
#
def visit_table(self, node):
self.th_required = 0
self.in_thead = False
self.default_visit(node)
def depart_table(self, node):
del self.th_required
del self.in_thead
self.default_departure(node)
def depart_colspec(self, node):
'''
<col /> tags are not generated anymore because they're pretty useless
since they cannot receive any attribute from a rst table.
Anyway, there are better ways to apply styles to columns.
See http://csswizardry.com/demos/zebra-striping/ for example.
Nevertheless,
if a colspec node with a "stub" attribute indicates that the column should be a th tag.
'''
if 'stub' in node:
self.th_required += 1
return
def visit_thead(self, node):
self.in_thead = True
self.default_visit(node)
def depart_thead(self, node):
self.in_thead = False
self.default_departure(node)
def visit_row(self, node):
self.th_available = self.th_required
self.default_visit(node)
def depart_row(self, node):
del self.th_available
self.default_departure(node)
def depart_entry(self, node):
if self.in_thead or self.th_available:
name = 'th'
self.th_available -= 1
else:
name = 'td'
if 'morerows' in node:
node['morerows'] = node['morerows'] + 1
if 'morecols' in node:
node['morecols'] = node['morecols'] + 1
waste, indent, attr = self.parse(node)
self.context.commit_elem(getattr(tag, name)(**attr))
def visit_reference(self, node):
if 'ids' in node:
del node.attributes['ids']
self.default_visit(node)
return
def depart_reference(self, node):
if 'name' in node:
del node['name']
if 'refid' in node:
node['refid'] = '#' + node['refid']
self.default_departure(node)
return
def visit_target(self, node):
if not node.astext():
raise nodes.SkipNode
self.expand_id_to_anchor = False
self.default_visit(node)
def visit_literal(self, node):
self.preserve_space = getattr(self, 'preserve_space', 0) + 1
self.default_visit(node)
return
def depart_literal(self, node):
self.preserve_space -= 1
if self.preserve_space == 0:
del self.preserve_space
if node['classes']:
node['classes'] = node['classes'][-1]
self.default_departure(node)
return
def visit_literal_block(self, node):
'''
Translates a code-block/sourcecode or a parsed-literal block.
Pygments is used for code-blocks.
However, its highlight output is cluttered and thus
rst2html5 cleans it up to a more HTML5 style.
rst2html5 does not apply a class attribute such as class="sourcecode"
to code-block elements. Instead, these elements should be addressed in CSS3 using
'[data-language]', 'pre[data-language]' or 'table[data-language]' selectors.
'''
if 'language' in node:
# code-block
language = node['language']
linenos = node['linenos']
highlight_args = node.get('highlight_args', {})
classes = ' '.join(node['classes']) or None
codeblock = pygmentize_to_tag(node.rawsource, language, linenos=linenos, **highlight_args)
codeblock(class_=classes)
self.context.append(codeblock)
raise nodes.SkipNode
# see case parsed_literal_as_code_block
language = None
if 'classes' in node and 'code' in node['classes']:
for c in node['classes']:
if c.startswith('language-'):
language = c
break
if language:
node['classes'].remove('code')
node['classes'].remove(language)
node.attributes['data-language'] = language[len('language-')::]
self.preserve_space = 1
self.default_visit(node)
return
def depart_literal_block(self, node):
del self.preserve_space
self.default_departure(node)
return
def visit_math_block(self, node):
'''
Only MathJax support
'''
math_code = node.astext()
math_env = pick_math_environment(math_code)
if 'align' in math_env:
template = '\\begin{%s}\n%s\n\\end{%s}' % (math_env, math_code, math_env)
elem = tag.div(template)
else: # equation
template = '\(%s\)' % math_code
elem = tag.span(template)
elem(class_='math')
self.context.append(elem)
if not getattr(self, 'already_has_math_script', None):
src = "http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"
self.scripts.append(tag.script(src=src))
self.already_has_math_script = True
raise nodes.SkipNode
def visit_document(self, node):
if 'title' in node:
self.metatags.insert(0, tag.title(node['title']))
self.title = node['title']
else:
self.title = ''
self.expand_id_to_anchor = False
self.default_visit(node)
def depart_document(self, node):
self.context.stack = self.context.stack[0]
return
def visit_raw(self, node):
if 'html' in node.get('format', '').split():
for line in node.astext().splitlines():
self.context.append(Markup(line))
raise nodes.SkipNode
def visit_aside(self, node):
self.save_heading_level = self.heading_level
self.heading_level = 1
self.default_visit(node)
def depart_aside(self, node):
self.heading_level = self.save_heading_level
del self.save_heading_level
if node['classes'] and node['classes'][0].startswith('admonition-'):
del node['classes'][0]
self.default_departure(node)
def depart_rubric(self, node):
node['classes'] = []
self.default_departure(node)
def visit_field(self, node):
self.docinfo[node.children[0].astext()] = self._strip_spaces(node.children[1].astext())
raise nodes.SkipNode
def visit_bibliographic_field(self, node):
self.docinfo[node.__class__.__name__] = self._strip_spaces(node.astext())
raise nodes.SkipNode
def visit_address(self, node):
self.docinfo[node.__class__.__name__] = ', '.join(node.astext().split('\n'))
raise nodes.SkipNode
def visit_authors(self, node):
self.docinfo[node.__class__.__name__] = '; '.join(node.astext().split())
raise nodes.SkipNode
def visit_option_list(self, node):
self.context.begin_elem() # table
self.context.begin_elem() # tbody
def depart_option_list(self, node):
self.context.commit_elem(tag.tbody)
waste, waste_, attr = self.parse(node)
self.context.commit_elem(tag.table(**attr))
def visit_citation(self, node):
self.visit_option_list(node)
self.context.begin_elem() # tr
def depart_citation(self, node):
# td initiated at depart_label
self.context.commit_elem(tag.td)
self.context.commit_elem(tag.tr)
self.depart_option_list(node)
def visit_option_group(self, node):
self.option_level = 0
self.default_visit(node)
def depart_option_group(self, node):
if self.document.settings.option_limit and \
len(node.astext()) > self.document.settings.option_limit:
node['morecols'] = 2
self.default_departure(node)
self.context.commit_elem(tag.tr) # closes this tr
self.context.begin_elem() # begins another tr
self.context.append(tag.td) # empty td due to colspan
else:
self.default_departure(node)
def visit_option(self, node):
if self.option_level:
self.context.append(', ', indent=False)
self.option_level += 1
self.default_visit(node)
def visit_option_argument(self, node):
if 'delimiter' in node:
self.context.append(node['delimiter'], indent=False)
del node.attributes['delimiter']
self.default_visit(node)
def visit_citation_reference(self, node):
"""
Instead of a typical visit_reference call this def is required to
remove the backref id that is included but not used in rst2html5.
"""
self.expand_id_to_anchor = False
self.default_visit(node)
def depart_label(self, node):
self.default_departure(node)
self.context.begin_elem() # next td
def visit_line(self, node):
self.line_level = getattr(self, 'line_level', -1) + 1
if self.line_level:
tab_width = self.document.settings.tab_width
separator = '\n' + ' ' * tab_width * (self.line_block_level - 1)
self.context.append(separator, indent=False)
raise nodes.SkipDeparture
def visit_line_block(self, node):
'''
Line blocks use <pre>.
Lines breaks and spacing are reconstructured based on line_block_level
'''
self.line_block_level = getattr(self, 'line_block_level', 0) + 1
if self.line_block_level == 1:
self.default_visit(node)
def depart_line_block(self, node):
self.line_block_level -= 1
if self.line_block_level == 0:
del self.line_block_level
del self.line_level
self.default_departure(node)
def visit_meta(self, node):
waste, waste_, attr = self.parse(node)
self.metatags.append(tag.meta(**attr))
raise nodes.SkipNode
def visit_problematic(self, node):
self.expand_id_to_anchor = False
if len(node['ids']) > 1:
node['ids'] = node['ids'][0]
self.default_visit(node)
return
def visit_system_message(self, node):
self.default_visit(node)
self.context.begin_elem() # h1
backrefs = [tag(' ', tag.a(v, href="#" + v)) for v in node['backrefs']]
node.attributes.setdefault('line', '')
text = 'System Message: {type}/{level} ({source} line ' \
'{line})'.format(**node.attributes)
h1 = tag.h1(text, *backrefs)
self.context.commit_elem(h1)
node.attributes = {'classes': [], 'ids': node.attributes['ids']}
return
def visit_figure(self, node):
# move up the ids of child img
for child in node:
if isinstance(child, nodes.image) and 'ids' in child:
node['ids'].extend(child['ids'])
child['ids'] = []
self.default_visit(node)
def visit_comment(self, node):
text = node.astext()
if text:
self.context.begin_elem()
comment = tag(Markup('<!-- '), text, Markup(' -->'))
self.context.commit_elem(comment)
raise nodes.SkipNode
def visit_blockquote(self, node):
if isinstance(node.parent, nodes.list_item):
raise nodes.SkipDeparture
self.default_visit(node)
def main():
from docutils.core import publish_cmdline, default_description
description = 'Generates (X)HTML5 documents from standalone ' \
'reStructuredText sources.' + default_description
publish_cmdline(writer=HTML5Writer(), description=description)
if __name__ == '__main__':
main()