diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py
index 7ea70fa4d..2fb1b3655 100644
--- a/sphinx/builders/html/__init__.py
+++ b/sphinx/builders/html/__init__.py
@@ -43,7 +43,8 @@ from sphinx.util.inventory import InventoryFile
from sphinx.util.matching import DOTFILES, Matcher, patmatch
from sphinx.util.osutil import copyfile, ensuredir, os_path, relative_uri
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
#: the filename for the inventory of objects
@@ -372,7 +373,7 @@ class StandaloneHTMLBuilder(Builder):
@property
def default_translator_class(self) -> Type[nodes.NodeVisitor]: # type: ignore
if self.config.html4_writer:
- return HTMLTranslator # RemovedInSphinx70Warning
+ return HTML4Translator # RemovedInSphinx70Warning
else:
return HTML5Translator
@@ -1326,8 +1327,12 @@ import sphinx.builders.singlehtml # noqa: E402,F401
deprecated_alias('sphinx.builders.html',
{
'html5_ready': True,
+ 'HTMLTranslator': HTML4Translator,
},
- RemovedInSphinx70Warning)
+ RemovedInSphinx70Warning,
+ {
+ 'HTMLTranslator': 'sphinx.writers.html.HTML5Translator',
+ })
def setup(app: Sphinx) -> Dict[str, Any]:
diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py
index 3cb2e54a7..268fb65a2 100644
--- a/sphinx/ext/autosummary/__init__.py
+++ b/sphinx/ext/autosummary/__init__.py
@@ -84,7 +84,7 @@ from sphinx.util.docutils import (NullReporter, SphinxDirective, SphinxRole, new
from sphinx.util.inspect import signature_from_str
from sphinx.util.matching import Matcher
from sphinx.util.typing import OptionSpec
-from sphinx.writers.html import HTMLTranslator
+from sphinx.writers.html import HTML5Translator
logger = logging.getLogger(__name__)
@@ -116,7 +116,7 @@ class autosummary_table(nodes.comment):
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."""
try:
table = cast(nodes.table, node[0])
diff --git a/sphinx/ext/graphviz.py b/sphinx/ext/graphviz.py
index 74291163f..ed7278f5c 100644
--- a/sphinx/ext/graphviz.py
+++ b/sphinx/ext/graphviz.py
@@ -23,7 +23,7 @@ from sphinx.util.i18n import search_image_for_language
from sphinx.util.nodes import set_source_info
from sphinx.util.osutil import ensuredir
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.manpage import ManualPageTranslator
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
-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,
alt: Optional[str] = None, filename: Optional[str] = None
) -> Tuple[str, str]:
@@ -315,7 +315,7 @@ def render_dot_html(self: HTMLTranslator, node: graphviz, code: str, options: Di
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'))
diff --git a/sphinx/ext/imgmath.py b/sphinx/ext/imgmath.py
index ac525d451..a5946aa01 100644
--- a/sphinx/ext/imgmath.py
+++ b/sphinx/ext/imgmath.py
@@ -24,7 +24,7 @@ from sphinx.util.math import get_node_equation_number, wrap_displaymath
from sphinx.util.osutil import ensuredir
from sphinx.util.png import read_png_depth, write_png_depth
from sphinx.util.template import LaTeXRenderer
-from sphinx.writers.html import HTMLTranslator
+from sphinx.writers.html import HTML5Translator
logger = logging.getLogger(__name__)
@@ -201,7 +201,7 @@ def convert_dvi_to_svg(dvipath: str, builder: Builder, out_path: str) -> Optiona
def render_math(
- self: HTMLTranslator,
+ self: HTML5Translator,
math: str,
) -> Tuple[Optional[str], Optional[int]]:
"""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
-def get_tooltip(self: HTMLTranslator, node: Element) -> str:
+def get_tooltip(self: HTML5Translator, node: Element) -> str:
if self.builder.config.imgmath_add_tooltips:
return ' alt="%s"' % self.encode(node.astext()).strip()
return ''
-def html_visit_math(self: HTMLTranslator, node: nodes.math) -> None:
+def html_visit_math(self: HTML5Translator, node: nodes.math) -> None:
try:
rendered_path, depth = render_math(self, '$' + node.astext() + '$')
except MathExtError as exc:
@@ -326,7 +326,7 @@ def html_visit_math(self: HTMLTranslator, node: nodes.math) -> None:
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']:
latex = node.astext()
else:
diff --git a/sphinx/ext/inheritance_diagram.py b/sphinx/ext/inheritance_diagram.py
index 5a1321328..38fc9255a 100644
--- a/sphinx/ext/inheritance_diagram.py
+++ b/sphinx/ext/inheritance_diagram.py
@@ -47,7 +47,7 @@ from sphinx.ext.graphviz import (figure_wrapper, graphviz, render_dot_html, rend
from sphinx.util import md5
from sphinx.util.docutils import SphinxDirective
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.texinfo import TexinfoTranslator
@@ -387,7 +387,7 @@ def get_graph_hash(node: inheritance_diagram) -> str:
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
image map.
diff --git a/sphinx/ext/mathjax.py b/sphinx/ext/mathjax.py
index e87e9ea64..9bdfc09c2 100644
--- a/sphinx/ext/mathjax.py
+++ b/sphinx/ext/mathjax.py
@@ -16,7 +16,7 @@ from sphinx.domains.math import MathDomain
from sphinx.errors import ExtensionError
from sphinx.locale import _
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:
# 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__)
-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.builder.config.mathjax_inline[0] +
self.encode(node.astext()) +
@@ -33,7 +33,7 @@ def html_visit_math(self: HTMLTranslator, node: nodes.math) -> None:
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'))
if node['nowrap']:
self.body.append(self.encode(node.astext()))
diff --git a/sphinx/ext/todo.py b/sphinx/ext/todo.py
index 79d1c0734..e35cbdba4 100644
--- a/sphinx/ext/todo.py
+++ b/sphinx/ext/todo.py
@@ -22,7 +22,7 @@ from sphinx.locale import _, __
from sphinx.util import logging, texescape
from sphinx.util.docutils import SphinxDirective, new_document
from sphinx.util.typing import OptionSpec
-from sphinx.writers.html import HTMLTranslator
+from sphinx.writers.html import HTML5Translator
from sphinx.writers.latex import LaTeXTranslator
logger = logging.getLogger(__name__)
@@ -188,14 +188,14 @@ class TodoListProcessor:
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:
self.visit_admonition(node)
else:
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)
diff --git a/sphinx/util/math.py b/sphinx/util/math.py
index 0c42a00ed..121c606c5 100644
--- a/sphinx/util/math.py
+++ b/sphinx/util/math.py
@@ -4,10 +4,10 @@ from typing import Optional
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:
figtype = 'displaymath'
if writer.builder.name == 'singlehtml':
diff --git a/sphinx/writers/_html4.py b/sphinx/writers/_html4.py
new file mode 100644
index 000000000..4ff2ebd3a
--- /dev/null
+++ b/sphinx/writers/_html4.py
@@ -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('' % 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('\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('\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('
')
+
+ 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('')
+
+ 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('')
+
+ # 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('')
+
+ 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('')
+
+ 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(' ')
+ self.body.append('')
+
+ def depart_desc_returns(self, node: Element) -> None:
+ self.body.append('')
+
+ def visit_desc_parameterlist(self, node: Element) -> None:
+ self.body.append(' ')
+ self.body.append(' (')
+ 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(')')
+
+ # 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('')
+
+ def depart_desc_parameter(self, node: Element) -> None:
+ if not node.hasattr('noemph'):
+ self.body.append('')
+ 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('[')
+
+ def depart_desc_optional(self, node: Element) -> None:
+ self.optional_param_level -= 1
+ self.body.append(']')
+
+ 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('')
+
+ ##############################################
+
+ 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('\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('\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('%s' %
+ ('.'.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('')
+ 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('')
+
+ 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 = '%s'
+ 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 (
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('') + 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('') + 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('
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('') - 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('') - 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('
description
\ndescription
\n