Repository: executablebooks/MyST-Parser (python)

Issue:
Output nested headings as rubric
This should be a quick fix when I'm back at my computer. 

When a nested parse is called, e.g. by an admonition directive, `match_title` is set to `False` by default. 
(See https://github.com/live-clones/docutils/blob/bd16bdb302f5b24340ddaa69d109d419886bf0ac/docutils/docutils/parsers/rst/states.py#L257)

Then, when a heading underline is encountered in RST, this makes it ignore that heading and just log a warning
(https://github.com/live-clones/docutils/blob/bd16bdb302f5b24340ddaa69d109d419886bf0ac/docutils/docutils/parsers/rst/states.py#L2419)

Currently in myst-parser, an error is correctly logged, but also the heading is still parsed. This can really mess up the resulting AST structure.
Instead of ignoring it though, I think it would be a nice behaviour to still log the warning (which can be suppressed in sphinx) but then output the heading as a rubric, which does not affect the AST structure. 

Pull request:
👌 IMPROVE: Convert nested headings to rubrics
Handle disallowed nested headings, by converting them to `rubric` nodes

closes #494

Patch:
diff --git a/myst_parser/docutils_renderer.py b/myst_parser/docutils_renderer.py
index 9caf091..4c7340e 100644
--- a/myst_parser/docutils_renderer.py
+++ b/myst_parser/docutils_renderer.py
@@ -584,12 +584,20 @@ class DocutilsRenderer(RendererProtocol):
     def render_heading(self, token: SyntaxTreeNode) -> None:
 
         if self.md_env.get("match_titles", None) is False:
+            # this can occur if a nested parse is performed by a directive
+            # (such as an admonition) which contains a header.
+            # this would break the document structure
             self.create_warning(
-                "Header nested in this element can lead to unexpected outcomes",
+                "Disallowed nested header found, converting to rubric",
                 line=token_line(token, default=0),
                 subtype="nested_header",
                 append_to=self.current_node,
             )
+            rubric = nodes.rubric(token.content, "")
+            self.add_line_and_source_path(rubric, token)
+            with self.current_node_context(rubric, append=True):
+                self.render_children(token)
+            return
 
         # Test if we're replacing a section level first
         level = int(token.tag[1])
diff --git a/myst_parser/mocking.py b/myst_parser/mocking.py
index 67ad419..b562e14 100644
--- a/myst_parser/mocking.py
+++ b/myst_parser/mocking.py
@@ -133,7 +133,16 @@ class MockState:
         state_machine_class=None,
         state_machine_kwargs=None,
     ) -> None:
-        """Perform a nested parse of the input block, with ``node`` as the parent."""
+        """Perform a nested parse of the input block, with ``node`` as the parent.
+
+        :param block: The block of lines to parse.
+        :param input_offset: The offset of the first line of block,
+            to the starting line of the state (i.e. directive).
+        :param node: The parent node to attach the parsed content to.
+        :param match_titles: Whether to to allow the parsing of headings
+            (normally this is false,
+            since nested heading would break the document structure)
+        """
         sm_match_titles = self.state_machine.match_titles
         render_match_titles = self._renderer.md_env.get("match_titles", None)
         self.state_machine.match_titles = self._renderer.md_env[
diff --git a/myst_parser/sphinx_renderer.py b/myst_parser/sphinx_renderer.py
index 5211892..cecbc90 100644
--- a/myst_parser/sphinx_renderer.py
+++ b/myst_parser/sphinx_renderer.py
@@ -135,6 +135,11 @@ class SphinxRenderer(DocutilsRenderer):
         The approach is similar to ``sphinx.ext.autosectionlabel``
         """
         super().render_heading(token)
+
+        if not isinstance(self.current_node, nodes.section):
+            return
+
+        # create the slug string
         slug = cast(str, token.attrGet("id"))
         if slug is None:
             return


Test patch:
diff --git a/tests/test_renderers/fixtures/reporter_warnings.md b/tests/test_renderers/fixtures/reporter_warnings.md
index 2fd6d67..3cbe211 100644
--- a/tests/test_renderers/fixtures/reporter_warnings.md
+++ b/tests/test_renderers/fixtures/reporter_warnings.md
@@ -136,7 +136,7 @@ header nested in admonition
 # Header
 ```
 .
-<string>:2: (WARNING/2) Header nested in this element can lead to unexpected outcomes
+<string>:2: (WARNING/2) Disallowed nested header found, converting to rubric
 .
 
 nested parse warning