')
- 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('')
-
- # 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('
\n')
- else:
- super().depart_caption(node)
-
- def visit_doctest_block(self, node: Element) -> None:
- self.visit_literal_block(node)
-
- # overwritten to add the (for XHTML compliance)
- def visit_block_quote(self, node: Element) -> None:
- self.body.append(self.starttag(node, 'blockquote') + '
')
-
- def depart_block_quote(self, node: Element) -> None:
- self.body.append('
\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() + "")
- raise nodes.SkipNode
-
- def depart_literal(self, node: Element) -> None:
- if 'kbd' in node['classes']:
- self.body.append('')
- else:
- self.protect_literal_text -= 1
- self.body.append('')
-
- 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 + ' ::= ')
- elif lastname is not None:
- self.body.append('%s ' % (' ' * len(lastname)))
- production.walkabout(self)
- self.body.append('\n')
- self.body.append('\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") +
- '
')
-
- def depart_centered(self, node: Element) -> None:
- self.body.append('')
-
- # overwritten
- def should_be_compact_paragraph(self, node: Node) -> bool:
- """Determine if the
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('
')
-
- def depart_hlist(self, node: Element) -> None:
- self.body.append('
\n')
-
- def visit_hlistcol(self, node: Element) -> None:
- self.body.append('
')
-
- def depart_hlistcol(self, node: Element) -> None:
- self.body.append(' | ')
-
- 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('
%s' % 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('')
-
- 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)
diff --git a/sphinx/writers/html.py b/sphinx/writers/html.py
index faa8d8ad8c1..198e035087b 100644
--- a/sphinx/writers/html.py
+++ b/sphinx/writers/html.py
@@ -7,15 +7,14 @@
from docutils.writers.html4css1 import Writer
from sphinx.util import logging
-from sphinx.writers._html4 import HTML4Translator
-from sphinx.writers.html5 import HTML5Translator # NoQA: F401
+from sphinx.writers.html5 import HTML5Translator
if TYPE_CHECKING:
from sphinx.builders.html import StandaloneHTMLBuilder
logger = logging.getLogger(__name__)
-HTMLTranslator = HTML4Translator
+HTMLTranslator = HTML5Translator
# A good overview of the purpose behind these classes can be found here:
# http://www.arnebrodowski.de/blog/write-your-own-restructuredtext-writer.html
@@ -33,7 +32,7 @@ def __init__(self, builder: StandaloneHTMLBuilder) -> None:
def translate(self) -> None:
# sadly, this is mostly copied from parent class
visitor = self.builder.create_translator(self.document, self.builder)
- self.visitor = cast(HTML4Translator, visitor)
+ self.visitor = cast(HTML5Translator, visitor)
self.document.walkabout(visitor)
self.output = self.visitor.astext()
for attr in ('head_prefix', 'stylesheet', 'head', 'body_prefix',
diff --git a/tests/test_build_html.py b/tests/test_build_html.py
index 013c09bdb67..8591897fb56 100644
--- a/tests/test_build_html.py
+++ b/tests/test_build_html.py
@@ -123,19 +123,17 @@ def test_html_warnings(app, warning):
'--- Got:\n' + html_warnings
-@pytest.mark.sphinx('html', confoverrides={'html4_writer': True})
-def test_html4_output(app, status, warning):
- app.build()
-
-
-def test_html4_deprecation(make_app, tempdir):
+def test_html4_error(make_app, tempdir):
(tempdir / 'conf.py').write_text('', encoding='utf-8')
- app = make_app(
- buildername='html',
- srcdir=tempdir,
- confoverrides={'html4_writer': True},
- )
- assert 'HTML 4 output is deprecated and will be removed' in app._warning.getvalue()
+ with pytest.raises(
+ ConfigError,
+ match=r'HTML 4 is no longer supported by Sphinx',
+ ):
+ make_app(
+ buildername='html',
+ srcdir=tempdir,
+ confoverrides={'html4_writer': True},
+ )
@pytest.mark.parametrize("fname,expect", flat_dict({