From ad473730a3763f241e9aaea1a87d1893f01b86fd Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Fri, 28 Apr 2023 11:32:12 +0100 Subject: Remove HTML 4 support (#11385) --- sphinx/builders/html/__init__.py | 22 +- sphinx/themes/basic/layout.html | 16 +- sphinx/writers/_html4.py | 857 --------------------------------------- sphinx/writers/html.py | 7 +- tests/test_build_html.py | 22 +- 5 files changed, 25 insertions(+), 899 deletions(-) delete mode 100644 sphinx/writers/_html4.py diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py index cd6e9498e..8b8c426a4 100644 --- a/sphinx/builders/html/__init__.py +++ b/sphinx/builders/html/__init__.py @@ -45,7 +45,6 @@ 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._html4 import HTML4Translator from sphinx.writers.html import HTMLWriter from sphinx.writers.html5 import HTML5Translator @@ -374,10 +373,7 @@ class StandaloneHTMLBuilder(Builder): @property def default_translator_class(self) -> type[nodes.NodeVisitor]: # type: ignore - if self.config.html4_writer: - return HTML4Translator # RemovedInSphinx70Warning - else: - return HTML5Translator + return HTML5Translator @property def math_renderer_name(self) -> str: @@ -565,7 +561,7 @@ class StandaloneHTMLBuilder(Builder): 'parents': [], 'logo_url': logo, 'favicon_url': favicon, - 'html5_doctype': not self.config.html4_writer, + 'html5_doctype': True, } if self.theme: self.globalcontext.update( @@ -1310,13 +1306,13 @@ def validate_html_favicon(app: Sphinx, config: Config) -> None: config.html_favicon = None # type: ignore -def deprecate_html_4(_app: Sphinx, config: Config) -> None: - """Warn on HTML 4.""" - # RemovedInSphinx70Warning +def error_on_html_4(_app: Sphinx, config: Config) -> None: + """Error on HTML 4.""" if config.html4_writer: - logger.warning(_('Support for emitting HTML 4 output is deprecated and ' - 'will be removed in Sphinx 7. ("html4_writer=True" ' - 'detected in configuration options)')) + raise ConfigError(_( + 'HTML 4 is no longer supported by Sphinx. ' + '("html4_writer=True" detected in configuration options)', + )) def setup(app: Sphinx) -> dict[str, Any]: @@ -1380,7 +1376,7 @@ def setup(app: Sphinx) -> dict[str, Any]: app.connect('config-inited', validate_html_static_path, priority=800) app.connect('config-inited', validate_html_logo, priority=800) app.connect('config-inited', validate_html_favicon, priority=800) - app.connect('config-inited', deprecate_html_4, priority=800) + app.connect('config-inited', error_on_html_4, priority=800) app.connect('builder-inited', validate_math_renderer) app.connect('html-page-context', setup_css_tag_helper) app.connect('html-page-context', setup_js_tag_helper) diff --git a/sphinx/themes/basic/layout.html b/sphinx/themes/basic/layout.html index 6e9096a1d..f3088f79a 100644 --- a/sphinx/themes/basic/layout.html +++ b/sphinx/themes/basic/layout.html @@ -7,12 +7,9 @@ :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} -{%- block doctype -%}{%- if html5_doctype %} +{%- block doctype -%} -{%- else %} - -{%- endif %}{%- endblock %} +{%- endblock %} {%- set reldelim1 = reldelim1 is not defined and ' »' or reldelim1 %} {%- set reldelim2 = reldelim2 is not defined and ' |' or reldelim2 %} {%- set render_sidebar = (not embedded) and (not theme_nosidebar|tobool) and @@ -105,17 +102,10 @@ {%- if html_tag %} {{ html_tag }} {%- else %} - + {%- endif %}
- {%- if not html5_doctype and not skip_ua_compatible %} - - {%- endif %} - {%- if use_meta_charset or html5_doctype %} - {%- else %} - - {%- endif %} {{- metatags }} {%- block htmltitle %} diff --git a/sphinx/writers/_html4.py b/sphinx/writers/_html4.py deleted file mode 100644 index 0b670bf99..000000000 --- a/sphinx/writers/_html4.py +++ /dev/null @@ -1,857 +0,0 @@ -"""Frozen HTML 4 translator.""" - -from __future__ import annotations - -import os -import posixpath -import re -import urllib.parse -from typing import TYPE_CHECKING, Iterable, 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 - if scale == 100: - return length - amount, unit = matched.groups() - result = float(amount) * scale / 100 - return f"{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('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('