mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2026-10-15 01:42:12 -05:00
Fix possibly-invalid HTML when a rubric node is manually created
This commit is contained in:
1 parent
ffd7b786d5
commit
18ac58bd53
7 files changed
+46
-25
No files matched your search
+4
-1
@@ -16,6 +16,9 @@ Features added
|
||||
Bugs fixed
|
||||
----------
|
||||
|
||||
* Fix invalid HTML when a rubric node with invalid ``heading-level`` is used.
|
||||
Patch by Adam Turner.
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
||||
@@ -120,7 +123,7 @@ Features added
|
||||
* #11773: Display :py:class:`~typing.Annotated` annotations
|
||||
with their metadata in the Python domain.
|
||||
Patch by Adam Turner and David Stansby.
|
||||
* #12506: Add ``level`` option to :rst:dir:`rubric` directive.
|
||||
* #12506: Add ``heading-level`` option to :rst:dir:`rubric` directive.
|
||||
Patch by Chris Sewell.
|
||||
* #12567: Add the :event:`write-started` event.
|
||||
Patch by Chris Sewell.
|
||||
|
||||
@@ -544,10 +544,10 @@ Presentational
|
||||
A rubric is like an informal heading that doesn't correspond to the document's structure,
|
||||
i.e. it does not create a table of contents node.
|
||||
|
||||
.. rst:directive:option:: level: n
|
||||
.. rst:directive:option:: heading-level: n
|
||||
:type: number from 1 to 6
|
||||
|
||||
.. versionadded:: 7.4
|
||||
.. versionadded:: 7.4.1
|
||||
|
||||
Use this option to specify the heading level of the rubric.
|
||||
In this case the rubric will be rendered as ``<h1>`` to ``<h6>`` for HTML output,
|
||||
|
||||
@@ -187,17 +187,17 @@ class Rubric(SphinxDirective):
|
||||
option_spec = {
|
||||
'class': directives.class_option,
|
||||
'name': directives.unchanged,
|
||||
'level': lambda c: directives.choice(c, ('1', '2', '3', '4', '5', '6')),
|
||||
'heading-level': lambda c: directives.choice(c, ('1', '2', '3', '4', '5', '6')),
|
||||
}
|
||||
|
||||
def run(self) -> list[Node]:
|
||||
def run(self) -> list[nodes.rubric | nodes.system_message]:
|
||||
set_classes(self.options)
|
||||
rubric_text = self.arguments[0]
|
||||
textnodes, messages = self.parse_inline(rubric_text, lineno=self.lineno)
|
||||
if 'heading-level' in self.options:
|
||||
self.options['heading-level'] = int(self.options['heading-level'])
|
||||
rubric = nodes.rubric(rubric_text, '', *textnodes, **self.options)
|
||||
self.add_name(rubric)
|
||||
if 'level' in self.options:
|
||||
rubric['level'] = int(self.options['level'])
|
||||
return [rubric, *messages]
|
||||
|
||||
|
||||
|
||||
@@ -510,10 +510,10 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
|
||||
super().depart_title(node)
|
||||
|
||||
# overwritten
|
||||
def visit_rubric(self, node: Element) -> None:
|
||||
if "level" in node:
|
||||
level = node["level"]
|
||||
if level in (1, 2, 3, 4, 5, 6):
|
||||
def visit_rubric(self, node: nodes.rubric) -> None:
|
||||
if 'heading-level' in node:
|
||||
level = node['heading-level']
|
||||
if level in {1, 2, 3, 4, 5, 6}:
|
||||
self.body.append(self.starttag(node, f'h{level}', '', CLASS='rubric'))
|
||||
else:
|
||||
logger.warning(
|
||||
@@ -527,8 +527,8 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
|
||||
super().visit_rubric(node)
|
||||
|
||||
# overwritten
|
||||
def depart_rubric(self, node: Element) -> None:
|
||||
if level := node.get("level"):
|
||||
def depart_rubric(self, node: nodes.rubric) -> None:
|
||||
if (level := node.get('heading-level')) in {1, 2, 3, 4, 5, 6}:
|
||||
self.body.append(f'</h{level}>\n')
|
||||
else:
|
||||
super().depart_rubric(node)
|
||||
|
||||
@@ -972,12 +972,12 @@ class LaTeXTranslator(SphinxTranslator):
|
||||
self.body.append(r'\end{sphinxseealso}')
|
||||
self.body.append(BLANKLINE)
|
||||
|
||||
def visit_rubric(self, node: Element) -> None:
|
||||
def visit_rubric(self, node: nodes.rubric) -> None:
|
||||
if len(node) == 1 and node.astext() in ('Footnotes', _('Footnotes')):
|
||||
raise nodes.SkipNode
|
||||
tag = 'subsubsection'
|
||||
if "level" in node:
|
||||
level = node["level"]
|
||||
if 'heading-level' in node:
|
||||
level = node['heading-level']
|
||||
try:
|
||||
tag = self.sectionnames[self.top_sectionlevel - 1 + level]
|
||||
except Exception:
|
||||
@@ -992,7 +992,7 @@ class LaTeXTranslator(SphinxTranslator):
|
||||
self.context.append('}' + CR)
|
||||
self.in_title = 1
|
||||
|
||||
def depart_rubric(self, node: Element) -> None:
|
||||
def depart_rubric(self, node: nodes.rubric) -> None:
|
||||
self.in_title = 0
|
||||
self.body.append(self.context.pop())
|
||||
|
||||
|
||||
@@ -10,30 +10,30 @@ test-markup-rubric
|
||||
:class: myclass
|
||||
|
||||
.. rubric:: A rubric with a heading level 1
|
||||
:level: 1
|
||||
:heading-level: 1
|
||||
:class: myclass
|
||||
|
||||
.. rubric:: A rubric with a heading level 2
|
||||
:level: 2
|
||||
:heading-level: 2
|
||||
:class: myclass
|
||||
|
||||
.. rubric:: A rubric with a heading level 3
|
||||
:level: 3
|
||||
:heading-level: 3
|
||||
:class: myclass
|
||||
|
||||
.. rubric:: A rubric with a heading level 4
|
||||
:level: 4
|
||||
:heading-level: 4
|
||||
:class: myclass
|
||||
|
||||
.. rubric:: A rubric with a heading level 5
|
||||
:level: 5
|
||||
:heading-level: 5
|
||||
:class: myclass
|
||||
|
||||
.. rubric:: A rubric with a heading level 6
|
||||
:level: 6
|
||||
:heading-level: 6
|
||||
:class: myclass
|
||||
|
||||
.. rubric:: A rubric with a heading level 7
|
||||
:level: 7
|
||||
:heading-level: 7
|
||||
:class: myclass
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import re
|
||||
|
||||
import pytest
|
||||
from docutils import nodes
|
||||
|
||||
from tests.test_builders.xpath_util import check_xpath
|
||||
|
||||
@@ -282,8 +283,25 @@ def test_html5_output(app, cached_etree_parse, fname, path, check):
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='markup-rubric')
|
||||
def test_html5_rubric(app):
|
||||
def insert_invalid_rubric_heading_level(app, doctree, docname):
|
||||
if docname != 'index':
|
||||
return
|
||||
new_node = nodes.rubric('', 'INSERTED RUBRIC')
|
||||
new_node['heading-level'] = 7
|
||||
doctree[0].append(new_node)
|
||||
|
||||
app.connect('doctree-resolved', insert_invalid_rubric_heading_level)
|
||||
app.build()
|
||||
assert '"7" unknown' in app.warning.getvalue()
|
||||
|
||||
warnings = app.warning.getvalue()
|
||||
content = (app.outdir / 'index.html').read_text(encoding='utf8')
|
||||
assert '<p class="rubric">This is a rubric</p>' in content
|
||||
assert '<h2 class="myclass rubric">A rubric with a heading level 2</h2>' in content
|
||||
|
||||
# directive warning
|
||||
assert '"7" unknown' in warnings
|
||||
|
||||
# html writer warning
|
||||
assert 'WARNING: unsupported rubric heading level: 7' in warnings
|
||||
assert '</h7>' not in content
|
||||
assert '<p class="rubric">INSERTED RUBRIC</p>' in content
|
||||
Reference in new issue
Block a user