mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Factor out HTML 4 translator (#11051)
Move the HTML 4 translator into a private module.
This commit is contained in:
parent
aa2fa38fef
commit
bf06d7ef4d
@ -43,7 +43,8 @@ from sphinx.util.inventory import InventoryFile
|
|||||||
from sphinx.util.matching import DOTFILES, Matcher, patmatch
|
from sphinx.util.matching import DOTFILES, Matcher, patmatch
|
||||||
from sphinx.util.osutil import copyfile, ensuredir, os_path, relative_uri
|
from sphinx.util.osutil import copyfile, ensuredir, os_path, relative_uri
|
||||||
from sphinx.util.tags import Tags
|
from sphinx.util.tags import Tags
|
||||||
from sphinx.writers.html import HTMLTranslator, HTMLWriter
|
from sphinx.writers._html4 import HTML4Translator
|
||||||
|
from sphinx.writers.html import HTMLWriter
|
||||||
from sphinx.writers.html5 import HTML5Translator
|
from sphinx.writers.html5 import HTML5Translator
|
||||||
|
|
||||||
#: the filename for the inventory of objects
|
#: the filename for the inventory of objects
|
||||||
@ -372,7 +373,7 @@ class StandaloneHTMLBuilder(Builder):
|
|||||||
@property
|
@property
|
||||||
def default_translator_class(self) -> Type[nodes.NodeVisitor]: # type: ignore
|
def default_translator_class(self) -> Type[nodes.NodeVisitor]: # type: ignore
|
||||||
if self.config.html4_writer:
|
if self.config.html4_writer:
|
||||||
return HTMLTranslator # RemovedInSphinx70Warning
|
return HTML4Translator # RemovedInSphinx70Warning
|
||||||
else:
|
else:
|
||||||
return HTML5Translator
|
return HTML5Translator
|
||||||
|
|
||||||
@ -1326,8 +1327,12 @@ import sphinx.builders.singlehtml # noqa: E402,F401
|
|||||||
deprecated_alias('sphinx.builders.html',
|
deprecated_alias('sphinx.builders.html',
|
||||||
{
|
{
|
||||||
'html5_ready': True,
|
'html5_ready': True,
|
||||||
|
'HTMLTranslator': HTML4Translator,
|
||||||
},
|
},
|
||||||
RemovedInSphinx70Warning)
|
RemovedInSphinx70Warning,
|
||||||
|
{
|
||||||
|
'HTMLTranslator': 'sphinx.writers.html.HTML5Translator',
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
def setup(app: Sphinx) -> Dict[str, Any]:
|
def setup(app: Sphinx) -> Dict[str, Any]:
|
||||||
|
@ -84,7 +84,7 @@ from sphinx.util.docutils import (NullReporter, SphinxDirective, SphinxRole, new
|
|||||||
from sphinx.util.inspect import signature_from_str
|
from sphinx.util.inspect import signature_from_str
|
||||||
from sphinx.util.matching import Matcher
|
from sphinx.util.matching import Matcher
|
||||||
from sphinx.util.typing import OptionSpec
|
from sphinx.util.typing import OptionSpec
|
||||||
from sphinx.writers.html import HTMLTranslator
|
from sphinx.writers.html import HTML5Translator
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -116,7 +116,7 @@ class autosummary_table(nodes.comment):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def autosummary_table_visit_html(self: HTMLTranslator, node: autosummary_table) -> None:
|
def autosummary_table_visit_html(self: HTML5Translator, node: autosummary_table) -> None:
|
||||||
"""Make the first column of the table non-breaking."""
|
"""Make the first column of the table non-breaking."""
|
||||||
try:
|
try:
|
||||||
table = cast(nodes.table, node[0])
|
table = cast(nodes.table, node[0])
|
||||||
|
@ -23,7 +23,7 @@ from sphinx.util.i18n import search_image_for_language
|
|||||||
from sphinx.util.nodes import set_source_info
|
from sphinx.util.nodes import set_source_info
|
||||||
from sphinx.util.osutil import ensuredir
|
from sphinx.util.osutil import ensuredir
|
||||||
from sphinx.util.typing import OptionSpec
|
from sphinx.util.typing import OptionSpec
|
||||||
from sphinx.writers.html import HTMLTranslator
|
from sphinx.writers.html import HTML5Translator
|
||||||
from sphinx.writers.latex import LaTeXTranslator
|
from sphinx.writers.latex import LaTeXTranslator
|
||||||
from sphinx.writers.manpage import ManualPageTranslator
|
from sphinx.writers.manpage import ManualPageTranslator
|
||||||
from sphinx.writers.texinfo import TexinfoTranslator
|
from sphinx.writers.texinfo import TexinfoTranslator
|
||||||
@ -262,7 +262,7 @@ def render_dot(self: SphinxTranslator, code: str, options: Dict, format: str,
|
|||||||
'[stdout]\n%r') % (exc.stderr, exc.stdout)) from exc
|
'[stdout]\n%r') % (exc.stderr, exc.stdout)) from exc
|
||||||
|
|
||||||
|
|
||||||
def render_dot_html(self: HTMLTranslator, node: graphviz, code: str, options: Dict,
|
def render_dot_html(self: HTML5Translator, node: graphviz, code: str, options: Dict,
|
||||||
prefix: str = 'graphviz', imgcls: Optional[str] = None,
|
prefix: str = 'graphviz', imgcls: Optional[str] = None,
|
||||||
alt: Optional[str] = None, filename: Optional[str] = None
|
alt: Optional[str] = None, filename: Optional[str] = None
|
||||||
) -> Tuple[str, str]:
|
) -> Tuple[str, str]:
|
||||||
@ -315,7 +315,7 @@ def render_dot_html(self: HTMLTranslator, node: graphviz, code: str, options: Di
|
|||||||
raise nodes.SkipNode
|
raise nodes.SkipNode
|
||||||
|
|
||||||
|
|
||||||
def html_visit_graphviz(self: HTMLTranslator, node: graphviz) -> None:
|
def html_visit_graphviz(self: HTML5Translator, node: graphviz) -> None:
|
||||||
render_dot_html(self, node, node['code'], node['options'], filename=node.get('filename'))
|
render_dot_html(self, node, node['code'], node['options'], filename=node.get('filename'))
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ from sphinx.util.math import get_node_equation_number, wrap_displaymath
|
|||||||
from sphinx.util.osutil import ensuredir
|
from sphinx.util.osutil import ensuredir
|
||||||
from sphinx.util.png import read_png_depth, write_png_depth
|
from sphinx.util.png import read_png_depth, write_png_depth
|
||||||
from sphinx.util.template import LaTeXRenderer
|
from sphinx.util.template import LaTeXRenderer
|
||||||
from sphinx.writers.html import HTMLTranslator
|
from sphinx.writers.html import HTML5Translator
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -201,7 +201,7 @@ def convert_dvi_to_svg(dvipath: str, builder: Builder, out_path: str) -> Optiona
|
|||||||
|
|
||||||
|
|
||||||
def render_math(
|
def render_math(
|
||||||
self: HTMLTranslator,
|
self: HTML5Translator,
|
||||||
math: str,
|
math: str,
|
||||||
) -> Tuple[Optional[str], Optional[int]]:
|
) -> Tuple[Optional[str], Optional[int]]:
|
||||||
"""Render the LaTeX math expression *math* using latex and dvipng or
|
"""Render the LaTeX math expression *math* using latex and dvipng or
|
||||||
@ -291,13 +291,13 @@ def clean_up_files(app: Sphinx, exc: Exception) -> None:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def get_tooltip(self: HTMLTranslator, node: Element) -> str:
|
def get_tooltip(self: HTML5Translator, node: Element) -> str:
|
||||||
if self.builder.config.imgmath_add_tooltips:
|
if self.builder.config.imgmath_add_tooltips:
|
||||||
return ' alt="%s"' % self.encode(node.astext()).strip()
|
return ' alt="%s"' % self.encode(node.astext()).strip()
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|
||||||
def html_visit_math(self: HTMLTranslator, node: nodes.math) -> None:
|
def html_visit_math(self: HTML5Translator, node: nodes.math) -> None:
|
||||||
try:
|
try:
|
||||||
rendered_path, depth = render_math(self, '$' + node.astext() + '$')
|
rendered_path, depth = render_math(self, '$' + node.astext() + '$')
|
||||||
except MathExtError as exc:
|
except MathExtError as exc:
|
||||||
@ -326,7 +326,7 @@ def html_visit_math(self: HTMLTranslator, node: nodes.math) -> None:
|
|||||||
raise nodes.SkipNode
|
raise nodes.SkipNode
|
||||||
|
|
||||||
|
|
||||||
def html_visit_displaymath(self: HTMLTranslator, node: nodes.math_block) -> None:
|
def html_visit_displaymath(self: HTML5Translator, node: nodes.math_block) -> None:
|
||||||
if node['nowrap']:
|
if node['nowrap']:
|
||||||
latex = node.astext()
|
latex = node.astext()
|
||||||
else:
|
else:
|
||||||
|
@ -47,7 +47,7 @@ from sphinx.ext.graphviz import (figure_wrapper, graphviz, render_dot_html, rend
|
|||||||
from sphinx.util import md5
|
from sphinx.util import md5
|
||||||
from sphinx.util.docutils import SphinxDirective
|
from sphinx.util.docutils import SphinxDirective
|
||||||
from sphinx.util.typing import OptionSpec
|
from sphinx.util.typing import OptionSpec
|
||||||
from sphinx.writers.html import HTMLTranslator
|
from sphinx.writers.html import HTML5Translator
|
||||||
from sphinx.writers.latex import LaTeXTranslator
|
from sphinx.writers.latex import LaTeXTranslator
|
||||||
from sphinx.writers.texinfo import TexinfoTranslator
|
from sphinx.writers.texinfo import TexinfoTranslator
|
||||||
|
|
||||||
@ -387,7 +387,7 @@ def get_graph_hash(node: inheritance_diagram) -> str:
|
|||||||
return md5(encoded).hexdigest()[-10:]
|
return md5(encoded).hexdigest()[-10:]
|
||||||
|
|
||||||
|
|
||||||
def html_visit_inheritance_diagram(self: HTMLTranslator, node: inheritance_diagram) -> None:
|
def html_visit_inheritance_diagram(self: HTML5Translator, node: inheritance_diagram) -> None:
|
||||||
"""
|
"""
|
||||||
Output the graph for HTML. This will insert a PNG with clickable
|
Output the graph for HTML. This will insert a PNG with clickable
|
||||||
image map.
|
image map.
|
||||||
|
@ -16,7 +16,7 @@ from sphinx.domains.math import MathDomain
|
|||||||
from sphinx.errors import ExtensionError
|
from sphinx.errors import ExtensionError
|
||||||
from sphinx.locale import _
|
from sphinx.locale import _
|
||||||
from sphinx.util.math import get_node_equation_number
|
from sphinx.util.math import get_node_equation_number
|
||||||
from sphinx.writers.html import HTMLTranslator
|
from sphinx.writers.html import HTML5Translator
|
||||||
|
|
||||||
# more information for mathjax secure url is here:
|
# more information for mathjax secure url is here:
|
||||||
# https://docs.mathjax.org/en/latest/start.html#secure-access-to-the-cdn
|
# https://docs.mathjax.org/en/latest/start.html#secure-access-to-the-cdn
|
||||||
@ -25,7 +25,7 @@ MATHJAX_URL = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js'
|
|||||||
logger = sphinx.util.logging.getLogger(__name__)
|
logger = sphinx.util.logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def html_visit_math(self: HTMLTranslator, node: nodes.math) -> None:
|
def html_visit_math(self: HTML5Translator, node: nodes.math) -> None:
|
||||||
self.body.append(self.starttag(node, 'span', '', CLASS='math notranslate nohighlight'))
|
self.body.append(self.starttag(node, 'span', '', CLASS='math notranslate nohighlight'))
|
||||||
self.body.append(self.builder.config.mathjax_inline[0] +
|
self.body.append(self.builder.config.mathjax_inline[0] +
|
||||||
self.encode(node.astext()) +
|
self.encode(node.astext()) +
|
||||||
@ -33,7 +33,7 @@ def html_visit_math(self: HTMLTranslator, node: nodes.math) -> None:
|
|||||||
raise nodes.SkipNode
|
raise nodes.SkipNode
|
||||||
|
|
||||||
|
|
||||||
def html_visit_displaymath(self: HTMLTranslator, node: nodes.math_block) -> None:
|
def html_visit_displaymath(self: HTML5Translator, node: nodes.math_block) -> None:
|
||||||
self.body.append(self.starttag(node, 'div', CLASS='math notranslate nohighlight'))
|
self.body.append(self.starttag(node, 'div', CLASS='math notranslate nohighlight'))
|
||||||
if node['nowrap']:
|
if node['nowrap']:
|
||||||
self.body.append(self.encode(node.astext()))
|
self.body.append(self.encode(node.astext()))
|
||||||
|
@ -22,7 +22,7 @@ from sphinx.locale import _, __
|
|||||||
from sphinx.util import logging, texescape
|
from sphinx.util import logging, texescape
|
||||||
from sphinx.util.docutils import SphinxDirective, new_document
|
from sphinx.util.docutils import SphinxDirective, new_document
|
||||||
from sphinx.util.typing import OptionSpec
|
from sphinx.util.typing import OptionSpec
|
||||||
from sphinx.writers.html import HTMLTranslator
|
from sphinx.writers.html import HTML5Translator
|
||||||
from sphinx.writers.latex import LaTeXTranslator
|
from sphinx.writers.latex import LaTeXTranslator
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -188,14 +188,14 @@ class TodoListProcessor:
|
|||||||
self.document.remove(todo)
|
self.document.remove(todo)
|
||||||
|
|
||||||
|
|
||||||
def visit_todo_node(self: HTMLTranslator, node: todo_node) -> None:
|
def visit_todo_node(self: HTML5Translator, node: todo_node) -> None:
|
||||||
if self.config.todo_include_todos:
|
if self.config.todo_include_todos:
|
||||||
self.visit_admonition(node)
|
self.visit_admonition(node)
|
||||||
else:
|
else:
|
||||||
raise nodes.SkipNode
|
raise nodes.SkipNode
|
||||||
|
|
||||||
|
|
||||||
def depart_todo_node(self: HTMLTranslator, node: todo_node) -> None:
|
def depart_todo_node(self: HTML5Translator, node: todo_node) -> None:
|
||||||
self.depart_admonition(node)
|
self.depart_admonition(node)
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,10 +4,10 @@ from typing import Optional
|
|||||||
|
|
||||||
from docutils import nodes
|
from docutils import nodes
|
||||||
|
|
||||||
from sphinx.builders.html import HTMLTranslator
|
from sphinx.builders.html import HTML5Translator
|
||||||
|
|
||||||
|
|
||||||
def get_node_equation_number(writer: HTMLTranslator, node: nodes.math_block) -> str:
|
def get_node_equation_number(writer: HTML5Translator, node: nodes.math_block) -> str:
|
||||||
if writer.builder.config.math_numfig and writer.builder.config.numfig:
|
if writer.builder.config.math_numfig and writer.builder.config.numfig:
|
||||||
figtype = 'displaymath'
|
figtype = 'displaymath'
|
||||||
if writer.builder.name == 'singlehtml':
|
if writer.builder.name == 'singlehtml':
|
||||||
|
856
sphinx/writers/_html4.py
Normal file
856
sphinx/writers/_html4.py
Normal file
@ -0,0 +1,856 @@
|
|||||||
|
"""Frozen HTML 4 translator."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import posixpath
|
||||||
|
import re
|
||||||
|
import urllib.parse
|
||||||
|
from typing import TYPE_CHECKING, Iterable, Optional, Tuple, cast
|
||||||
|
|
||||||
|
from docutils import nodes
|
||||||
|
from docutils.nodes import Element, Node, Text
|
||||||
|
from docutils.writers.html4css1 import HTMLTranslator as BaseTranslator
|
||||||
|
|
||||||
|
from sphinx import addnodes
|
||||||
|
from sphinx.builders import Builder
|
||||||
|
from sphinx.locale import _, __, admonitionlabels
|
||||||
|
from sphinx.util import logging
|
||||||
|
from sphinx.util.docutils import SphinxTranslator
|
||||||
|
from sphinx.util.images import get_image_size
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from sphinx.builders.html import StandaloneHTMLBuilder
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def multiply_length(length: str, scale: int) -> str:
|
||||||
|
"""Multiply *length* (width or height) by *scale*."""
|
||||||
|
matched = re.match(r'^(\d*\.?\d*)\s*(\S*)$', length)
|
||||||
|
if not matched:
|
||||||
|
return length
|
||||||
|
elif scale == 100:
|
||||||
|
return length
|
||||||
|
else:
|
||||||
|
amount, unit = matched.groups()
|
||||||
|
result = float(amount) * scale / 100
|
||||||
|
return "%s%s" % (int(result), unit)
|
||||||
|
|
||||||
|
|
||||||
|
# RemovedInSphinx70Warning
|
||||||
|
class HTML4Translator(SphinxTranslator, BaseTranslator):
|
||||||
|
"""
|
||||||
|
Our custom HTML translator.
|
||||||
|
"""
|
||||||
|
|
||||||
|
builder: "StandaloneHTMLBuilder"
|
||||||
|
|
||||||
|
def __init__(self, document: nodes.document, builder: Builder) -> None:
|
||||||
|
super().__init__(document, builder)
|
||||||
|
|
||||||
|
self.highlighter = self.builder.highlighter
|
||||||
|
self.docnames = [self.builder.current_docname] # for singlehtml builder
|
||||||
|
self.manpages_url = self.config.manpages_url
|
||||||
|
self.protect_literal_text = 0
|
||||||
|
self.secnumber_suffix = self.config.html_secnumber_suffix
|
||||||
|
self.param_separator = ''
|
||||||
|
self.optional_param_level = 0
|
||||||
|
self._table_row_indices = [0]
|
||||||
|
self._fieldlist_row_indices = [0]
|
||||||
|
self.required_params_left = 0
|
||||||
|
|
||||||
|
def visit_start_of_file(self, node: Element) -> None:
|
||||||
|
# only occurs in the single-file builder
|
||||||
|
self.docnames.append(node['docname'])
|
||||||
|
self.body.append('<span id="document-%s"></span>' % node['docname'])
|
||||||
|
|
||||||
|
def depart_start_of_file(self, node: Element) -> None:
|
||||||
|
self.docnames.pop()
|
||||||
|
|
||||||
|
#############################################################
|
||||||
|
# Domain-specific object descriptions
|
||||||
|
#############################################################
|
||||||
|
|
||||||
|
# Top-level nodes for descriptions
|
||||||
|
##################################
|
||||||
|
|
||||||
|
def visit_desc(self, node: Element) -> None:
|
||||||
|
self.body.append(self.starttag(node, 'dl'))
|
||||||
|
|
||||||
|
def depart_desc(self, node: Element) -> None:
|
||||||
|
self.body.append('</dl>\n\n')
|
||||||
|
|
||||||
|
def visit_desc_signature(self, node: Element) -> None:
|
||||||
|
# the id is set automatically
|
||||||
|
self.body.append(self.starttag(node, 'dt'))
|
||||||
|
self.protect_literal_text += 1
|
||||||
|
|
||||||
|
def depart_desc_signature(self, node: Element) -> None:
|
||||||
|
self.protect_literal_text -= 1
|
||||||
|
if not node.get('is_multiline'):
|
||||||
|
self.add_permalink_ref(node, _('Permalink to this definition'))
|
||||||
|
self.body.append('</dt>\n')
|
||||||
|
|
||||||
|
def visit_desc_signature_line(self, node: Element) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def depart_desc_signature_line(self, node: Element) -> None:
|
||||||
|
if node.get('add_permalink'):
|
||||||
|
# the permalink info is on the parent desc_signature node
|
||||||
|
self.add_permalink_ref(node.parent, _('Permalink to this definition'))
|
||||||
|
self.body.append('<br />')
|
||||||
|
|
||||||
|
def visit_desc_content(self, node: Element) -> None:
|
||||||
|
self.body.append(self.starttag(node, 'dd', ''))
|
||||||
|
|
||||||
|
def depart_desc_content(self, node: Element) -> None:
|
||||||
|
self.body.append('</dd>')
|
||||||
|
|
||||||
|
def visit_desc_inline(self, node: Element) -> None:
|
||||||
|
self.body.append(self.starttag(node, 'span', ''))
|
||||||
|
|
||||||
|
def depart_desc_inline(self, node: Element) -> None:
|
||||||
|
self.body.append('</span>')
|
||||||
|
|
||||||
|
# Nodes for high-level structure in signatures
|
||||||
|
##############################################
|
||||||
|
|
||||||
|
def visit_desc_name(self, node: Element) -> None:
|
||||||
|
self.body.append(self.starttag(node, 'code', ''))
|
||||||
|
|
||||||
|
def depart_desc_name(self, node: Element) -> None:
|
||||||
|
self.body.append('</code>')
|
||||||
|
|
||||||
|
def visit_desc_addname(self, node: Element) -> None:
|
||||||
|
self.body.append(self.starttag(node, 'code', ''))
|
||||||
|
|
||||||
|
def depart_desc_addname(self, node: Element) -> None:
|
||||||
|
self.body.append('</code>')
|
||||||
|
|
||||||
|
def visit_desc_type(self, node: Element) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def depart_desc_type(self, node: Element) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def visit_desc_returns(self, node: Element) -> None:
|
||||||
|
self.body.append(' <span class="sig-return">')
|
||||||
|
self.body.append('<span class="sig-return-icon">→</span>')
|
||||||
|
self.body.append(' <span class="sig-return-typehint">')
|
||||||
|
|
||||||
|
def depart_desc_returns(self, node: Element) -> None:
|
||||||
|
self.body.append('</span></span>')
|
||||||
|
|
||||||
|
def visit_desc_parameterlist(self, node: Element) -> None:
|
||||||
|
self.body.append('<span class="sig-paren">(</span>')
|
||||||
|
self.first_param = 1
|
||||||
|
self.optional_param_level = 0
|
||||||
|
# How many required parameters are left.
|
||||||
|
self.required_params_left = sum([isinstance(c, addnodes.desc_parameter)
|
||||||
|
for c in node.children])
|
||||||
|
self.param_separator = node.child_text_separator
|
||||||
|
|
||||||
|
def depart_desc_parameterlist(self, node: Element) -> None:
|
||||||
|
self.body.append('<span class="sig-paren">)</span>')
|
||||||
|
|
||||||
|
# If required parameters are still to come, then put the comma after
|
||||||
|
# the parameter. Otherwise, put the comma before. This ensures that
|
||||||
|
# signatures like the following render correctly (see issue #1001):
|
||||||
|
#
|
||||||
|
# foo([a, ]b, c[, d])
|
||||||
|
#
|
||||||
|
def visit_desc_parameter(self, node: Element) -> None:
|
||||||
|
if self.first_param:
|
||||||
|
self.first_param = 0
|
||||||
|
elif not self.required_params_left:
|
||||||
|
self.body.append(self.param_separator)
|
||||||
|
if self.optional_param_level == 0:
|
||||||
|
self.required_params_left -= 1
|
||||||
|
if not node.hasattr('noemph'):
|
||||||
|
self.body.append('<em>')
|
||||||
|
|
||||||
|
def depart_desc_parameter(self, node: Element) -> None:
|
||||||
|
if not node.hasattr('noemph'):
|
||||||
|
self.body.append('</em>')
|
||||||
|
if self.required_params_left:
|
||||||
|
self.body.append(self.param_separator)
|
||||||
|
|
||||||
|
def visit_desc_optional(self, node: Element) -> None:
|
||||||
|
self.optional_param_level += 1
|
||||||
|
self.body.append('<span class="optional">[</span>')
|
||||||
|
|
||||||
|
def depart_desc_optional(self, node: Element) -> None:
|
||||||
|
self.optional_param_level -= 1
|
||||||
|
self.body.append('<span class="optional">]</span>')
|
||||||
|
|
||||||
|
def visit_desc_annotation(self, node: Element) -> None:
|
||||||
|
self.body.append(self.starttag(node, 'em', '', CLASS='property'))
|
||||||
|
|
||||||
|
def depart_desc_annotation(self, node: Element) -> None:
|
||||||
|
self.body.append('</em>')
|
||||||
|
|
||||||
|
##############################################
|
||||||
|
|
||||||
|
def visit_versionmodified(self, node: Element) -> None:
|
||||||
|
self.body.append(self.starttag(node, 'div', CLASS=node['type']))
|
||||||
|
|
||||||
|
def depart_versionmodified(self, node: Element) -> None:
|
||||||
|
self.body.append('</div>\n')
|
||||||
|
|
||||||
|
# overwritten
|
||||||
|
def visit_reference(self, node: Element) -> None:
|
||||||
|
atts = {'class': 'reference'}
|
||||||
|
if node.get('internal') or 'refuri' not in node:
|
||||||
|
atts['class'] += ' internal'
|
||||||
|
else:
|
||||||
|
atts['class'] += ' external'
|
||||||
|
if 'refuri' in node:
|
||||||
|
atts['href'] = node['refuri'] or '#'
|
||||||
|
if self.settings.cloak_email_addresses and atts['href'].startswith('mailto:'):
|
||||||
|
atts['href'] = self.cloak_mailto(atts['href'])
|
||||||
|
self.in_mailto = True
|
||||||
|
else:
|
||||||
|
assert 'refid' in node, \
|
||||||
|
'References must have "refuri" or "refid" attribute.'
|
||||||
|
atts['href'] = '#' + node['refid']
|
||||||
|
if not isinstance(node.parent, nodes.TextElement):
|
||||||
|
assert len(node) == 1 and isinstance(node[0], nodes.image)
|
||||||
|
atts['class'] += ' image-reference'
|
||||||
|
if 'reftitle' in node:
|
||||||
|
atts['title'] = node['reftitle']
|
||||||
|
if 'target' in node:
|
||||||
|
atts['target'] = node['target']
|
||||||
|
self.body.append(self.starttag(node, 'a', '', **atts))
|
||||||
|
|
||||||
|
if node.get('secnumber'):
|
||||||
|
self.body.append(('%s' + self.secnumber_suffix) %
|
||||||
|
'.'.join(map(str, node['secnumber'])))
|
||||||
|
|
||||||
|
def visit_number_reference(self, node: Element) -> None:
|
||||||
|
self.visit_reference(node)
|
||||||
|
|
||||||
|
def depart_number_reference(self, node: Element) -> None:
|
||||||
|
self.depart_reference(node)
|
||||||
|
|
||||||
|
# overwritten -- we don't want source comments to show up in the HTML
|
||||||
|
def visit_comment(self, node: Element) -> None: # type: ignore
|
||||||
|
raise nodes.SkipNode
|
||||||
|
|
||||||
|
# overwritten
|
||||||
|
def visit_admonition(self, node: Element, name: str = '') -> None:
|
||||||
|
self.body.append(self.starttag(
|
||||||
|
node, 'div', CLASS=('admonition ' + name)))
|
||||||
|
if name:
|
||||||
|
node.insert(0, nodes.title(name, admonitionlabels[name]))
|
||||||
|
self.set_first_last(node)
|
||||||
|
|
||||||
|
def depart_admonition(self, node: Optional[Element] = None) -> None:
|
||||||
|
self.body.append('</div>\n')
|
||||||
|
|
||||||
|
def visit_seealso(self, node: Element) -> None:
|
||||||
|
self.visit_admonition(node, 'seealso')
|
||||||
|
|
||||||
|
def depart_seealso(self, node: Element) -> None:
|
||||||
|
self.depart_admonition(node)
|
||||||
|
|
||||||
|
def get_secnumber(self, node: Element) -> Optional[Tuple[int, ...]]:
|
||||||
|
if node.get('secnumber'):
|
||||||
|
return node['secnumber']
|
||||||
|
elif isinstance(node.parent, nodes.section):
|
||||||
|
if self.builder.name == 'singlehtml':
|
||||||
|
docname = self.docnames[-1]
|
||||||
|
anchorname = "%s/#%s" % (docname, node.parent['ids'][0])
|
||||||
|
if anchorname not in self.builder.secnumbers:
|
||||||
|
anchorname = "%s/" % docname # try first heading which has no anchor
|
||||||
|
else:
|
||||||
|
anchorname = '#' + node.parent['ids'][0]
|
||||||
|
if anchorname not in self.builder.secnumbers:
|
||||||
|
anchorname = '' # try first heading which has no anchor
|
||||||
|
|
||||||
|
if self.builder.secnumbers.get(anchorname):
|
||||||
|
return self.builder.secnumbers[anchorname]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def add_secnumber(self, node: Element) -> None:
|
||||||
|
secnumber = self.get_secnumber(node)
|
||||||
|
if secnumber:
|
||||||
|
self.body.append('<span class="section-number">%s</span>' %
|
||||||
|
('.'.join(map(str, secnumber)) + self.secnumber_suffix))
|
||||||
|
|
||||||
|
def add_fignumber(self, node: Element) -> None:
|
||||||
|
def append_fignumber(figtype: str, figure_id: str) -> None:
|
||||||
|
if self.builder.name == 'singlehtml':
|
||||||
|
key = "%s/%s" % (self.docnames[-1], figtype)
|
||||||
|
else:
|
||||||
|
key = figtype
|
||||||
|
|
||||||
|
if figure_id in self.builder.fignumbers.get(key, {}):
|
||||||
|
self.body.append('<span class="caption-number">')
|
||||||
|
prefix = self.config.numfig_format.get(figtype)
|
||||||
|
if prefix is None:
|
||||||
|
msg = __('numfig_format is not defined for %s') % figtype
|
||||||
|
logger.warning(msg)
|
||||||
|
else:
|
||||||
|
numbers = self.builder.fignumbers[key][figure_id]
|
||||||
|
self.body.append(prefix % '.'.join(map(str, numbers)) + ' ')
|
||||||
|
self.body.append('</span>')
|
||||||
|
|
||||||
|
figtype = self.builder.env.domains['std'].get_enumerable_node_type(node)
|
||||||
|
if figtype:
|
||||||
|
if len(node['ids']) == 0:
|
||||||
|
msg = __('Any IDs not assigned for %s node') % node.tagname
|
||||||
|
logger.warning(msg, location=node)
|
||||||
|
else:
|
||||||
|
append_fignumber(figtype, node['ids'][0])
|
||||||
|
|
||||||
|
def add_permalink_ref(self, node: Element, title: str) -> None:
|
||||||
|
if node['ids'] and self.config.html_permalinks and self.builder.add_permalinks:
|
||||||
|
format = '<a class="headerlink" href="#%s" title="%s">%s</a>'
|
||||||
|
self.body.append(format % (node['ids'][0], title,
|
||||||
|
self.config.html_permalinks_icon))
|
||||||
|
|
||||||
|
def generate_targets_for_listing(self, node: Element) -> None:
|
||||||
|
"""Generate hyperlink targets for listings.
|
||||||
|
|
||||||
|
Original visit_bullet_list(), visit_definition_list() and visit_enumerated_list()
|
||||||
|
generates hyperlink targets inside listing tags (<ul>, <ol> and <dl>) if multiple
|
||||||
|
IDs are assigned to listings. That is invalid DOM structure.
|
||||||
|
(This is a bug of docutils <= 0.12)
|
||||||
|
|
||||||
|
This exports hyperlink targets before listings to make valid DOM structure.
|
||||||
|
"""
|
||||||
|
for id in node['ids'][1:]:
|
||||||
|
self.body.append('<span id="%s"></span>' % id)
|
||||||
|
node['ids'].remove(id)
|
||||||
|
|
||||||
|
# overwritten
|
||||||
|
def visit_bullet_list(self, node: Element) -> None:
|
||||||
|
if len(node) == 1 and isinstance(node[0], addnodes.toctree):
|
||||||
|
# avoid emitting empty <ul></ul>
|
||||||
|
raise nodes.SkipNode
|
||||||
|
self.generate_targets_for_listing(node)
|
||||||
|
super().visit_bullet_list(node)
|
||||||
|
|
||||||
|
# overwritten
|
||||||
|
def visit_enumerated_list(self, node: Element) -> None:
|
||||||
|
self.generate_targets_for_listing(node)
|
||||||
|
super().visit_enumerated_list(node)
|
||||||
|
|
||||||
|
# overwritten
|
||||||
|
def visit_definition(self, node: Element) -> None:
|
||||||
|
# don't insert </dt> here.
|
||||||
|
self.body.append(self.starttag(node, 'dd', ''))
|
||||||
|
|
||||||
|
# overwritten
|
||||||
|
def depart_definition(self, node: Element) -> None:
|
||||||
|
self.body.append('</dd>\n')
|
||||||
|
|
||||||
|
# overwritten
|
||||||
|
def visit_classifier(self, node: Element) -> None:
|
||||||
|
self.body.append(self.starttag(node, 'span', '', CLASS='classifier'))
|
||||||
|
|
||||||
|
# overwritten
|
||||||
|
def depart_classifier(self, node: Element) -> None:
|
||||||
|
self.body.append('</span>')
|
||||||
|
|
||||||
|
next_node: Node = node.next_node(descend=False, siblings=True)
|
||||||
|
if not isinstance(next_node, nodes.classifier):
|
||||||
|
# close `<dt>` tag at the tail of classifiers
|
||||||
|
self.body.append('</dt>')
|
||||||
|
|
||||||
|
# overwritten
|
||||||
|
def visit_term(self, node: Element) -> None:
|
||||||
|
self.body.append(self.starttag(node, 'dt', ''))
|
||||||
|
|
||||||
|
# overwritten
|
||||||
|
def depart_term(self, node: Element) -> None:
|
||||||
|
next_node: Node = node.next_node(descend=False, siblings=True)
|
||||||
|
if isinstance(next_node, nodes.classifier):
|
||||||
|
# Leave the end tag to `self.depart_classifier()`, in case
|
||||||
|
# there's a classifier.
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if isinstance(node.parent.parent.parent, addnodes.glossary):
|
||||||
|
# add permalink if glossary terms
|
||||||
|
self.add_permalink_ref(node, _('Permalink to this term'))
|
||||||
|
|
||||||
|
self.body.append('</dt>')
|
||||||
|
|
||||||
|
# overwritten
|
||||||
|
def visit_title(self, node: Element) -> None:
|
||||||
|
if isinstance(node.parent, addnodes.compact_paragraph) and node.parent.get('toctree'):
|
||||||
|
self.body.append(self.starttag(node, 'p', '', CLASS='caption', ROLE='heading'))
|
||||||
|
self.body.append('<span class="caption-text">')
|
||||||
|
self.context.append('</span></p>\n')
|
||||||
|
else:
|
||||||
|
super().visit_title(node)
|
||||||
|
self.add_secnumber(node)
|
||||||
|
self.add_fignumber(node.parent)
|
||||||
|
if isinstance(node.parent, nodes.table):
|
||||||
|
self.body.append('<span class="caption-text">')
|
||||||
|
|
||||||
|
def depart_title(self, node: Element) -> None:
|
||||||
|
close_tag = self.context[-1]
|
||||||
|
if (self.config.html_permalinks and self.builder.add_permalinks and
|
||||||
|
node.parent.hasattr('ids') and node.parent['ids']):
|
||||||
|
# add permalink anchor
|
||||||
|
if close_tag.startswith('</h'):
|
||||||
|
self.add_permalink_ref(node.parent, _('Permalink to this heading'))
|
||||||
|
elif close_tag.startswith('</a></h'):
|
||||||
|
self.body.append('</a><a class="headerlink" href="#%s" ' %
|
||||||
|
node.parent['ids'][0] +
|
||||||
|
'title="%s">%s' % (
|
||||||
|
_('Permalink to this heading'),
|
||||||
|
self.config.html_permalinks_icon))
|
||||||
|
elif isinstance(node.parent, nodes.table):
|
||||||
|
self.body.append('</span>')
|
||||||
|
self.add_permalink_ref(node.parent, _('Permalink to this table'))
|
||||||
|
elif isinstance(node.parent, nodes.table):
|
||||||
|
self.body.append('</span>')
|
||||||
|
|
||||||
|
super().depart_title(node)
|
||||||
|
|
||||||
|
# overwritten
|
||||||
|
def visit_literal_block(self, node: Element) -> None:
|
||||||
|
if node.rawsource != node.astext():
|
||||||
|
# most probably a parsed-literal block -- don't highlight
|
||||||
|
return super().visit_literal_block(node)
|
||||||
|
|
||||||
|
lang = node.get('language', 'default')
|
||||||
|
linenos = node.get('linenos', False)
|
||||||
|
highlight_args = node.get('highlight_args', {})
|
||||||
|
highlight_args['force'] = node.get('force', False)
|
||||||
|
opts = self.config.highlight_options.get(lang, {})
|
||||||
|
|
||||||
|
if linenos and self.config.html_codeblock_linenos_style:
|
||||||
|
linenos = self.config.html_codeblock_linenos_style
|
||||||
|
|
||||||
|
highlighted = self.highlighter.highlight_block(
|
||||||
|
node.rawsource, lang, opts=opts, linenos=linenos,
|
||||||
|
location=node, **highlight_args
|
||||||
|
)
|
||||||
|
starttag = self.starttag(node, 'div', suffix='',
|
||||||
|
CLASS='highlight-%s notranslate' % lang)
|
||||||
|
self.body.append(starttag + highlighted + '</div>\n')
|
||||||
|
raise nodes.SkipNode
|
||||||
|
|
||||||
|
def visit_caption(self, node: Element) -> None:
|
||||||
|
if isinstance(node.parent, nodes.container) and node.parent.get('literal_block'):
|
||||||
|
self.body.append('<div class="code-block-caption">')
|
||||||
|
else:
|
||||||
|
super().visit_caption(node)
|
||||||
|
self.add_fignumber(node.parent)
|
||||||
|
self.body.append(self.starttag(node, 'span', '', CLASS='caption-text'))
|
||||||
|
|
||||||
|
def depart_caption(self, node: Element) -> None:
|
||||||
|
self.body.append('</span>')
|
||||||
|
|
||||||
|
# append permalink if available
|
||||||
|
if isinstance(node.parent, nodes.container) and node.parent.get('literal_block'):
|
||||||
|
self.add_permalink_ref(node.parent, _('Permalink to this code'))
|
||||||
|
elif isinstance(node.parent, nodes.figure):
|
||||||
|
self.add_permalink_ref(node.parent, _('Permalink to this image'))
|
||||||
|
elif node.parent.get('toctree'):
|
||||||
|
self.add_permalink_ref(node.parent.parent, _('Permalink to this toctree'))
|
||||||
|
|
||||||
|
if isinstance(node.parent, nodes.container) and node.parent.get('literal_block'):
|
||||||
|
self.body.append('</div>\n')
|
||||||
|
else:
|
||||||
|
super().depart_caption(node)
|
||||||
|
|
||||||
|
def visit_doctest_block(self, node: Element) -> None:
|
||||||
|
self.visit_literal_block(node)
|
||||||
|
|
||||||
|
# overwritten to add the <div> (for XHTML compliance)
|
||||||
|
def visit_block_quote(self, node: Element) -> None:
|
||||||
|
self.body.append(self.starttag(node, 'blockquote') + '<div>')
|
||||||
|
|
||||||
|
def depart_block_quote(self, node: Element) -> None:
|
||||||
|
self.body.append('</div></blockquote>\n')
|
||||||
|
|
||||||
|
# overwritten
|
||||||
|
def visit_literal(self, node: Element) -> None:
|
||||||
|
if 'kbd' in node['classes']:
|
||||||
|
self.body.append(self.starttag(node, 'kbd', '',
|
||||||
|
CLASS='docutils literal notranslate'))
|
||||||
|
return
|
||||||
|
lang = node.get("language", None)
|
||||||
|
if 'code' not in node['classes'] or not lang:
|
||||||
|
self.body.append(self.starttag(node, 'code', '',
|
||||||
|
CLASS='docutils literal notranslate'))
|
||||||
|
self.protect_literal_text += 1
|
||||||
|
return
|
||||||
|
|
||||||
|
opts = self.config.highlight_options.get(lang, {})
|
||||||
|
highlighted = self.highlighter.highlight_block(
|
||||||
|
node.astext(), lang, opts=opts, location=node, nowrap=True)
|
||||||
|
starttag = self.starttag(
|
||||||
|
node,
|
||||||
|
"code",
|
||||||
|
suffix="",
|
||||||
|
CLASS="docutils literal highlight highlight-%s" % lang,
|
||||||
|
)
|
||||||
|
self.body.append(starttag + highlighted.strip() + "</code>")
|
||||||
|
raise nodes.SkipNode
|
||||||
|
|
||||||
|
def depart_literal(self, node: Element) -> None:
|
||||||
|
if 'kbd' in node['classes']:
|
||||||
|
self.body.append('</kbd>')
|
||||||
|
else:
|
||||||
|
self.protect_literal_text -= 1
|
||||||
|
self.body.append('</code>')
|
||||||
|
|
||||||
|
def visit_productionlist(self, node: Element) -> None:
|
||||||
|
self.body.append(self.starttag(node, 'pre'))
|
||||||
|
names = []
|
||||||
|
productionlist = cast(Iterable[addnodes.production], node)
|
||||||
|
for production in productionlist:
|
||||||
|
names.append(production['tokenname'])
|
||||||
|
maxlen = max(len(name) for name in names)
|
||||||
|
lastname = None
|
||||||
|
for production in productionlist:
|
||||||
|
if production['tokenname']:
|
||||||
|
lastname = production['tokenname'].ljust(maxlen)
|
||||||
|
self.body.append(self.starttag(production, 'strong', ''))
|
||||||
|
self.body.append(lastname + '</strong> ::= ')
|
||||||
|
elif lastname is not None:
|
||||||
|
self.body.append('%s ' % (' ' * len(lastname)))
|
||||||
|
production.walkabout(self)
|
||||||
|
self.body.append('\n')
|
||||||
|
self.body.append('</pre>\n')
|
||||||
|
raise nodes.SkipNode
|
||||||
|
|
||||||
|
def depart_productionlist(self, node: Element) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def visit_production(self, node: Element) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def depart_production(self, node: Element) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def visit_centered(self, node: Element) -> None:
|
||||||
|
self.body.append(self.starttag(node, 'p', CLASS="centered") +
|
||||||
|
'<strong>')
|
||||||
|
|
||||||
|
def depart_centered(self, node: Element) -> None:
|
||||||
|
self.body.append('</strong></p>')
|
||||||
|
|
||||||
|
# overwritten
|
||||||
|
def should_be_compact_paragraph(self, node: Node) -> bool:
|
||||||
|
"""Determine if the <p> tags around paragraph can be omitted."""
|
||||||
|
if isinstance(node.parent, addnodes.desc_content):
|
||||||
|
# Never compact desc_content items.
|
||||||
|
return False
|
||||||
|
if isinstance(node.parent, addnodes.versionmodified):
|
||||||
|
# Never compact versionmodified nodes.
|
||||||
|
return False
|
||||||
|
return super().should_be_compact_paragraph(node)
|
||||||
|
|
||||||
|
def visit_compact_paragraph(self, node: Element) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def depart_compact_paragraph(self, node: Element) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def visit_download_reference(self, node: Element) -> None:
|
||||||
|
atts = {'class': 'reference download',
|
||||||
|
'download': ''}
|
||||||
|
|
||||||
|
if not self.builder.download_support:
|
||||||
|
self.context.append('')
|
||||||
|
elif 'refuri' in node:
|
||||||
|
atts['class'] += ' external'
|
||||||
|
atts['href'] = node['refuri']
|
||||||
|
self.body.append(self.starttag(node, 'a', '', **atts))
|
||||||
|
self.context.append('</a>')
|
||||||
|
elif 'filename' in node:
|
||||||
|
atts['class'] += ' internal'
|
||||||
|
atts['href'] = posixpath.join(self.builder.dlpath,
|
||||||
|
urllib.parse.quote(node['filename']))
|
||||||
|
self.body.append(self.starttag(node, 'a', '', **atts))
|
||||||
|
self.context.append('</a>')
|
||||||
|
else:
|
||||||
|
self.context.append('')
|
||||||
|
|
||||||
|
def depart_download_reference(self, node: Element) -> None:
|
||||||
|
self.body.append(self.context.pop())
|
||||||
|
|
||||||
|
# overwritten
|
||||||
|
def visit_figure(self, node: Element) -> None:
|
||||||
|
# set align=default if align not specified to give a default style
|
||||||
|
node.setdefault('align', 'default')
|
||||||
|
|
||||||
|
return super().visit_figure(node)
|
||||||
|
|
||||||
|
# overwritten
|
||||||
|
def visit_image(self, node: Element) -> None:
|
||||||
|
olduri = node['uri']
|
||||||
|
# rewrite the URI if the environment knows about it
|
||||||
|
if olduri in self.builder.images:
|
||||||
|
node['uri'] = posixpath.join(self.builder.imgpath,
|
||||||
|
urllib.parse.quote(self.builder.images[olduri]))
|
||||||
|
|
||||||
|
if 'scale' in node:
|
||||||
|
# Try to figure out image height and width. Docutils does that too,
|
||||||
|
# but it tries the final file name, which does not necessarily exist
|
||||||
|
# yet at the time the HTML file is written.
|
||||||
|
if not ('width' in node and 'height' in node):
|
||||||
|
size = get_image_size(os.path.join(self.builder.srcdir, olduri))
|
||||||
|
if size is None:
|
||||||
|
logger.warning(
|
||||||
|
__('Could not obtain image size. :scale: option is ignored.'),
|
||||||
|
location=node,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if 'width' not in node:
|
||||||
|
node['width'] = str(size[0])
|
||||||
|
if 'height' not in node:
|
||||||
|
node['height'] = str(size[1])
|
||||||
|
|
||||||
|
uri = node['uri']
|
||||||
|
if uri.lower().endswith(('svg', 'svgz')):
|
||||||
|
atts = {'src': uri}
|
||||||
|
if 'width' in node:
|
||||||
|
atts['width'] = node['width']
|
||||||
|
if 'height' in node:
|
||||||
|
atts['height'] = node['height']
|
||||||
|
if 'scale' in node:
|
||||||
|
if 'width' in atts:
|
||||||
|
atts['width'] = multiply_length(atts['width'], node['scale'])
|
||||||
|
if 'height' in atts:
|
||||||
|
atts['height'] = multiply_length(atts['height'], node['scale'])
|
||||||
|
atts['alt'] = node.get('alt', uri)
|
||||||
|
if 'align' in node:
|
||||||
|
atts['class'] = 'align-%s' % node['align']
|
||||||
|
self.body.append(self.emptytag(node, 'img', '', **atts))
|
||||||
|
return
|
||||||
|
|
||||||
|
super().visit_image(node)
|
||||||
|
|
||||||
|
# overwritten
|
||||||
|
def depart_image(self, node: Element) -> None:
|
||||||
|
if node['uri'].lower().endswith(('svg', 'svgz')):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
super().depart_image(node)
|
||||||
|
|
||||||
|
def visit_toctree(self, node: Element) -> None:
|
||||||
|
# this only happens when formatting a toc from env.tocs -- in this
|
||||||
|
# case we don't want to include the subtree
|
||||||
|
raise nodes.SkipNode
|
||||||
|
|
||||||
|
def visit_index(self, node: Element) -> None:
|
||||||
|
raise nodes.SkipNode
|
||||||
|
|
||||||
|
def visit_tabular_col_spec(self, node: Element) -> None:
|
||||||
|
raise nodes.SkipNode
|
||||||
|
|
||||||
|
def visit_glossary(self, node: Element) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def depart_glossary(self, node: Element) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def visit_acks(self, node: Element) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def depart_acks(self, node: Element) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def visit_hlist(self, node: Element) -> None:
|
||||||
|
self.body.append('<table class="hlist"><tr>')
|
||||||
|
|
||||||
|
def depart_hlist(self, node: Element) -> None:
|
||||||
|
self.body.append('</tr></table>\n')
|
||||||
|
|
||||||
|
def visit_hlistcol(self, node: Element) -> None:
|
||||||
|
self.body.append('<td>')
|
||||||
|
|
||||||
|
def depart_hlistcol(self, node: Element) -> None:
|
||||||
|
self.body.append('</td>')
|
||||||
|
|
||||||
|
def visit_option_group(self, node: Element) -> None:
|
||||||
|
super().visit_option_group(node)
|
||||||
|
self.context[-2] = self.context[-2].replace(' ', ' ')
|
||||||
|
|
||||||
|
# overwritten
|
||||||
|
def visit_Text(self, node: Text) -> None:
|
||||||
|
text = node.astext()
|
||||||
|
encoded = self.encode(text)
|
||||||
|
if self.protect_literal_text:
|
||||||
|
# moved here from base class's visit_literal to support
|
||||||
|
# more formatting in literal nodes
|
||||||
|
for token in self.words_and_spaces.findall(encoded):
|
||||||
|
if token.strip():
|
||||||
|
# protect literal text from line wrapping
|
||||||
|
self.body.append('<span class="pre">%s</span>' % token)
|
||||||
|
elif token in ' \n':
|
||||||
|
# allow breaks at whitespace
|
||||||
|
self.body.append(token)
|
||||||
|
else:
|
||||||
|
# protect runs of multiple spaces; the last one can wrap
|
||||||
|
self.body.append(' ' * (len(token) - 1) + ' ')
|
||||||
|
else:
|
||||||
|
if self.in_mailto and self.settings.cloak_email_addresses:
|
||||||
|
encoded = self.cloak_email(encoded)
|
||||||
|
self.body.append(encoded)
|
||||||
|
|
||||||
|
def visit_note(self, node: Element) -> None:
|
||||||
|
self.visit_admonition(node, 'note')
|
||||||
|
|
||||||
|
def depart_note(self, node: Element) -> None:
|
||||||
|
self.depart_admonition(node)
|
||||||
|
|
||||||
|
def visit_warning(self, node: Element) -> None:
|
||||||
|
self.visit_admonition(node, 'warning')
|
||||||
|
|
||||||
|
def depart_warning(self, node: Element) -> None:
|
||||||
|
self.depart_admonition(node)
|
||||||
|
|
||||||
|
def visit_attention(self, node: Element) -> None:
|
||||||
|
self.visit_admonition(node, 'attention')
|
||||||
|
|
||||||
|
def depart_attention(self, node: Element) -> None:
|
||||||
|
self.depart_admonition(node)
|
||||||
|
|
||||||
|
def visit_caution(self, node: Element) -> None:
|
||||||
|
self.visit_admonition(node, 'caution')
|
||||||
|
|
||||||
|
def depart_caution(self, node: Element) -> None:
|
||||||
|
self.depart_admonition(node)
|
||||||
|
|
||||||
|
def visit_danger(self, node: Element) -> None:
|
||||||
|
self.visit_admonition(node, 'danger')
|
||||||
|
|
||||||
|
def depart_danger(self, node: Element) -> None:
|
||||||
|
self.depart_admonition(node)
|
||||||
|
|
||||||
|
def visit_error(self, node: Element) -> None:
|
||||||
|
self.visit_admonition(node, 'error')
|
||||||
|
|
||||||
|
def depart_error(self, node: Element) -> None:
|
||||||
|
self.depart_admonition(node)
|
||||||
|
|
||||||
|
def visit_hint(self, node: Element) -> None:
|
||||||
|
self.visit_admonition(node, 'hint')
|
||||||
|
|
||||||
|
def depart_hint(self, node: Element) -> None:
|
||||||
|
self.depart_admonition(node)
|
||||||
|
|
||||||
|
def visit_important(self, node: Element) -> None:
|
||||||
|
self.visit_admonition(node, 'important')
|
||||||
|
|
||||||
|
def depart_important(self, node: Element) -> None:
|
||||||
|
self.depart_admonition(node)
|
||||||
|
|
||||||
|
def visit_tip(self, node: Element) -> None:
|
||||||
|
self.visit_admonition(node, 'tip')
|
||||||
|
|
||||||
|
def depart_tip(self, node: Element) -> None:
|
||||||
|
self.depart_admonition(node)
|
||||||
|
|
||||||
|
def visit_literal_emphasis(self, node: Element) -> None:
|
||||||
|
return self.visit_emphasis(node)
|
||||||
|
|
||||||
|
def depart_literal_emphasis(self, node: Element) -> None:
|
||||||
|
return self.depart_emphasis(node)
|
||||||
|
|
||||||
|
def visit_literal_strong(self, node: Element) -> None:
|
||||||
|
return self.visit_strong(node)
|
||||||
|
|
||||||
|
def depart_literal_strong(self, node: Element) -> None:
|
||||||
|
return self.depart_strong(node)
|
||||||
|
|
||||||
|
def visit_abbreviation(self, node: Element) -> None:
|
||||||
|
attrs = {}
|
||||||
|
if node.hasattr('explanation'):
|
||||||
|
attrs['title'] = node['explanation']
|
||||||
|
self.body.append(self.starttag(node, 'abbr', '', **attrs))
|
||||||
|
|
||||||
|
def depart_abbreviation(self, node: Element) -> None:
|
||||||
|
self.body.append('</abbr>')
|
||||||
|
|
||||||
|
def visit_manpage(self, node: Element) -> None:
|
||||||
|
self.visit_literal_emphasis(node)
|
||||||
|
if self.manpages_url:
|
||||||
|
node['refuri'] = self.manpages_url.format(**node.attributes)
|
||||||
|
self.visit_reference(node)
|
||||||
|
|
||||||
|
def depart_manpage(self, node: Element) -> None:
|
||||||
|
if self.manpages_url:
|
||||||
|
self.depart_reference(node)
|
||||||
|
self.depart_literal_emphasis(node)
|
||||||
|
|
||||||
|
# overwritten to add even/odd classes
|
||||||
|
|
||||||
|
def visit_table(self, node: Element) -> None:
|
||||||
|
self._table_row_indices.append(0)
|
||||||
|
|
||||||
|
# set align=default if align not specified to give a default style
|
||||||
|
node.setdefault('align', 'default')
|
||||||
|
|
||||||
|
return super().visit_table(node)
|
||||||
|
|
||||||
|
def depart_table(self, node: Element) -> None:
|
||||||
|
self._table_row_indices.pop()
|
||||||
|
super().depart_table(node)
|
||||||
|
|
||||||
|
def visit_row(self, node: Element) -> None:
|
||||||
|
self._table_row_indices[-1] += 1
|
||||||
|
if self._table_row_indices[-1] % 2 == 0:
|
||||||
|
node['classes'].append('row-even')
|
||||||
|
else:
|
||||||
|
node['classes'].append('row-odd')
|
||||||
|
self.body.append(self.starttag(node, 'tr', ''))
|
||||||
|
node.column = 0 # type: ignore
|
||||||
|
|
||||||
|
def visit_entry(self, node: Element) -> None:
|
||||||
|
super().visit_entry(node)
|
||||||
|
if self.body[-1] == ' ':
|
||||||
|
self.body[-1] = ' '
|
||||||
|
|
||||||
|
def visit_field_list(self, node: Element) -> None:
|
||||||
|
self._fieldlist_row_indices.append(0)
|
||||||
|
return super().visit_field_list(node)
|
||||||
|
|
||||||
|
def depart_field_list(self, node: Element) -> None:
|
||||||
|
self._fieldlist_row_indices.pop()
|
||||||
|
return super().depart_field_list(node)
|
||||||
|
|
||||||
|
def visit_field(self, node: Element) -> None:
|
||||||
|
self._fieldlist_row_indices[-1] += 1
|
||||||
|
if self._fieldlist_row_indices[-1] % 2 == 0:
|
||||||
|
node['classes'].append('field-even')
|
||||||
|
else:
|
||||||
|
node['classes'].append('field-odd')
|
||||||
|
self.body.append(self.starttag(node, 'tr', '', CLASS='field'))
|
||||||
|
|
||||||
|
def visit_field_name(self, node: Element) -> None:
|
||||||
|
context_count = len(self.context)
|
||||||
|
super().visit_field_name(node)
|
||||||
|
if context_count != len(self.context):
|
||||||
|
self.context[-1] = self.context[-1].replace(' ', ' ')
|
||||||
|
|
||||||
|
def visit_math(self, node: Element, math_env: str = '') -> None:
|
||||||
|
name = self.builder.math_renderer_name
|
||||||
|
visit, _ = self.builder.app.registry.html_inline_math_renderers[name]
|
||||||
|
visit(self, node)
|
||||||
|
|
||||||
|
def depart_math(self, node: Element, math_env: str = '') -> None:
|
||||||
|
name = self.builder.math_renderer_name
|
||||||
|
_, depart = self.builder.app.registry.html_inline_math_renderers[name]
|
||||||
|
if depart: # type: ignore[truthy-function]
|
||||||
|
depart(self, node)
|
||||||
|
|
||||||
|
def visit_math_block(self, node: Element, math_env: str = '') -> None:
|
||||||
|
name = self.builder.math_renderer_name
|
||||||
|
visit, _ = self.builder.app.registry.html_block_math_renderers[name]
|
||||||
|
visit(self, node)
|
||||||
|
|
||||||
|
def depart_math_block(self, node: Element, math_env: str = '') -> None:
|
||||||
|
name = self.builder.math_renderer_name
|
||||||
|
_, depart = self.builder.app.registry.html_block_math_renderers[name]
|
||||||
|
if depart: # type: ignore[truthy-function]
|
||||||
|
depart(self, node)
|
@ -1,46 +1,24 @@
|
|||||||
"""docutils writers handling Sphinx' custom nodes."""
|
"""docutils writers handling Sphinx' custom nodes."""
|
||||||
|
|
||||||
import os
|
from typing import TYPE_CHECKING, cast
|
||||||
import posixpath
|
|
||||||
import re
|
|
||||||
import urllib.parse
|
|
||||||
from typing import TYPE_CHECKING, Iterable, Optional, Tuple, cast
|
|
||||||
|
|
||||||
from docutils import nodes
|
|
||||||
from docutils.nodes import Element, Node, Text
|
|
||||||
from docutils.writers.html4css1 import HTMLTranslator as BaseTranslator
|
|
||||||
from docutils.writers.html4css1 import Writer
|
from docutils.writers.html4css1 import Writer
|
||||||
|
|
||||||
from sphinx import addnodes
|
|
||||||
from sphinx.builders import Builder
|
|
||||||
from sphinx.locale import _, __, admonitionlabels
|
|
||||||
from sphinx.util import logging
|
from sphinx.util import logging
|
||||||
from sphinx.util.docutils import SphinxTranslator
|
from sphinx.writers._html4 import HTML4Translator
|
||||||
from sphinx.util.images import get_image_size
|
from sphinx.writers.html5 import HTML5Translator # NoQA: F401
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from sphinx.builders.html import StandaloneHTMLBuilder
|
from sphinx.builders.html import StandaloneHTMLBuilder
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
HTMLTranslator = HTML4Translator
|
||||||
|
|
||||||
# A good overview of the purpose behind these classes can be found here:
|
# A good overview of the purpose behind these classes can be found here:
|
||||||
# http://www.arnebrodowski.de/blog/write-your-own-restructuredtext-writer.html
|
# http://www.arnebrodowski.de/blog/write-your-own-restructuredtext-writer.html
|
||||||
|
|
||||||
|
|
||||||
def multiply_length(length: str, scale: int) -> str:
|
|
||||||
"""Multiply *length* (width or height) by *scale*."""
|
|
||||||
matched = re.match(r'^(\d*\.?\d*)\s*(\S*)$', length)
|
|
||||||
if not matched:
|
|
||||||
return length
|
|
||||||
elif scale == 100:
|
|
||||||
return length
|
|
||||||
else:
|
|
||||||
amount, unit = matched.groups()
|
|
||||||
result = float(amount) * scale / 100
|
|
||||||
return "%s%s" % (int(result), unit)
|
|
||||||
|
|
||||||
|
|
||||||
class HTMLWriter(Writer):
|
class HTMLWriter(Writer):
|
||||||
|
|
||||||
# override embed-stylesheet default value to False.
|
# override embed-stylesheet default value to False.
|
||||||
@ -53,7 +31,7 @@ class HTMLWriter(Writer):
|
|||||||
def translate(self) -> None:
|
def translate(self) -> None:
|
||||||
# sadly, this is mostly copied from parent class
|
# sadly, this is mostly copied from parent class
|
||||||
visitor = self.builder.create_translator(self.document, self.builder)
|
visitor = self.builder.create_translator(self.document, self.builder)
|
||||||
self.visitor = cast(HTMLTranslator, visitor)
|
self.visitor = cast(HTML4Translator, visitor)
|
||||||
self.document.walkabout(visitor)
|
self.document.walkabout(visitor)
|
||||||
self.output = self.visitor.astext()
|
self.output = self.visitor.astext()
|
||||||
for attr in ('head_prefix', 'stylesheet', 'head', 'body_prefix',
|
for attr in ('head_prefix', 'stylesheet', 'head', 'body_prefix',
|
||||||
@ -63,822 +41,3 @@ class HTMLWriter(Writer):
|
|||||||
'html_subtitle', 'html_body', ):
|
'html_subtitle', 'html_body', ):
|
||||||
setattr(self, attr, getattr(visitor, attr, None))
|
setattr(self, attr, getattr(visitor, attr, None))
|
||||||
self.clean_meta = ''.join(self.visitor.meta[2:])
|
self.clean_meta = ''.join(self.visitor.meta[2:])
|
||||||
|
|
||||||
|
|
||||||
# RemovedInSphinx70Warning
|
|
||||||
class HTMLTranslator(SphinxTranslator, BaseTranslator):
|
|
||||||
"""
|
|
||||||
Our custom HTML translator.
|
|
||||||
"""
|
|
||||||
|
|
||||||
builder: "StandaloneHTMLBuilder"
|
|
||||||
|
|
||||||
def __init__(self, document: nodes.document, builder: Builder) -> None:
|
|
||||||
super().__init__(document, builder)
|
|
||||||
|
|
||||||
self.highlighter = self.builder.highlighter
|
|
||||||
self.docnames = [self.builder.current_docname] # for singlehtml builder
|
|
||||||
self.manpages_url = self.config.manpages_url
|
|
||||||
self.protect_literal_text = 0
|
|
||||||
self.secnumber_suffix = self.config.html_secnumber_suffix
|
|
||||||
self.param_separator = ''
|
|
||||||
self.optional_param_level = 0
|
|
||||||
self._table_row_indices = [0]
|
|
||||||
self._fieldlist_row_indices = [0]
|
|
||||||
self.required_params_left = 0
|
|
||||||
|
|
||||||
def visit_start_of_file(self, node: Element) -> None:
|
|
||||||
# only occurs in the single-file builder
|
|
||||||
self.docnames.append(node['docname'])
|
|
||||||
self.body.append('<span id="document-%s"></span>' % node['docname'])
|
|
||||||
|
|
||||||
def depart_start_of_file(self, node: Element) -> None:
|
|
||||||
self.docnames.pop()
|
|
||||||
|
|
||||||
#############################################################
|
|
||||||
# Domain-specific object descriptions
|
|
||||||
#############################################################
|
|
||||||
|
|
||||||
# Top-level nodes for descriptions
|
|
||||||
##################################
|
|
||||||
|
|
||||||
def visit_desc(self, node: Element) -> None:
|
|
||||||
self.body.append(self.starttag(node, 'dl'))
|
|
||||||
|
|
||||||
def depart_desc(self, node: Element) -> None:
|
|
||||||
self.body.append('</dl>\n\n')
|
|
||||||
|
|
||||||
def visit_desc_signature(self, node: Element) -> None:
|
|
||||||
# the id is set automatically
|
|
||||||
self.body.append(self.starttag(node, 'dt'))
|
|
||||||
self.protect_literal_text += 1
|
|
||||||
|
|
||||||
def depart_desc_signature(self, node: Element) -> None:
|
|
||||||
self.protect_literal_text -= 1
|
|
||||||
if not node.get('is_multiline'):
|
|
||||||
self.add_permalink_ref(node, _('Permalink to this definition'))
|
|
||||||
self.body.append('</dt>\n')
|
|
||||||
|
|
||||||
def visit_desc_signature_line(self, node: Element) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def depart_desc_signature_line(self, node: Element) -> None:
|
|
||||||
if node.get('add_permalink'):
|
|
||||||
# the permalink info is on the parent desc_signature node
|
|
||||||
self.add_permalink_ref(node.parent, _('Permalink to this definition'))
|
|
||||||
self.body.append('<br />')
|
|
||||||
|
|
||||||
def visit_desc_content(self, node: Element) -> None:
|
|
||||||
self.body.append(self.starttag(node, 'dd', ''))
|
|
||||||
|
|
||||||
def depart_desc_content(self, node: Element) -> None:
|
|
||||||
self.body.append('</dd>')
|
|
||||||
|
|
||||||
def visit_desc_inline(self, node: Element) -> None:
|
|
||||||
self.body.append(self.starttag(node, 'span', ''))
|
|
||||||
|
|
||||||
def depart_desc_inline(self, node: Element) -> None:
|
|
||||||
self.body.append('</span>')
|
|
||||||
|
|
||||||
# Nodes for high-level structure in signatures
|
|
||||||
##############################################
|
|
||||||
|
|
||||||
def visit_desc_name(self, node: Element) -> None:
|
|
||||||
self.body.append(self.starttag(node, 'code', ''))
|
|
||||||
|
|
||||||
def depart_desc_name(self, node: Element) -> None:
|
|
||||||
self.body.append('</code>')
|
|
||||||
|
|
||||||
def visit_desc_addname(self, node: Element) -> None:
|
|
||||||
self.body.append(self.starttag(node, 'code', ''))
|
|
||||||
|
|
||||||
def depart_desc_addname(self, node: Element) -> None:
|
|
||||||
self.body.append('</code>')
|
|
||||||
|
|
||||||
def visit_desc_type(self, node: Element) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def depart_desc_type(self, node: Element) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def visit_desc_returns(self, node: Element) -> None:
|
|
||||||
self.body.append(' <span class="sig-return">')
|
|
||||||
self.body.append('<span class="sig-return-icon">→</span>')
|
|
||||||
self.body.append(' <span class="sig-return-typehint">')
|
|
||||||
|
|
||||||
def depart_desc_returns(self, node: Element) -> None:
|
|
||||||
self.body.append('</span></span>')
|
|
||||||
|
|
||||||
def visit_desc_parameterlist(self, node: Element) -> None:
|
|
||||||
self.body.append('<span class="sig-paren">(</span>')
|
|
||||||
self.first_param = 1
|
|
||||||
self.optional_param_level = 0
|
|
||||||
# How many required parameters are left.
|
|
||||||
self.required_params_left = sum([isinstance(c, addnodes.desc_parameter)
|
|
||||||
for c in node.children])
|
|
||||||
self.param_separator = node.child_text_separator
|
|
||||||
|
|
||||||
def depart_desc_parameterlist(self, node: Element) -> None:
|
|
||||||
self.body.append('<span class="sig-paren">)</span>')
|
|
||||||
|
|
||||||
# If required parameters are still to come, then put the comma after
|
|
||||||
# the parameter. Otherwise, put the comma before. This ensures that
|
|
||||||
# signatures like the following render correctly (see issue #1001):
|
|
||||||
#
|
|
||||||
# foo([a, ]b, c[, d])
|
|
||||||
#
|
|
||||||
def visit_desc_parameter(self, node: Element) -> None:
|
|
||||||
if self.first_param:
|
|
||||||
self.first_param = 0
|
|
||||||
elif not self.required_params_left:
|
|
||||||
self.body.append(self.param_separator)
|
|
||||||
if self.optional_param_level == 0:
|
|
||||||
self.required_params_left -= 1
|
|
||||||
if not node.hasattr('noemph'):
|
|
||||||
self.body.append('<em>')
|
|
||||||
|
|
||||||
def depart_desc_parameter(self, node: Element) -> None:
|
|
||||||
if not node.hasattr('noemph'):
|
|
||||||
self.body.append('</em>')
|
|
||||||
if self.required_params_left:
|
|
||||||
self.body.append(self.param_separator)
|
|
||||||
|
|
||||||
def visit_desc_optional(self, node: Element) -> None:
|
|
||||||
self.optional_param_level += 1
|
|
||||||
self.body.append('<span class="optional">[</span>')
|
|
||||||
|
|
||||||
def depart_desc_optional(self, node: Element) -> None:
|
|
||||||
self.optional_param_level -= 1
|
|
||||||
self.body.append('<span class="optional">]</span>')
|
|
||||||
|
|
||||||
def visit_desc_annotation(self, node: Element) -> None:
|
|
||||||
self.body.append(self.starttag(node, 'em', '', CLASS='property'))
|
|
||||||
|
|
||||||
def depart_desc_annotation(self, node: Element) -> None:
|
|
||||||
self.body.append('</em>')
|
|
||||||
|
|
||||||
##############################################
|
|
||||||
|
|
||||||
def visit_versionmodified(self, node: Element) -> None:
|
|
||||||
self.body.append(self.starttag(node, 'div', CLASS=node['type']))
|
|
||||||
|
|
||||||
def depart_versionmodified(self, node: Element) -> None:
|
|
||||||
self.body.append('</div>\n')
|
|
||||||
|
|
||||||
# overwritten
|
|
||||||
def visit_reference(self, node: Element) -> None:
|
|
||||||
atts = {'class': 'reference'}
|
|
||||||
if node.get('internal') or 'refuri' not in node:
|
|
||||||
atts['class'] += ' internal'
|
|
||||||
else:
|
|
||||||
atts['class'] += ' external'
|
|
||||||
if 'refuri' in node:
|
|
||||||
atts['href'] = node['refuri'] or '#'
|
|
||||||
if self.settings.cloak_email_addresses and atts['href'].startswith('mailto:'):
|
|
||||||
atts['href'] = self.cloak_mailto(atts['href'])
|
|
||||||
self.in_mailto = True
|
|
||||||
else:
|
|
||||||
assert 'refid' in node, \
|
|
||||||
'References must have "refuri" or "refid" attribute.'
|
|
||||||
atts['href'] = '#' + node['refid']
|
|
||||||
if not isinstance(node.parent, nodes.TextElement):
|
|
||||||
assert len(node) == 1 and isinstance(node[0], nodes.image)
|
|
||||||
atts['class'] += ' image-reference'
|
|
||||||
if 'reftitle' in node:
|
|
||||||
atts['title'] = node['reftitle']
|
|
||||||
if 'target' in node:
|
|
||||||
atts['target'] = node['target']
|
|
||||||
self.body.append(self.starttag(node, 'a', '', **atts))
|
|
||||||
|
|
||||||
if node.get('secnumber'):
|
|
||||||
self.body.append(('%s' + self.secnumber_suffix) %
|
|
||||||
'.'.join(map(str, node['secnumber'])))
|
|
||||||
|
|
||||||
def visit_number_reference(self, node: Element) -> None:
|
|
||||||
self.visit_reference(node)
|
|
||||||
|
|
||||||
def depart_number_reference(self, node: Element) -> None:
|
|
||||||
self.depart_reference(node)
|
|
||||||
|
|
||||||
# overwritten -- we don't want source comments to show up in the HTML
|
|
||||||
def visit_comment(self, node: Element) -> None: # type: ignore
|
|
||||||
raise nodes.SkipNode
|
|
||||||
|
|
||||||
# overwritten
|
|
||||||
def visit_admonition(self, node: Element, name: str = '') -> None:
|
|
||||||
self.body.append(self.starttag(
|
|
||||||
node, 'div', CLASS=('admonition ' + name)))
|
|
||||||
if name:
|
|
||||||
node.insert(0, nodes.title(name, admonitionlabels[name]))
|
|
||||||
self.set_first_last(node)
|
|
||||||
|
|
||||||
def depart_admonition(self, node: Optional[Element] = None) -> None:
|
|
||||||
self.body.append('</div>\n')
|
|
||||||
|
|
||||||
def visit_seealso(self, node: Element) -> None:
|
|
||||||
self.visit_admonition(node, 'seealso')
|
|
||||||
|
|
||||||
def depart_seealso(self, node: Element) -> None:
|
|
||||||
self.depart_admonition(node)
|
|
||||||
|
|
||||||
def get_secnumber(self, node: Element) -> Optional[Tuple[int, ...]]:
|
|
||||||
if node.get('secnumber'):
|
|
||||||
return node['secnumber']
|
|
||||||
elif isinstance(node.parent, nodes.section):
|
|
||||||
if self.builder.name == 'singlehtml':
|
|
||||||
docname = self.docnames[-1]
|
|
||||||
anchorname = "%s/#%s" % (docname, node.parent['ids'][0])
|
|
||||||
if anchorname not in self.builder.secnumbers:
|
|
||||||
anchorname = "%s/" % docname # try first heading which has no anchor
|
|
||||||
else:
|
|
||||||
anchorname = '#' + node.parent['ids'][0]
|
|
||||||
if anchorname not in self.builder.secnumbers:
|
|
||||||
anchorname = '' # try first heading which has no anchor
|
|
||||||
|
|
||||||
if self.builder.secnumbers.get(anchorname):
|
|
||||||
return self.builder.secnumbers[anchorname]
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def add_secnumber(self, node: Element) -> None:
|
|
||||||
secnumber = self.get_secnumber(node)
|
|
||||||
if secnumber:
|
|
||||||
self.body.append('<span class="section-number">%s</span>' %
|
|
||||||
('.'.join(map(str, secnumber)) + self.secnumber_suffix))
|
|
||||||
|
|
||||||
def add_fignumber(self, node: Element) -> None:
|
|
||||||
def append_fignumber(figtype: str, figure_id: str) -> None:
|
|
||||||
if self.builder.name == 'singlehtml':
|
|
||||||
key = "%s/%s" % (self.docnames[-1], figtype)
|
|
||||||
else:
|
|
||||||
key = figtype
|
|
||||||
|
|
||||||
if figure_id in self.builder.fignumbers.get(key, {}):
|
|
||||||
self.body.append('<span class="caption-number">')
|
|
||||||
prefix = self.config.numfig_format.get(figtype)
|
|
||||||
if prefix is None:
|
|
||||||
msg = __('numfig_format is not defined for %s') % figtype
|
|
||||||
logger.warning(msg)
|
|
||||||
else:
|
|
||||||
numbers = self.builder.fignumbers[key][figure_id]
|
|
||||||
self.body.append(prefix % '.'.join(map(str, numbers)) + ' ')
|
|
||||||
self.body.append('</span>')
|
|
||||||
|
|
||||||
figtype = self.builder.env.domains['std'].get_enumerable_node_type(node)
|
|
||||||
if figtype:
|
|
||||||
if len(node['ids']) == 0:
|
|
||||||
msg = __('Any IDs not assigned for %s node') % node.tagname
|
|
||||||
logger.warning(msg, location=node)
|
|
||||||
else:
|
|
||||||
append_fignumber(figtype, node['ids'][0])
|
|
||||||
|
|
||||||
def add_permalink_ref(self, node: Element, title: str) -> None:
|
|
||||||
if node['ids'] and self.config.html_permalinks and self.builder.add_permalinks:
|
|
||||||
format = '<a class="headerlink" href="#%s" title="%s">%s</a>'
|
|
||||||
self.body.append(format % (node['ids'][0], title,
|
|
||||||
self.config.html_permalinks_icon))
|
|
||||||
|
|
||||||
def generate_targets_for_listing(self, node: Element) -> None:
|
|
||||||
"""Generate hyperlink targets for listings.
|
|
||||||
|
|
||||||
Original visit_bullet_list(), visit_definition_list() and visit_enumerated_list()
|
|
||||||
generates hyperlink targets inside listing tags (<ul>, <ol> and <dl>) if multiple
|
|
||||||
IDs are assigned to listings. That is invalid DOM structure.
|
|
||||||
(This is a bug of docutils <= 0.12)
|
|
||||||
|
|
||||||
This exports hyperlink targets before listings to make valid DOM structure.
|
|
||||||
"""
|
|
||||||
for id in node['ids'][1:]:
|
|
||||||
self.body.append('<span id="%s"></span>' % id)
|
|
||||||
node['ids'].remove(id)
|
|
||||||
|
|
||||||
# overwritten
|
|
||||||
def visit_bullet_list(self, node: Element) -> None:
|
|
||||||
if len(node) == 1 and isinstance(node[0], addnodes.toctree):
|
|
||||||
# avoid emitting empty <ul></ul>
|
|
||||||
raise nodes.SkipNode
|
|
||||||
self.generate_targets_for_listing(node)
|
|
||||||
super().visit_bullet_list(node)
|
|
||||||
|
|
||||||
# overwritten
|
|
||||||
def visit_enumerated_list(self, node: Element) -> None:
|
|
||||||
self.generate_targets_for_listing(node)
|
|
||||||
super().visit_enumerated_list(node)
|
|
||||||
|
|
||||||
# overwritten
|
|
||||||
def visit_definition(self, node: Element) -> None:
|
|
||||||
# don't insert </dt> here.
|
|
||||||
self.body.append(self.starttag(node, 'dd', ''))
|
|
||||||
|
|
||||||
# overwritten
|
|
||||||
def depart_definition(self, node: Element) -> None:
|
|
||||||
self.body.append('</dd>\n')
|
|
||||||
|
|
||||||
# overwritten
|
|
||||||
def visit_classifier(self, node: Element) -> None:
|
|
||||||
self.body.append(self.starttag(node, 'span', '', CLASS='classifier'))
|
|
||||||
|
|
||||||
# overwritten
|
|
||||||
def depart_classifier(self, node: Element) -> None:
|
|
||||||
self.body.append('</span>')
|
|
||||||
|
|
||||||
next_node: Node = node.next_node(descend=False, siblings=True)
|
|
||||||
if not isinstance(next_node, nodes.classifier):
|
|
||||||
# close `<dt>` tag at the tail of classifiers
|
|
||||||
self.body.append('</dt>')
|
|
||||||
|
|
||||||
# overwritten
|
|
||||||
def visit_term(self, node: Element) -> None:
|
|
||||||
self.body.append(self.starttag(node, 'dt', ''))
|
|
||||||
|
|
||||||
# overwritten
|
|
||||||
def depart_term(self, node: Element) -> None:
|
|
||||||
next_node: Node = node.next_node(descend=False, siblings=True)
|
|
||||||
if isinstance(next_node, nodes.classifier):
|
|
||||||
# Leave the end tag to `self.depart_classifier()`, in case
|
|
||||||
# there's a classifier.
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
if isinstance(node.parent.parent.parent, addnodes.glossary):
|
|
||||||
# add permalink if glossary terms
|
|
||||||
self.add_permalink_ref(node, _('Permalink to this term'))
|
|
||||||
|
|
||||||
self.body.append('</dt>')
|
|
||||||
|
|
||||||
# overwritten
|
|
||||||
def visit_title(self, node: Element) -> None:
|
|
||||||
if isinstance(node.parent, addnodes.compact_paragraph) and node.parent.get('toctree'):
|
|
||||||
self.body.append(self.starttag(node, 'p', '', CLASS='caption', ROLE='heading'))
|
|
||||||
self.body.append('<span class="caption-text">')
|
|
||||||
self.context.append('</span></p>\n')
|
|
||||||
else:
|
|
||||||
super().visit_title(node)
|
|
||||||
self.add_secnumber(node)
|
|
||||||
self.add_fignumber(node.parent)
|
|
||||||
if isinstance(node.parent, nodes.table):
|
|
||||||
self.body.append('<span class="caption-text">')
|
|
||||||
|
|
||||||
def depart_title(self, node: Element) -> None:
|
|
||||||
close_tag = self.context[-1]
|
|
||||||
if (self.config.html_permalinks and self.builder.add_permalinks and
|
|
||||||
node.parent.hasattr('ids') and node.parent['ids']):
|
|
||||||
# add permalink anchor
|
|
||||||
if close_tag.startswith('</h'):
|
|
||||||
self.add_permalink_ref(node.parent, _('Permalink to this heading'))
|
|
||||||
elif close_tag.startswith('</a></h'):
|
|
||||||
self.body.append('</a><a class="headerlink" href="#%s" ' %
|
|
||||||
node.parent['ids'][0] +
|
|
||||||
'title="%s">%s' % (
|
|
||||||
_('Permalink to this heading'),
|
|
||||||
self.config.html_permalinks_icon))
|
|
||||||
elif isinstance(node.parent, nodes.table):
|
|
||||||
self.body.append('</span>')
|
|
||||||
self.add_permalink_ref(node.parent, _('Permalink to this table'))
|
|
||||||
elif isinstance(node.parent, nodes.table):
|
|
||||||
self.body.append('</span>')
|
|
||||||
|
|
||||||
super().depart_title(node)
|
|
||||||
|
|
||||||
# overwritten
|
|
||||||
def visit_literal_block(self, node: Element) -> None:
|
|
||||||
if node.rawsource != node.astext():
|
|
||||||
# most probably a parsed-literal block -- don't highlight
|
|
||||||
return super().visit_literal_block(node)
|
|
||||||
|
|
||||||
lang = node.get('language', 'default')
|
|
||||||
linenos = node.get('linenos', False)
|
|
||||||
highlight_args = node.get('highlight_args', {})
|
|
||||||
highlight_args['force'] = node.get('force', False)
|
|
||||||
opts = self.config.highlight_options.get(lang, {})
|
|
||||||
|
|
||||||
if linenos and self.config.html_codeblock_linenos_style:
|
|
||||||
linenos = self.config.html_codeblock_linenos_style
|
|
||||||
|
|
||||||
highlighted = self.highlighter.highlight_block(
|
|
||||||
node.rawsource, lang, opts=opts, linenos=linenos,
|
|
||||||
location=node, **highlight_args
|
|
||||||
)
|
|
||||||
starttag = self.starttag(node, 'div', suffix='',
|
|
||||||
CLASS='highlight-%s notranslate' % lang)
|
|
||||||
self.body.append(starttag + highlighted + '</div>\n')
|
|
||||||
raise nodes.SkipNode
|
|
||||||
|
|
||||||
def visit_caption(self, node: Element) -> None:
|
|
||||||
if isinstance(node.parent, nodes.container) and node.parent.get('literal_block'):
|
|
||||||
self.body.append('<div class="code-block-caption">')
|
|
||||||
else:
|
|
||||||
super().visit_caption(node)
|
|
||||||
self.add_fignumber(node.parent)
|
|
||||||
self.body.append(self.starttag(node, 'span', '', CLASS='caption-text'))
|
|
||||||
|
|
||||||
def depart_caption(self, node: Element) -> None:
|
|
||||||
self.body.append('</span>')
|
|
||||||
|
|
||||||
# append permalink if available
|
|
||||||
if isinstance(node.parent, nodes.container) and node.parent.get('literal_block'):
|
|
||||||
self.add_permalink_ref(node.parent, _('Permalink to this code'))
|
|
||||||
elif isinstance(node.parent, nodes.figure):
|
|
||||||
self.add_permalink_ref(node.parent, _('Permalink to this image'))
|
|
||||||
elif node.parent.get('toctree'):
|
|
||||||
self.add_permalink_ref(node.parent.parent, _('Permalink to this toctree'))
|
|
||||||
|
|
||||||
if isinstance(node.parent, nodes.container) and node.parent.get('literal_block'):
|
|
||||||
self.body.append('</div>\n')
|
|
||||||
else:
|
|
||||||
super().depart_caption(node)
|
|
||||||
|
|
||||||
def visit_doctest_block(self, node: Element) -> None:
|
|
||||||
self.visit_literal_block(node)
|
|
||||||
|
|
||||||
# overwritten to add the <div> (for XHTML compliance)
|
|
||||||
def visit_block_quote(self, node: Element) -> None:
|
|
||||||
self.body.append(self.starttag(node, 'blockquote') + '<div>')
|
|
||||||
|
|
||||||
def depart_block_quote(self, node: Element) -> None:
|
|
||||||
self.body.append('</div></blockquote>\n')
|
|
||||||
|
|
||||||
# overwritten
|
|
||||||
def visit_literal(self, node: Element) -> None:
|
|
||||||
if 'kbd' in node['classes']:
|
|
||||||
self.body.append(self.starttag(node, 'kbd', '',
|
|
||||||
CLASS='docutils literal notranslate'))
|
|
||||||
return
|
|
||||||
lang = node.get("language", None)
|
|
||||||
if 'code' not in node['classes'] or not lang:
|
|
||||||
self.body.append(self.starttag(node, 'code', '',
|
|
||||||
CLASS='docutils literal notranslate'))
|
|
||||||
self.protect_literal_text += 1
|
|
||||||
return
|
|
||||||
|
|
||||||
opts = self.config.highlight_options.get(lang, {})
|
|
||||||
highlighted = self.highlighter.highlight_block(
|
|
||||||
node.astext(), lang, opts=opts, location=node, nowrap=True)
|
|
||||||
starttag = self.starttag(
|
|
||||||
node,
|
|
||||||
"code",
|
|
||||||
suffix="",
|
|
||||||
CLASS="docutils literal highlight highlight-%s" % lang,
|
|
||||||
)
|
|
||||||
self.body.append(starttag + highlighted.strip() + "</code>")
|
|
||||||
raise nodes.SkipNode
|
|
||||||
|
|
||||||
def depart_literal(self, node: Element) -> None:
|
|
||||||
if 'kbd' in node['classes']:
|
|
||||||
self.body.append('</kbd>')
|
|
||||||
else:
|
|
||||||
self.protect_literal_text -= 1
|
|
||||||
self.body.append('</code>')
|
|
||||||
|
|
||||||
def visit_productionlist(self, node: Element) -> None:
|
|
||||||
self.body.append(self.starttag(node, 'pre'))
|
|
||||||
names = []
|
|
||||||
productionlist = cast(Iterable[addnodes.production], node)
|
|
||||||
for production in productionlist:
|
|
||||||
names.append(production['tokenname'])
|
|
||||||
maxlen = max(len(name) for name in names)
|
|
||||||
lastname = None
|
|
||||||
for production in productionlist:
|
|
||||||
if production['tokenname']:
|
|
||||||
lastname = production['tokenname'].ljust(maxlen)
|
|
||||||
self.body.append(self.starttag(production, 'strong', ''))
|
|
||||||
self.body.append(lastname + '</strong> ::= ')
|
|
||||||
elif lastname is not None:
|
|
||||||
self.body.append('%s ' % (' ' * len(lastname)))
|
|
||||||
production.walkabout(self)
|
|
||||||
self.body.append('\n')
|
|
||||||
self.body.append('</pre>\n')
|
|
||||||
raise nodes.SkipNode
|
|
||||||
|
|
||||||
def depart_productionlist(self, node: Element) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def visit_production(self, node: Element) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def depart_production(self, node: Element) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def visit_centered(self, node: Element) -> None:
|
|
||||||
self.body.append(self.starttag(node, 'p', CLASS="centered") +
|
|
||||||
'<strong>')
|
|
||||||
|
|
||||||
def depart_centered(self, node: Element) -> None:
|
|
||||||
self.body.append('</strong></p>')
|
|
||||||
|
|
||||||
# overwritten
|
|
||||||
def should_be_compact_paragraph(self, node: Node) -> bool:
|
|
||||||
"""Determine if the <p> tags around paragraph can be omitted."""
|
|
||||||
if isinstance(node.parent, addnodes.desc_content):
|
|
||||||
# Never compact desc_content items.
|
|
||||||
return False
|
|
||||||
if isinstance(node.parent, addnodes.versionmodified):
|
|
||||||
# Never compact versionmodified nodes.
|
|
||||||
return False
|
|
||||||
return super().should_be_compact_paragraph(node)
|
|
||||||
|
|
||||||
def visit_compact_paragraph(self, node: Element) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def depart_compact_paragraph(self, node: Element) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def visit_download_reference(self, node: Element) -> None:
|
|
||||||
atts = {'class': 'reference download',
|
|
||||||
'download': ''}
|
|
||||||
|
|
||||||
if not self.builder.download_support:
|
|
||||||
self.context.append('')
|
|
||||||
elif 'refuri' in node:
|
|
||||||
atts['class'] += ' external'
|
|
||||||
atts['href'] = node['refuri']
|
|
||||||
self.body.append(self.starttag(node, 'a', '', **atts))
|
|
||||||
self.context.append('</a>')
|
|
||||||
elif 'filename' in node:
|
|
||||||
atts['class'] += ' internal'
|
|
||||||
atts['href'] = posixpath.join(self.builder.dlpath,
|
|
||||||
urllib.parse.quote(node['filename']))
|
|
||||||
self.body.append(self.starttag(node, 'a', '', **atts))
|
|
||||||
self.context.append('</a>')
|
|
||||||
else:
|
|
||||||
self.context.append('')
|
|
||||||
|
|
||||||
def depart_download_reference(self, node: Element) -> None:
|
|
||||||
self.body.append(self.context.pop())
|
|
||||||
|
|
||||||
# overwritten
|
|
||||||
def visit_figure(self, node: Element) -> None:
|
|
||||||
# set align=default if align not specified to give a default style
|
|
||||||
node.setdefault('align', 'default')
|
|
||||||
|
|
||||||
return super().visit_figure(node)
|
|
||||||
|
|
||||||
# overwritten
|
|
||||||
def visit_image(self, node: Element) -> None:
|
|
||||||
olduri = node['uri']
|
|
||||||
# rewrite the URI if the environment knows about it
|
|
||||||
if olduri in self.builder.images:
|
|
||||||
node['uri'] = posixpath.join(self.builder.imgpath,
|
|
||||||
urllib.parse.quote(self.builder.images[olduri]))
|
|
||||||
|
|
||||||
if 'scale' in node:
|
|
||||||
# Try to figure out image height and width. Docutils does that too,
|
|
||||||
# but it tries the final file name, which does not necessarily exist
|
|
||||||
# yet at the time the HTML file is written.
|
|
||||||
if not ('width' in node and 'height' in node):
|
|
||||||
size = get_image_size(os.path.join(self.builder.srcdir, olduri))
|
|
||||||
if size is None:
|
|
||||||
logger.warning(
|
|
||||||
__('Could not obtain image size. :scale: option is ignored.'),
|
|
||||||
location=node,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
if 'width' not in node:
|
|
||||||
node['width'] = str(size[0])
|
|
||||||
if 'height' not in node:
|
|
||||||
node['height'] = str(size[1])
|
|
||||||
|
|
||||||
uri = node['uri']
|
|
||||||
if uri.lower().endswith(('svg', 'svgz')):
|
|
||||||
atts = {'src': uri}
|
|
||||||
if 'width' in node:
|
|
||||||
atts['width'] = node['width']
|
|
||||||
if 'height' in node:
|
|
||||||
atts['height'] = node['height']
|
|
||||||
if 'scale' in node:
|
|
||||||
if 'width' in atts:
|
|
||||||
atts['width'] = multiply_length(atts['width'], node['scale'])
|
|
||||||
if 'height' in atts:
|
|
||||||
atts['height'] = multiply_length(atts['height'], node['scale'])
|
|
||||||
atts['alt'] = node.get('alt', uri)
|
|
||||||
if 'align' in node:
|
|
||||||
atts['class'] = 'align-%s' % node['align']
|
|
||||||
self.body.append(self.emptytag(node, 'img', '', **atts))
|
|
||||||
return
|
|
||||||
|
|
||||||
super().visit_image(node)
|
|
||||||
|
|
||||||
# overwritten
|
|
||||||
def depart_image(self, node: Element) -> None:
|
|
||||||
if node['uri'].lower().endswith(('svg', 'svgz')):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
super().depart_image(node)
|
|
||||||
|
|
||||||
def visit_toctree(self, node: Element) -> None:
|
|
||||||
# this only happens when formatting a toc from env.tocs -- in this
|
|
||||||
# case we don't want to include the subtree
|
|
||||||
raise nodes.SkipNode
|
|
||||||
|
|
||||||
def visit_index(self, node: Element) -> None:
|
|
||||||
raise nodes.SkipNode
|
|
||||||
|
|
||||||
def visit_tabular_col_spec(self, node: Element) -> None:
|
|
||||||
raise nodes.SkipNode
|
|
||||||
|
|
||||||
def visit_glossary(self, node: Element) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def depart_glossary(self, node: Element) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def visit_acks(self, node: Element) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def depart_acks(self, node: Element) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def visit_hlist(self, node: Element) -> None:
|
|
||||||
self.body.append('<table class="hlist"><tr>')
|
|
||||||
|
|
||||||
def depart_hlist(self, node: Element) -> None:
|
|
||||||
self.body.append('</tr></table>\n')
|
|
||||||
|
|
||||||
def visit_hlistcol(self, node: Element) -> None:
|
|
||||||
self.body.append('<td>')
|
|
||||||
|
|
||||||
def depart_hlistcol(self, node: Element) -> None:
|
|
||||||
self.body.append('</td>')
|
|
||||||
|
|
||||||
def visit_option_group(self, node: Element) -> None:
|
|
||||||
super().visit_option_group(node)
|
|
||||||
self.context[-2] = self.context[-2].replace(' ', ' ')
|
|
||||||
|
|
||||||
# overwritten
|
|
||||||
def visit_Text(self, node: Text) -> None:
|
|
||||||
text = node.astext()
|
|
||||||
encoded = self.encode(text)
|
|
||||||
if self.protect_literal_text:
|
|
||||||
# moved here from base class's visit_literal to support
|
|
||||||
# more formatting in literal nodes
|
|
||||||
for token in self.words_and_spaces.findall(encoded):
|
|
||||||
if token.strip():
|
|
||||||
# protect literal text from line wrapping
|
|
||||||
self.body.append('<span class="pre">%s</span>' % token)
|
|
||||||
elif token in ' \n':
|
|
||||||
# allow breaks at whitespace
|
|
||||||
self.body.append(token)
|
|
||||||
else:
|
|
||||||
# protect runs of multiple spaces; the last one can wrap
|
|
||||||
self.body.append(' ' * (len(token) - 1) + ' ')
|
|
||||||
else:
|
|
||||||
if self.in_mailto and self.settings.cloak_email_addresses:
|
|
||||||
encoded = self.cloak_email(encoded)
|
|
||||||
self.body.append(encoded)
|
|
||||||
|
|
||||||
def visit_note(self, node: Element) -> None:
|
|
||||||
self.visit_admonition(node, 'note')
|
|
||||||
|
|
||||||
def depart_note(self, node: Element) -> None:
|
|
||||||
self.depart_admonition(node)
|
|
||||||
|
|
||||||
def visit_warning(self, node: Element) -> None:
|
|
||||||
self.visit_admonition(node, 'warning')
|
|
||||||
|
|
||||||
def depart_warning(self, node: Element) -> None:
|
|
||||||
self.depart_admonition(node)
|
|
||||||
|
|
||||||
def visit_attention(self, node: Element) -> None:
|
|
||||||
self.visit_admonition(node, 'attention')
|
|
||||||
|
|
||||||
def depart_attention(self, node: Element) -> None:
|
|
||||||
self.depart_admonition(node)
|
|
||||||
|
|
||||||
def visit_caution(self, node: Element) -> None:
|
|
||||||
self.visit_admonition(node, 'caution')
|
|
||||||
|
|
||||||
def depart_caution(self, node: Element) -> None:
|
|
||||||
self.depart_admonition(node)
|
|
||||||
|
|
||||||
def visit_danger(self, node: Element) -> None:
|
|
||||||
self.visit_admonition(node, 'danger')
|
|
||||||
|
|
||||||
def depart_danger(self, node: Element) -> None:
|
|
||||||
self.depart_admonition(node)
|
|
||||||
|
|
||||||
def visit_error(self, node: Element) -> None:
|
|
||||||
self.visit_admonition(node, 'error')
|
|
||||||
|
|
||||||
def depart_error(self, node: Element) -> None:
|
|
||||||
self.depart_admonition(node)
|
|
||||||
|
|
||||||
def visit_hint(self, node: Element) -> None:
|
|
||||||
self.visit_admonition(node, 'hint')
|
|
||||||
|
|
||||||
def depart_hint(self, node: Element) -> None:
|
|
||||||
self.depart_admonition(node)
|
|
||||||
|
|
||||||
def visit_important(self, node: Element) -> None:
|
|
||||||
self.visit_admonition(node, 'important')
|
|
||||||
|
|
||||||
def depart_important(self, node: Element) -> None:
|
|
||||||
self.depart_admonition(node)
|
|
||||||
|
|
||||||
def visit_tip(self, node: Element) -> None:
|
|
||||||
self.visit_admonition(node, 'tip')
|
|
||||||
|
|
||||||
def depart_tip(self, node: Element) -> None:
|
|
||||||
self.depart_admonition(node)
|
|
||||||
|
|
||||||
def visit_literal_emphasis(self, node: Element) -> None:
|
|
||||||
return self.visit_emphasis(node)
|
|
||||||
|
|
||||||
def depart_literal_emphasis(self, node: Element) -> None:
|
|
||||||
return self.depart_emphasis(node)
|
|
||||||
|
|
||||||
def visit_literal_strong(self, node: Element) -> None:
|
|
||||||
return self.visit_strong(node)
|
|
||||||
|
|
||||||
def depart_literal_strong(self, node: Element) -> None:
|
|
||||||
return self.depart_strong(node)
|
|
||||||
|
|
||||||
def visit_abbreviation(self, node: Element) -> None:
|
|
||||||
attrs = {}
|
|
||||||
if node.hasattr('explanation'):
|
|
||||||
attrs['title'] = node['explanation']
|
|
||||||
self.body.append(self.starttag(node, 'abbr', '', **attrs))
|
|
||||||
|
|
||||||
def depart_abbreviation(self, node: Element) -> None:
|
|
||||||
self.body.append('</abbr>')
|
|
||||||
|
|
||||||
def visit_manpage(self, node: Element) -> None:
|
|
||||||
self.visit_literal_emphasis(node)
|
|
||||||
if self.manpages_url:
|
|
||||||
node['refuri'] = self.manpages_url.format(**node.attributes)
|
|
||||||
self.visit_reference(node)
|
|
||||||
|
|
||||||
def depart_manpage(self, node: Element) -> None:
|
|
||||||
if self.manpages_url:
|
|
||||||
self.depart_reference(node)
|
|
||||||
self.depart_literal_emphasis(node)
|
|
||||||
|
|
||||||
# overwritten to add even/odd classes
|
|
||||||
|
|
||||||
def visit_table(self, node: Element) -> None:
|
|
||||||
self._table_row_indices.append(0)
|
|
||||||
|
|
||||||
# set align=default if align not specified to give a default style
|
|
||||||
node.setdefault('align', 'default')
|
|
||||||
|
|
||||||
return super().visit_table(node)
|
|
||||||
|
|
||||||
def depart_table(self, node: Element) -> None:
|
|
||||||
self._table_row_indices.pop()
|
|
||||||
super().depart_table(node)
|
|
||||||
|
|
||||||
def visit_row(self, node: Element) -> None:
|
|
||||||
self._table_row_indices[-1] += 1
|
|
||||||
if self._table_row_indices[-1] % 2 == 0:
|
|
||||||
node['classes'].append('row-even')
|
|
||||||
else:
|
|
||||||
node['classes'].append('row-odd')
|
|
||||||
self.body.append(self.starttag(node, 'tr', ''))
|
|
||||||
node.column = 0 # type: ignore
|
|
||||||
|
|
||||||
def visit_entry(self, node: Element) -> None:
|
|
||||||
super().visit_entry(node)
|
|
||||||
if self.body[-1] == ' ':
|
|
||||||
self.body[-1] = ' '
|
|
||||||
|
|
||||||
def visit_field_list(self, node: Element) -> None:
|
|
||||||
self._fieldlist_row_indices.append(0)
|
|
||||||
return super().visit_field_list(node)
|
|
||||||
|
|
||||||
def depart_field_list(self, node: Element) -> None:
|
|
||||||
self._fieldlist_row_indices.pop()
|
|
||||||
return super().depart_field_list(node)
|
|
||||||
|
|
||||||
def visit_field(self, node: Element) -> None:
|
|
||||||
self._fieldlist_row_indices[-1] += 1
|
|
||||||
if self._fieldlist_row_indices[-1] % 2 == 0:
|
|
||||||
node['classes'].append('field-even')
|
|
||||||
else:
|
|
||||||
node['classes'].append('field-odd')
|
|
||||||
self.body.append(self.starttag(node, 'tr', '', CLASS='field'))
|
|
||||||
|
|
||||||
def visit_field_name(self, node: Element) -> None:
|
|
||||||
context_count = len(self.context)
|
|
||||||
super().visit_field_name(node)
|
|
||||||
if context_count != len(self.context):
|
|
||||||
self.context[-1] = self.context[-1].replace(' ', ' ')
|
|
||||||
|
|
||||||
def visit_math(self, node: Element, math_env: str = '') -> None:
|
|
||||||
name = self.builder.math_renderer_name
|
|
||||||
visit, _ = self.builder.app.registry.html_inline_math_renderers[name]
|
|
||||||
visit(self, node)
|
|
||||||
|
|
||||||
def depart_math(self, node: Element, math_env: str = '') -> None:
|
|
||||||
name = self.builder.math_renderer_name
|
|
||||||
_, depart = self.builder.app.registry.html_inline_math_renderers[name]
|
|
||||||
if depart: # type: ignore[truthy-function]
|
|
||||||
depart(self, node)
|
|
||||||
|
|
||||||
def visit_math_block(self, node: Element, math_env: str = '') -> None:
|
|
||||||
name = self.builder.math_renderer_name
|
|
||||||
visit, _ = self.builder.app.registry.html_block_math_renderers[name]
|
|
||||||
visit(self, node)
|
|
||||||
|
|
||||||
def depart_math_block(self, node: Element, math_env: str = '') -> None:
|
|
||||||
name = self.builder.math_renderer_name
|
|
||||||
_, depart = self.builder.app.registry.html_block_math_renderers[name]
|
|
||||||
if depart: # type: ignore[truthy-function]
|
|
||||||
depart(self, node)
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
from docutils.writers.docutils_xml import XMLTranslator
|
from docutils.writers.docutils_xml import XMLTranslator
|
||||||
|
|
||||||
from sphinx.writers.html import HTMLTranslator
|
from sphinx.writers.html import HTML5Translator
|
||||||
from sphinx.writers.latex import LaTeXTranslator
|
from sphinx.writers.latex import LaTeXTranslator
|
||||||
from sphinx.writers.manpage import ManualPageTranslator
|
from sphinx.writers.manpage import ManualPageTranslator
|
||||||
from sphinx.writers.texinfo import TexinfoTranslator
|
from sphinx.writers.texinfo import TexinfoTranslator
|
||||||
@ -14,23 +14,23 @@ from sphinx.writers.text import TextTranslator
|
|||||||
project = 'test'
|
project = 'test'
|
||||||
|
|
||||||
|
|
||||||
class ConfHTMLTranslator(HTMLTranslator):
|
class ConfHTMLTranslator(HTML5Translator):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ConfDirHTMLTranslator(HTMLTranslator):
|
class ConfDirHTMLTranslator(HTML5Translator):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ConfSingleHTMLTranslator(HTMLTranslator):
|
class ConfSingleHTMLTranslator(HTML5Translator):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ConfPickleTranslator(HTMLTranslator):
|
class ConfPickleTranslator(HTML5Translator):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ConfJsonTranslator(HTMLTranslator):
|
class ConfJsonTranslator(HTML5Translator):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from sphinx.writers.html import HTMLTranslator
|
from sphinx.writers.html import HTML5Translator
|
||||||
|
|
||||||
|
|
||||||
class ExtHTMLTranslator(HTMLTranslator):
|
class ExtHTMLTranslator(HTML5Translator):
|
||||||
pass
|
pass
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
from sphinx.writers.html import HTMLTranslator
|
from sphinx.writers.html import HTML5Translator
|
||||||
|
|
||||||
project = 'test'
|
project = 'test'
|
||||||
|
|
||||||
|
|
||||||
class ConfHTMLTranslator(HTMLTranslator):
|
class ConfHTMLTranslator(HTML5Translator):
|
||||||
depart_with_node = 0
|
depart_with_node = 0
|
||||||
|
|
||||||
def depart_admonition(self, node=None):
|
def depart_admonition(self, node=None):
|
||||||
if node is not None:
|
if node is not None:
|
||||||
self.depart_with_node += 1
|
self.depart_with_node += 1
|
||||||
HTMLTranslator.depart_admonition(self, node)
|
HTML5Translator.depart_admonition(self, node)
|
||||||
|
|
||||||
|
|
||||||
def setup(app):
|
def setup(app):
|
||||||
|
@ -16,7 +16,7 @@ from sphinx.testing.util import Struct, assert_node
|
|||||||
from sphinx.transforms import SphinxSmartQuotes
|
from sphinx.transforms import SphinxSmartQuotes
|
||||||
from sphinx.util import texescape
|
from sphinx.util import texescape
|
||||||
from sphinx.util.docutils import sphinx_domains
|
from sphinx.util.docutils import sphinx_domains
|
||||||
from sphinx.writers.html import HTMLTranslator, HTMLWriter
|
from sphinx.writers.html import HTML5Translator, HTMLWriter
|
||||||
from sphinx.writers.latex import LaTeXTranslator, LaTeXWriter
|
from sphinx.writers.latex import LaTeXTranslator, LaTeXWriter
|
||||||
|
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ class ForgivingTranslator:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ForgivingHTMLTranslator(HTMLTranslator, ForgivingTranslator):
|
class ForgivingHTMLTranslator(HTML5Translator, ForgivingTranslator):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@ -357,27 +357,27 @@ def get_verifier(verify, verify_re):
|
|||||||
# description list: simple
|
# description list: simple
|
||||||
'verify',
|
'verify',
|
||||||
'term\n description',
|
'term\n description',
|
||||||
'<dl class="docutils">\n<dt>term</dt><dd>description</dd>\n</dl>',
|
'<dl class="simple">\n<dt>term</dt><dd><p>description</p>\n</dd>\n</dl>',
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
# description list: with classifiers
|
# description list: with classifiers
|
||||||
'verify',
|
'verify',
|
||||||
'term : class1 : class2\n description',
|
'term : class1 : class2\n description',
|
||||||
('<dl class="docutils">\n<dt>term<span class="classifier">class1</span>'
|
('<dl class="simple">\n<dt>term<span class="classifier">class1</span>'
|
||||||
'<span class="classifier">class2</span></dt><dd>description</dd>\n</dl>'),
|
'<span class="classifier">class2</span></dt><dd><p>description</p>\n</dd>\n</dl>'),
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
# glossary (description list): multiple terms
|
# glossary (description list): multiple terms
|
||||||
'verify',
|
'verify',
|
||||||
'.. glossary::\n\n term1\n term2\n description',
|
'.. glossary::\n\n term1\n term2\n description',
|
||||||
('<dl class="glossary docutils">\n'
|
('<dl class="simple glossary">\n'
|
||||||
'<dt id="term-term1">term1<a class="headerlink" href="#term-term1"'
|
'<dt id="term-term1">term1<a class="headerlink" href="#term-term1"'
|
||||||
' title="Permalink to this term">¶</a></dt>'
|
' title="Permalink to this term">¶</a></dt>'
|
||||||
'<dt id="term-term2">term2<a class="headerlink" href="#term-term2"'
|
'<dt id="term-term2">term2<a class="headerlink" href="#term-term2"'
|
||||||
' title="Permalink to this term">¶</a></dt>'
|
' title="Permalink to this term">¶</a></dt>'
|
||||||
'<dd>description</dd>\n</dl>'),
|
'<dd><p>description</p>\n</dd>\n</dl>'),
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
])
|
])
|
||||||
|
Loading…
Reference in New Issue
Block a user